I'm currently working on a system that has several interesting problems to solve. One of the problems I tackled this weekend was how to select random members from a set one at a time until the set is exhausted. All members must be selected and no member selection can be duplicated until the entire set has been exhausted.
For illustration purposes say I'm building a shuffle function for the songs on an MP3 player. I want to randomly select a song to play, play it, remove it from the list of songs available to play and repeat the process until all songs on my MP3 player have been played.
Simple right?
Well… There's a difference between meeting requirements and coming up with an elegant solution. I want something that's efficient and scalable.
So here's my setup:
A SQL Server database with a table to hold the songs present on my MP3 player Website.
An Entity Framework repository class that contains all of my song objects as a list.
A method on the repository object to save updates to my songs.
An ASP.Net website using MVC 3.0 and .Net 4.0.
I could have added a property to each song object that indicated that it had been played in the current pass and only select my next song from those that had been played. The problem with that is that it would require another database call to make the update for each song played. Also, at the end of the pass all songs would have to be updated to set their played-in-pass property to false.
A better way would be to track the songs played in the user session and modify the list controller to exclude played songs when populating the repository.
To make this happen I used a session variable and LINQ.
Each time I played a song I added it to the session variable SongsPlayed.
if (Session["SongsPlayed"] != null || Session["SongsPlayed"].Length == 0) {
Session["SongsPlayed"] += "|" + currentSongId + "|";
}
Then, when I moved on to the next song and populated the repository of songs I excluded those that were in the list.
songRepository.songs = songRepository.songs.FindAll(s => Session["SongsPlayed"].IndexOf(s.SongId) == -1);
Now my repository of songs only returned songs I had not played in the current pass.
Lastly I needed a way to reset songs played when I reached the end of the pass. This was as simple as removing the session variable I had set to contain the songs played.
Session.Remove("SongsPlayed");
That's it. Now my MP3 player site would play through all of my songs, removing each new song played from the list of remaining songs until all of the songs had been played. No extra trips to the database, no repeated songs.
Question: What problems have you solved in a way that you were particularly proud of?