Shuffle Groups algorithm - algorithm

I need a "shuffle albums" algorithm for my audio player like in foobar2k. So the problem is: I have a list of tracks, sorted according to some criteria so that tracks with same album are all adjacent. Now I need to be able to play songs from the playlist in "shuffle albums" mode, that is, if the next track is from the same album, just play it, otherwise, go to the first track of a next random album. If the user wants to play previous track, do the same thing but backwards. So the question is: how do I know what previous album was? I really don't want to keep a history of played albums, or keep a separate list.
Currently, I implement regular shuffle mode by giving each track random shuffle index, so that I can find previous and next tracks by finding tracks with largest shuffle index smaller than current and smallest shuffle index larger than current. But it doesn't work for shuffle albums mode. Can somebody help me with this?
Sample input:
Track 1, Album A
Track 2, Album A
Track 1, Album B
Track 2, Album B
Track 3, Album B
Track 1, Album C
Track 2, Album C
Track 3, Album C
Let's say current track is Track 1, Album A. Next track will be Track 2, Album A. Next track is not from the same album, so a first track from a random album should be chosen, let's say, Track 1, Album C. What I'm doing now is choosing next track as if it was regular shuffle mode, then going to the first track of its album, thus loosing information from where I came to this album. SO when the user wants to go to the previous album, I have no information how I got there. Hope that makes the question clearer.
Thank you.

You can reuse your shuffle index technique to index albums. Now a track index is an (album shuffle index, track pos) pair. To navigate, increment / decrement the track pos; if it goes out of bounds, update the album index.
That said, you should reconsider not keeping an history; it would let you skip back much faster with a large number of albums.

Related

Most efficient way to repeatedly pair up a group of users for a quick game

I have an application where users sign on to play a quick 1v1 game (20 seconds duration). I would like to know the most efficient way to pair each user up with another user to play the game and move onto the next user without playing the same user multiple times in a row.
My first idea was to have two queue's containing the user id's of each online user. Whenever a new user goes online I would add them to whichever queue is the shortest and constantly be popping one person off the top of each queue to play each other. After the game I would simply add each user to the same queue to avoid them playing each other again. This seems good but I would like to see if there are any other more efficient ways to do this concept without needing to keep a list of previous users played on the server.
You need to matchmaking system that will prioritize players who have been waiting the longest.
You only need 1 queue and you also need to keep track of users history using a table. The table can either be temporary session data or a database table if you want permanent data across multiple sessions or if the match making server crashes. The table should have the playerID and an array of previous playerIDs that they previously played against. It can be best to limit the size of the array and use LIFO as you might not want to just store the players most recent match ups i.e. match history. Also the player could run out of players to play against if they already played against everyone else online. The table should look like this:
playerID (integer)
previousPlayerIDs (array of integers)
When a match starts you can update the the previousPlayerIDs for all the players in the match. You need to listen to an event when a player has joined the queue lets call it onPlayerJoin(). If the queue has more than 1 player you should take longest queuing player and compare their playerID against the previousPlayerIDs of each player until you find no history of a match up.
const historyLimit = 10;
function onPlayerJoin(Player newPlayer){
playerQueue.push(newPlayer);
if(playerQueue.length > 1){
for(let a=0; a<playerQueue.length-1; a++){
Player player = playerQueue[a];
for(int i=a+1; i<playerQueue.length; i++){
Player otherPlayer = playerQueue[i];
//if the player have not played before
if(otherPlayer.previousPlayerIDs.indexOf(player.id) > -1){
//save match up
player.previousPlayerIDs.push(otherPlayer.id);
otherPlayer.previousPlayerIDs.push(player.id);
//limit matchup histroy
if(player.previousPlayerIDs.length > historyLimit){
player.previousPlayerIDs.removeAt(0);
}
if(otherPlayer.previousPlayerIDs.length > historyLimit){
otherPlayer.previousPlayerIDs.removeAt(0);
}
//create lobby and remove players from the queue
createLobby(player, otherPlayer);
playerQueue.removeAt(a);
playerQueue.removeAt(i);
}
}
}
}
}
It is possible for a player to have played everyone else and they are waiting for someone to come online that they haven't played against before. You will need a reoccurring event to check if the longest waiting player has been waiting too long. If this is the case just ignore the matching of previousPlayerIDs and create a lobby for the the player up with another potentially long waiting player.
If you want you could add more columns to the table such as a timestamp when they joined the queue and their match making rank (elo). But if you just want to prioritize the most recent player you don't need these other columns.
Also this solution might not scale up very well if you have massive amounts of concurrent user but it should be fine if you have less than 1,000-10,000
Your idea doesn't work. Eventually (possibly very quickly), you'll end up in a position where two players who have played previously end up in separate queues. You can't guarantee that they'll never be selected to play together again.
Imagine this simple case:
Queue 1 Queue 2
A B
C
A and B play, and get added to Queue 2:
Queue 1 Queue 2
C A
B
A and C play, and get added to Queue 1:
Queue 1 Queue 2
A B
C
Now A and B play again. C never got the opportunity to play B.
This particular example is unlikely to occur, true. But something similar can happen even with larger queues as the space between player X and all the players he's played over time increases. The likelihood of ending up with two players playing each other again without having played every other potential player is quite high. I suspect the probability is similar to the birthday problem.
Your solution seems fine, but you should just use a single queue.

