If you aren't already in the know, these ten problems are from the 2008 Winter Scripting games.

For each problem, I'm going to quickly sum up the interesting (interesting TO ME) bits of each problem, then I'm going to post the full source.

Event 8: Making Beautiful Music

In this problem, we are asked to generate a random playlist for our CDs. I'll just say: I cheated. If you look carefully, you'll notice I sorted the songs from longest to shortest, making the "song packing" algorithm more likely to succeed. But it's not perfect and can fail.

So, in summary, I solved the problem like everyone else, but probably not well enough.

Source

#PROBLEM #8
$filename = "C:\Scripts\songlist.csv"
$global:nextId = 0
$minimumSecondsPerCd = (75 * 60)
$maximumSecondsPerCd = (80 * 60)
$maximumSongsPerArtistOnPlaylist = 2


function Create-SongObject ($artist, $title, $durationString)
{
    $o = new-object PSObject


    Add-Member -inputObject $o -memberType NoteProperty -name "Artist" -value $artist
    Add-Member -inputObject $o -memberType NoteProperty -name "Title" -value $title
    Add-Member -inputObject $o -memberType NoteProperty -name "Duration" -value $durationString


    Add-Member -inputObject $o -memberType ScriptProperty -name "GetSeconds" -value { Get-DurationInSeconds $this.Duration }


    Add-Member -inputObject $o -memberType NoteProperty -name "Id" -value $nextId
    $global:nextId++


    Add-Member -inputObject $o -memberType ScriptProperty -name "PrintDetails" -value { "$($this.Artist)`t$($this.Title)`t$($this.Duration)"}
   
    $o
}


#POSTSCRIPT-OOPS: I forgot the TimeSpan class! Argh! Instead of
#using TimeSpan, I implement it again here below!
function Get-DurationInSeconds ($durationString)
{
    $column = $durationString.Split(":")
    $minutes = [int]$column[0]
    $seconds = [int]$column[1]
   
    $minutes * 60 + $seconds
}


function Get-AvailableSongs
{
    foreach ($line in (cat $filename) )
    {
        $column = $line.Split(",")
        Create-SongObject -artist $column[0] -title $column[1] -durationString $column[2]
    }
}


function Sum-PlaylistTime ($playlist)
{
    #initialize to 0 in case of empty playlist
    $totalSeconds = 0
   
    foreach ($song in $playlist)
    {
        $totalSeconds += $song.GetSeconds
    }
   
    $totalSeconds
}


#POSTSCRIPT-OOPS: ok, well, this isn't an oops moment
#necessarily. But I do apologize for the THIRTY ONE
#CHARACTER variable name used below. 31!
function Has-PlaylistReachedArtistSongLimit ($artist, $playlist)
{
    $playlistArtistSongCount = $playlist | group Artist | ? { $_.Name -eq $artist } | % { $_.Count }
   
    $maximumSongsPerArtistOnPlaylist -le $playlistArtistSongCount
}


function Add-NextEligibleSong ($catalog, $existingPlaylist)
{
    $existingPlaylistIds = $existingPlaylist | % { $_.Id }
   
    foreach ($candidateSong in $catalog)
    {
        if ($existingPlaylistIds -contains $candidateSong.Id)
        {
            continue
        }
       
        if ( Has-PlaylistReachedArtistSongLimit -artist $candidateSong.Artist -playlist $existingPlaylist )
        {
            continue
        }
       
        #I should handle the possibility of building a playlist in a combinatorial manner, i.e.
        #I should be attempting to remove songs to make room for others if necessary.
        #this would be the spot for it (would require some restructuring in how I add/remove songs from the list)
        if ( ((Sum-PlaylistTime $existingPlaylist) + $candidateSong.Seconds) -gt $maximumSecondsPerCd)
        {
            continue
        }


        return $candidateSong
    }
   
    Throw "No candidate songs remain."
}


function Solve-Problem8
{
    $songs = Get-AvailableSongs | sort GetSeconds -desc
   
    $myPlaylist = @()
   
    while ((Sum-PlaylistTime $myPlaylist) -lt $minimumSecondsPerCd )
    {
        $myPlaylist += Add-NextEligibleSong -catalog $songs -existingPlaylist $myPlaylist
    }
   
    foreach ($song in $myPlaylist)
    {
        Write-Host $song.PrintDetails
    }
   
    $totalSeconds = Sum-PlaylistTime $myPlaylist
    $totalMusicMinutes = [int]([Math]::Floor($totalSeconds / 60))
    $totalRemainingSeconds = $totalSeconds % 60
    $totalMusicTime = "$($totalMusicMinutes):$($totalRemainingSeconds)"
   
    Write-Host "`nTotal music time: $totalMusicTime"
}




Solve-Problem8

2008 Winter Scripting Game Events: Index