【问题标题】:Create a randomly ordered playlist from a table of songs and return the name of the current, next, and previous song?从歌曲表中创建一个随机排序的播放列表并返回当前、下一首和上一首歌曲的名称?
【发布时间】:2009-12-16 14:00:23
【问题描述】:

考虑下表:

Song
----
SongID GUID Primary Key
Name Varchar
dateAdded DateTime

我正在创建一个播放歌曲表中歌曲的网站。我想为站点的每个访问者生成一个随机排序的播放列表,该列表在不使用任何服务器端存储(没有会话样式或数据库存储)的情况下持续存在。播放器具有播放、下一首歌曲和上一首歌曲按钮。跨请求保留此数据的最有效方法是什么?对这些信息最有效的查询是什么?我有一个可行的解决方案(在 mySql 中),但我正在寻找更有效的方法。另外,我不想让答案偏向于使用我的解决方案。请在您的回答中包含一个查询。

【问题讨论】:

  • 您希望如何处理用户获取/启动其播放列表后插入或删除的歌曲?
  • 忽略它们 :) 我也想过这个
  • 我想那么我需要一个添加日期的列,哎呀
  • 这需要在 SQL 中完成吗?你不能使用 C#、php、无论你的语言来生成随机数吗?
  • 是的,但是我需要把整张桌子拿下来才能得到下一首和上一首歌曲。

标签: sql optimization


【解决方案1】:

更新:这是我遵循的要求,详细说明:

  • 无论表中有多少歌曲或播放列表中的当前位置,客户端需要存储的信息(例如 cookie 中)都必须具有恒定大小。
  • 使用 'next' 100 次,然后使用 'prev' 100 次应该会产生 100 首歌曲的某个序列(可能包括重复),然后将确切的序列颠倒,无论在任何“下一个”之间可能已插入任何内容或“上一个”动作。 (“上一首歌曲总是指上一首歌曲。”)
  • 将跳过“在生成/初始化时”在播放列表中并已被删除的歌曲。
    • 不难改变我在下面的答案以返回未找到的指示符。

我认为您还需要一条信息:除了您的 GUID(或者您可以用它替换它)之外,还有一个整数位置的辅助歌曲键。然后,结合PRNG,您可以进行最简单快捷的查找。伪代码:

def next_song(initial_seed, current_prng_index, high_song_index):
  """Returns the next song and the parameters to be later passed
  to next/prev_song."""
  while True:
    current_prng_index += 1
    current_seed = PRNG_advance(initial_seed, current_prng_index)
    song_index = song_index_from_seed(current_seed, high_song_index)
    song_id = (SELECT SongID FROM Songs WHERE SongIndex=song_index)
    if song_id: # test, somehow, for non-deleted songs
      return song_id, (initial_seed, current_prng_index, high_song_index)
      # include values that must be passed on the next call
# prev is the same, except uses -= 1

def song_index_from_seed(seed, highest):
  return seed % (highest + 1)
  # simple, but better possibilities might exist for your data

def new_playlist():
  """Returns the parameters to be passed to next/prev_song."""
  high_song_index = (SELECT MAX(SongIndex) FROM Songs)
  return get_a_random_number(), 0, high_song_index

这将在每首下一首歌曲中逐渐变慢,并且不允许通过向后进行包装(没有一些创造力)。如果您找到合适的可逆 PRNG(尽管在好的 PRNG 中并不常见,AFAIK):

def next_song(current_seed, high_song_index):
  while True:
    current_seed = PRNG_next(current_seed)
    song_index = song_index_from_seed(current_seed, high_song_index)
    song_id = (SELECT SongID FROM Songs WHERE SongIndex=song_index)
    if song_id: # test, somehow, for non-deleted songs
      return song_id, (current_seed, high_song_index)
      # include values that must be passed on the next call
# prev is the same, except uses PRNG_prev

这通过跳过这些歌曲来处理删除(或者如果您从不删除这甚至不是问题),但是无论删除多少歌曲都不会改变顺序。插入由 song_index_from_seed 函数处理,通过约束索引使新歌曲永远不会“看到”。如果所有可用的歌曲都被删除,这也是一个无限循环,但我认为这段代码可以处理所有其他极端情况。

(将下一个/上一个版本重构为一个通用函数的简单包装器并不难,但为了清晰起见,这里省略了。)

我已经用我的位置索引有效地替换了你的 dateAdded,但这是一个改进,因为你不需要像计算位置索引那样将已删除的行保留为虚拟对象(但仍然不能重用它们的索引)按添加日期订购。

使用这样的 PRNG 看起来很幼稚,但很有效;并且您必须小心选择的 PRNG 和 song_index_from_seed 组合的行为符合您的预期:一些初始种子可能会生成“非随机”播放列表。 (不是因为它们不是随机的,而是因为听众期望混合歌曲,而不仅仅是4, 49, 9、...)

【讨论】:

    【解决方案2】:

    这里有一个想法,如果 SQL 将进行伪随机排序。我现在没有数据库来测试这个,所以实际的 SQL 可能需要调整。我还假设您的 ID 列是整数。

    随机选择两个多位素数,P1 和 P2,其中 P2 大于 P1。

    Select * from song order by (mod ( (ID * P2) , P1)), ID

    这个想法是将 ID 乘以一个素数,然后使用 P1 进行 mod 运算。这应该可以很好地对歌曲进行洗牌,并且每个主要对都会以不同的方式洗牌。

    要重新创建确切的顺序,您只需要有 P1 和 P2,它们应该只是几个字符。在请求期间来回发送没有问题。您可能需要稍微尝试一下您的素数范围,但我猜想使用所有 4、5、6 位素数应该会给您提供相当多的随机化。

    【讨论】:

    • 这是在编写你自己的 PRNG,但需要在每个 next/prev 上枚举整个表,并且完全忽略插入和删除的任何处理(导致“上一首歌曲”实际上没有给你上一首歌曲)。
    • 你说得对,插入和删除会改变预期的顺序,但只要现有歌曲的ID不改变,歌曲的相对顺序就会保持不变。新歌会被放在排序的某个地方,所以当你回击时,你可能会得到一首意想不到的奖励歌曲。如果你删除一首歌曲,它可能无论如何都应该从列表中删除,这个算法会这样做,所以我认为这是正确的处理方式。肖恩上面的评论表明插入的歌曲可以被忽略。为此,请使用添加日期/时间的列并添加“where timestamp
    【解决方案3】:

    您可以使用Pseudo random sequence。将 gseed 和播放列表位置存储在 cookie 中。伪随机种子将保证您在每个请求上重新创建相同的播放列表序列,而无需将播放列表实际存储在服务器上

    【讨论】:

    • 这是我的方法,虽然查询实际上有点困难
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-09
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多