Sorting sets Q of a set S such, that all members are represented biased on S (shuffling music titles of different artists within a directory)

Starting position:
My wife has a collection of music titles stored on her walkman. The files are all stored in a single directory, with the help of a tool I wrote plus some manual assistance, in the format
Artist1 - Title1
Artist1 - Title2
...
Artist1 - TitleN
Artist2 - Title1
...
ArtistM - TitleZ
Some artists have just as few as a handful titles (one featuring just 1 title), while others sport as many as 56 titles. All in all, the growing collection encompasses a 4-digits number of n titles now (about 1200).
These titles are all sorted alphabetically within that single directory, so that a title can be found quite easily, mainly for the purpose of removing a now disliked title from the collection.
This said, it should be obvious, that this collection is to be played randomly, using the walkman's built-in shuffle mode.
Problem:
The shuffle mode of this walkman is really not worth being called this way.
It is quite common, that songs from the same artist (stored sequentially in the folder) are played consecutively, not rarely even 3 of them in a row. This behaviour certainly is not perceived as "random".
Approach:
My approach was to pre-shuffle these titles programmatically, to be played sequentially. The strategy was to ensure, that an artist does not play 2 of her songs in a row.
For this I let my program evaluate for each artist the number of titles present, to determine the artist with the most titles, and found m=56. From that number I programmatically find the next prime p=59.
Now the idea was to iterate through the original alphabetically sorted list in a step width of n mod p, so that eventually all titles get copied in a way that they don't expose two consecutive titles of the same artist.
This works as expected.
However, after about n/p (currently about 30) titles, the artists repeat (with different titles, but still giving the impression "just heard her"), while many other artists featuring less than m titles are quite never played.
Question:
Is there a mathematical approach to improve my psychologically quite trivial approach to play as many titles from different artists as possible before playing an arbitrary other one of her titles (even at the expense of playing underrepresented artists more often), other than just trivially keeping track of every artist's already copied title and duplicating titles in the play list?

Efficiently and dynamically rank many users in memory?

