在天体点唱机上实施随机播放

时间:2009-05-13 19:59:34

标签: algorithm language-agnostic math random puzzle

如何实施“Celestial Jukebox”的洗牌?

更准确地说,在每个时间t,返回0..n(t)之间的均匀随机数,这样整个序列中就没有重复,n()随着时间的推移而增加。

对于具体示例,假设一种统一费率的音乐服务,它允许通过基于0的索引号播放目录中的任何歌曲。每隔一段时间,就会增加新的歌曲,增加索引号的范围。目标是每次播放一首新歌(假设目录中没有重复)。

理想的解决方案在现有硬件上是可行的 - 我如何在8MB的DRAM中找到600万首歌曲的列表?类似地,高歌数会加剧O(n)选择时序。

- 对于LCG发生器,如果在0..N 0 上给出部分耗尽的LCG,则可以将其转换为0..N 1 上的不同LCG (其中N1> N0),不重复耗尽的序列 - 检查某首歌是否已播放似乎很快失控,虽然这可能是唯一的方法?这有一个有效的数据结构吗?

5 个答案:

答案 0 :(得分:3)

我喜欢这种非重复随机选择的方式是有一个列表,每次我在[0-N)之间随机选择一个项目时,我会从该列表中删除它。在您的情况下,当新项目添加到目录时,它也将添加到尚未选择的列表中。一旦结束,只需将所有歌曲重新加载到列表中即可。

编辑:

如果考虑到v3的建议,可以在O(1)初始化步骤后的基本O(N)时间内完成此操作。它保证了不重复的随机选择。

这是概述:

  1. 将初始项添加到列表
  2. 随机选择索引i(来自[0,N)
  3. 删除索引i
  4. 的项目
  5. i处的洞替换为Nth项(如果i == Nth,则为空,并递减N
  6. 对于新项目,只需附加到列表末尾并根据需要增加N
  7. 如果您要播放所有歌曲(我怀疑您是否有6M歌曲),然后将所有歌曲添加回列表,泡沫,冲洗和重复。
  8. 由于您正在尝试处理相当大的集合,我建议使用数据库。一个基本上包含两个字段的简单表格:id和“pointer”(其中“pointer”告诉您要播放的歌曲,可能是GUID,FileName等,具体取决于你想怎么做)。在id上有一个索引,你应该在应用程序运行之间保持稳定的性能。

    编辑8MB限制:

    嗯,这确实让它变得有点困难......在8 MB中,你可以使用32位密钥存储最多约2M的条目。

    所以我建议的是预先选择下一个2M条目。如果用户一生中播放2M歌曲,该死的!要预先选择它们,请使用上述算法执行pre-init步骤。我要做的一个改变就是当你添加新歌时,滚动骰子,看看你是否想要随意添加这首歌。如果是,则选择随机索引并将其替换为新歌曲的索引。

答案 1 :(得分:1)

对于600万首歌曲,限制为8MB,即使每首歌曲只有一个32位整数,也显然没有空间存储。除非您准备将列表存储在磁盘上(在这种情况下,请参见下文)。

如果你准备放弃将新项目立即添加到随机播放的要求,你可以在当前的歌曲集上生成一个LCG,然后当它用完时,只在那些歌曲上生成一个新的LCG。自从你开始以来添加冲洗并重复,直到您不再有新歌。您还可以使用this rather cool algorithm在任意范围内生成不可取的排列而不存储它。

如果您准备放宽对800万首歌曲的8MB ram的要求,或者转到磁盘(例如,通过内存映射),您可以在开始时从1..n生成序列,将其随机播放使用fisher-yates,每当添加新歌曲时,从最远未播放的部分中选择一个随机元素,在其中插入新ID,并将原始ID附加到列表的末尾。

如果您不太关心计算效率,可以存储所有歌曲的位图,并随机重复选择ID,直到找到尚未播放的ID。这需要600万次尝试才能找到最后一首歌(平均而言),这在现代CPU上仍然很快。

答案 2 :(得分:0)

虽然Erich的解决方案可能更适合您的特定用例,但检查歌曲是否已经播放的速度非常快(使用基于散列的结构分摊O(1)),例如Python中的set或者是C ++中的hashset<int>

答案 3 :(得分:0)

您可以简单地生成从1到n的数字序列,然后使用Fisher-Yates shuffle对其进行随机播放。这样就可以保证序列不会重复,无论n。

答案 4 :(得分:0)

您可以在数组中使用链接列表: 要构建初始播放列表,请使用包含以下内容的数组:

 struct playlistNode{
  songLocator* song;
  playlistNode  *next;
};
struct playlistNode arr[N];

还要保留“头部”和“空闲列表”指针;

在2遍中填充:
    1.用0..N。中填写目录中所有歌曲的arr     2.随机遍历所有索引,填入next指针;

删除播放的歌曲是O(1):

head=cur->next;
cur->song=NULL;
freelist->next = freelist;
cur->next=freelist;
freelist=cur;

插入新歌也是O(1):随机选择一个数组索引,并修补一个新节点。

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;