I run a Java game server where I need to efficiently rank players in various ways. For example, by score, money, games won, and other achievements. This is so I can recognize the top 25 players in a given category to apply medals to those players, and dynamically update them as the rankings change. Performance is a high priority.
Note that this cannot easily be done in the database only, as the ranks will come from different sources of data and different database tables, so my hope is to handle this all in memory, and call methods on the ranked list when a value needs to be updated. Also, potentially many users can tie for the same rank.
For example, let's say I have a million players in the database. A given player might earn some extra points and instantly move from 21,305th place to 23rd place, and then later drop back off the top 25 list. I need a way to handle this efficiently. I imagine that some kind of doubly-linked list would be used, but am unsure of how to handle quickly jumping many spots in the list without traversing it one at a time to find the correct new ranking. The fact that players can tie complicates things a little bit, as each element in the ranked list can have multiple users.
How would you handle this in Java?
I don't know whether there is library that may help you, but I think you can maintain a minimum heap in the memory. When a player's point updates, you can compare this to the root of the heap, if less than,do nothing.else adjust the heap.
That means, you can maintain a minimum heap that has 25 nodes which are the highest 25 of all the players in one category.
Forget linked list. It allows fast insertions, but no efficient searching, so it's of no use.
Use the following data
double threshold
ArrayList<Player> top;
ArrayList<Player> others; (3)
and manage the following properties
each player in top has a score greater or equal to threshold
each player in others has a score lower than threshold
top is sorted
top.size() >= 25
top.size() < 25 + N where N is some arbitrary limit (e.g., 50)
Whenever some player raises it score, do the following:
if they're in top, sort top (*)
if they're in others, check if their score promotes them to top
if so, remove them from others, insert in top, and sort top
if top grew too big, move the n/2 worst players from top to others and update threshold
Whenever some player lowers it score, do the following:
- if they're in others, do nothing
- if they're in top, check if their new score allows them to stay in top
- if so, sort top (1)
- otherwise, demote them to bottom, and check if top got too small
- if so, determine an appropriate new threshold and move all corresponding players to top. (2)
(1) Sorting top is cheap as it's small. Moreover, TimSort (i.e., the algorithm behind Arrays.sort(Object[])) works very well on partially sorted sequences. Instead of sorting, you can simply remember that top is unsorted and sort it later when needed.
(2) Determining a proper threshold can be expensive and so can be moving the players. That's why only N/2 player get moved away from it when it grows too big. This leaves some spare players and makes this case pretty improbable assuming that players rarely lose score.
EDIT
For managing the objects, you also need to be able to find them in the lists. Either add a corresponding field to Player or use a TObjectIntHashMap.
EDIT 2
(3) When removing an element from the middle of others, simply replace the element by the last one and shorten the list by one. You can do it as the order doesn't matter and you must do it because of speed. (4)
(4) The whole others list needn't be actually stored anywhere. All you need is a possibility to iterate all the players not contained in top. This can be done by using an additional Set or by simply iterating though all the players and skipping those scoring above threshold.
FINAL RECOMMENDATIONS
Forget the others list (unless I'm overlooking something, you won't need it).
I guess you will need no TObjectIntHashMap either.
Use a list top and a boolean isTopSorted, which gets cleared whenever a top score changes or a player gets promoted to top (simple condition: oldScore >= threshold | newScore >= threshold).
For handling ties, make top contain at least 25 differently scored players. You can check this condition easily when printing the top players.
I assume you may use plenty of memory to do that or memory is not a concern for you. Now as you want only the top 25 entries for any category, I would suggest the following:
Have a HashSet of Player objects. Player objects have the info like name, games won, money etc.
Now have a HashMap of category name vs TreeSet of top 25 player objects in that category. The category name may be a checksum of some columns say gamewon, money, achievent etc.
HashMap<String /*for category name */, TreeSet /*Sort based on the criteria */> ......
Whenever you update a player object, you update the common HashSet first and then check if the player object is a candidate for top 25 entries in any of the categories. If it is a candidate, some player object unfortunately may lose their ranking and hence may get kicked out of the corresponding treeset.
>> if you make the TreeSet sorted by the score, it'll break whenever the score changes (and the player will not be found in it
Correct. Now I got the point :) . So, I will do the following to mitigate the problem. The player object will have a field that indicate whether it is already in some categories, basically a set of categories it is already in. While updating a player object, we have to check if the player is already in some categories, if it is in some categories already, we will rearrange the corresponding treeset first; i.e. remove the player object and readjust the score and add it back to the treeset. Whenever a player object is kicked out of a category, we remove the category from the field which is holding a set of categories the player is in
Now, what you do if the look-up is done with a brand new search criteria (means the top 25 is not computed for this criteria already) ? ------
Traverse the HashMap and build the top entries for this category from "scratch". This will be expensive operation, just like indexing something afresh.

Improve "resolution" of random data

I've been working on an MPD front end in Ruby, with the ability to play a random album.
album = all[(rand*all.length).floor]
Where all is an array of the names of all albums in the library, chooses the album to play.
This works, however, I find it plays some albums more than others, and sometimes very obviously (I've seen it play the same album twice in a row, more than once, my library has a few hundred albums, so this should statistically be very unlikely to happen), and on the other end, a lot of albums never get played.
Is there any way that I can get a more random number? Is there a gem that implements a better random number algorithm? Do I need to change the seed?
Instead of doing a new random selection every time, shuffle the list once and then just take albums off that shuffled queue until you feel like resetting.
queue = albums.sort_by{rand}
while next = queue.pop
play next
end
I don't know if this will improve the results or not, but you can do rand(all.length) to get an integer directly.
Interestingly, lots of folks think the random shuffle of the IPod Shuffle isn't random either. So this puts you in good company. :-)
http://www.npr.org/templates/story/story.php?storyId=89408926&ft=1&f=1006
http://www.cnet.com.au/itunes-just-how-random-is-random-339274094.htm?omnRef=NULL
A main point of the above articles is human's sense of what is random is flawed. You probably do have a random selection.
But you may want to implement a similar "random but no repeats" feature as the IPod Shuffle has. Or "random but biased towards favorites."
What you want is Normally Distributed Random Numbers
You should also check RandomR

Implementing shuffle on the celestial jukebox

How would one implement shuffle for the "Celestial Jukebox"?
More precisely, at each time t, return an uniform random number between 0..n(t), such that there are no repeats in the entire sequence, with n() increasing over time.
For the concrete example, assume a flat-rate music service which allows playing any song in the catalog by a 0 based index number. Every so often, new songs are added which increase range of index numbers. The goal is to play a new song each time (assuming no duplicates in the catalog).
an ideal solution would be feasible on existing hardware - how would I shoehorn a list of six million songs in 8MB of DRAM? Similarly, the high song count exacerbates O(n) selection timings.
-- For an LCG generator, given a partially exhausted LCG on 0..N0, can that be translated to a different LCG on 0..N1 (where N1 > N0), that doen't repeat the exhausted sequence.
-- Checking if a particular song has already been played seems to rapidly grow out of hand, although this might be the only way ? Is there an efficient data structure for this?
The way that I like to do that kind of non-repeating random selection is to have a list, and each time I select an item at random between [0-N), I remove it from that list. In your case, as new items get added to the catalog, it would also be added to the not-yet-selected list. Once you get to the end, simply reload all the songs back to the list.
EDIT:
If you take v3's suggestion into account, this can be done in basically O(1) time after the O(N) initialization step. It guarantees non-repeating random selection.
Here is the recap:
Add the initial items to a list
Pick index i at random (from set of [0,N))
Remove item at index i
Replace the hole at i with the Nth item (or null if i == Nth) and decrement N
For new items, simply append to the end of the list and increment N as necessary
If you ever get to playing through all the songs (which I doubt if you have 6M songs), then add all the songs back to the list, lather, rinse, and repeat.
Since you are trying to deal with rather large sets, I would recommend the use of a DB. A simple table with basically two fields: id and "pointer" (where "pointer" is what tells you the song to play which could be a GUID, FileName, etc, depending on how you want to do it). Have an index on id and you should get very decent performance with persistence between application runs.
EDIT for 8MB limit:
Umm, this does make it a bit harder... In 8 MB, you can store a maximum of ~2M entries using 32-bit keys.
So what I would recommend is to pre-select the next 2M entries. If the user plays through 2M songs in a lifetime, damn! To pre-select them, do a pre-init step using the above algorithm. The one change I would make is that as you add new songs, roll the dice and see if you want to randomly add that song to the mix. If yes, then pick a random index and replace it with the new song's index.
With a limit of 8MB for 6 million songs, there's plainly not room to store even a single 32 bit integer for each song. Unless you're prepared to store the list on disk (in which case, see below).
If you're prepared to drop the requirement that new items be immediately added to the shuffle, you can generate an LCG over the current set of songs, then when that is exhausted, generate a new LCG over only the songs that were added since you began. Rinse and repeat until you no longer have any new songs. You can also use this rather cool algorithm that generates an unguessable permutation over an arbitrary range without storing it.
If you're prepared to relax the requirement of 8MB ram for 6 million songs, or to go to disk (for example, by memory mapping), you could generate the sequence from 1..n at the beginning, shuffle it with fisher-yates, and whenever a new song is added, pick a random element from the so-far-unplayed section, insert the new ID there, and append the original ID to the end of the list.
If you don't care much about computational efficiency, you could store a bitmap of all songs, and repeatedly pick IDs uniformly at random until you find one you haven't played yet. This would take 6 million tries to find the last song (on average), which is still damn fast on a modern CPU.
While Erich's solution is probably better for your specific use case, checking if a song has already been played is very fast (amortized O(1)) with a hash-based structure, such as a set in Python or a hashset<int> in C++.
You could simply generate the sequence of numbers from 1 to n and then shuffle it using a Fisher-Yates shuffle. That way you can guarantee that the sequence won't repeat, regardless of n.
You could use a linked list inside an array:
To build the initial playlist, use an array containing a something like this:
struct playlistNode{
songLocator* song;
playlistNode *next;
};
struct playlistNode arr[N];
Also keep a 'head' and 'freelist' pointer;
Populate it in 2 passes:
1. fill in arr with all the songs in the catalog in order 0..N.
2. randomly iterate through all the indexes, filling in the next pointer;
Deletion of songs played is O(1):
head=cur->next;
cur->song=NULL;
freelist->next = freelist;
cur->next=freelist;
freelist=cur;
Insertion of new songs is O(1) also: pick an array index at random, and patch a new node.
node = freelist;
freelist=freelist->next;
do {
i=rand(N);
} while (!arr[i].song); //make sure you didn't hit a played node
node->next = arr[i].next;
arr[i].next=node;

Resources