更好的算法来随机洗牌(或交错)不同长度的多个列表

发布于 2024-10-27 08:46:48 字数 2517 浏览 2 评论 0原文

我喜欢在旅途中观看我最喜欢的电视节目。我的播放列表中包含我正在关注的每个节目的所有剧集。并非所有节目都包含相同数量的剧集。与一些喜欢马拉松的人不同,我喜欢将一个节目的剧集与另一个节目的剧集交织在一起。

例如,如果我有一个名为 ABC 的节目,有 2 集,还有一个名为 XYZ 的节目,有 4 集,我希望我的播放列表如下所示:

XYZe1.mp4
ABCe1.mp4
XYZe2.mp4
XYZe3.mp4
ABCe2.mp4
XYZe4.mp4

生成此交错播放列表的一种方法是将每个节目表示为剧集列表,并且在所有节目中进行 riffle shuffle。人们可以编写一个函数,计算每个剧集在单位时间间隔上的位置(0.0 和 1.0 之间不包括在内,0.0 是季节开始,1.0 是季节结束),然后根据位置对所有剧集进行排序。

我在 Python 2.7 中编写了以下简单函数来执行 in-shuffle:

def riffle_shuffle(piles_list):
    scored_pile = ((((item_position + 0.5) / len(pile), len(piles_list) - pile_position), item) for pile_position, pile in enumerate(piles_list) for item_position, item in enumerate(pile))
    shuffled_pile = [item for score, item in sorted(scored_pile)]
    return shuffled_pile

要获取上面示例的播放列表,我只需调用:

riffle_shuffle([['ABCe1.mp4', 'ABCe2.mp4'], ['XYZe1.mp4', 'XYZe2.mp4', 'XYZe3.mp4', 'XYZe4.mp4']])

这在大多数情况下工作得相当好。然而,在某些情况下,结果并非最佳——播放列表中的两个相邻条目是同一节目的剧集。例如:

>>> riffle_shuffle([['ABCe1', 'ABCe2'], ['LMNe1', 'LMNe2', 'LMNe3'], ['XYZe1', 'XYZe2', 'XYZe3', 'XYZe4', 'XYZe5']])
['XYZe1', 'LMNe1', 'ABCe1', 'XYZe2', 'XYZe3', 'LMNe2', 'XYZe4', 'ABCe2', 'LMNe3', 'XYZe5']

请注意,“XYZ”有两集并排出现。这种情况可以简单地修复(手动将“ABCe1”与“XYZe2”交换)。

我很想知道是否有更好的方法在不同长度的多个列表上交错或执行 riffle shuffle。我想知道您是否有更简单、更高效或只是简单优雅的解决方案。


贝利撒留提出的解决方案(谢谢!):

import itertools
def riffle_shuffle_belisarius(piles_list):
    def grouper(n, iterable, fillvalue=None):
        args = [iter(iterable)] * n
        return itertools.izip_longest(fillvalue=fillvalue, *args)
    if not piles_list:
        return []
    piles_list.sort(key=len, reverse=True)
    width = len(piles_list[0])
    pile_iters_list = [iter(pile) for pile in piles_list]
    pile_sizes_list = [[pile_position] * len(pile) for pile_position, pile in enumerate(piles_list)]
    grouped_rows = grouper(width, itertools.chain.from_iterable(pile_sizes_list))
    grouped_columns = itertools.izip_longest(*grouped_rows)
    shuffled_pile = [pile_iters_list[position].next() for position in itertools.chain.from_iterable(grouped_columns) if position is not None]
    return shuffled_pile

运行示例:

>>> riffle_shuffle_belisarius([['ABCe1', 'ABCe2'], ['LMNe1', 'LMNe2', 'LMNe3'], ['XYZe1', 'XYZe2', 'XYZe3', 'XYZe4', 'XYZe5']])
['XYZe1', 'LMNe1', 'XYZe2', 'LMNe2', 'XYZe3', 'LMNe3', 'XYZe4', 'ABCe1', 'XYZe5', 'ABCe2']

I like to watch my favorite TV shows on the go. I have all episodes of each show I'm following in my playlist. Not all shows consist of the same number of episodes. Unlike some who prefer marathons, I like to interleave episodes of one show with those of another.

For example, if I have a show called ABC with 2 episodes, and a show called XYZ with 4 episodes, I would like my playlist to look like:

XYZe1.mp4
ABCe1.mp4
XYZe2.mp4
XYZe3.mp4
ABCe2.mp4
XYZe4.mp4

One way to generate this interleaved playlist is to represent each show as a list of episodes and perform a riffle shuffle on all shows. One could write a function that would compute, for each episode, its position on a unit-time interval (between 0.0 and 1.0 exclusive, 0.0 being beginning of season, 1.0 being end of season), then sort all episodes according to their position.

I wrote the following simple function in Python 2.7 to perform an in-shuffle:

def riffle_shuffle(piles_list):
    scored_pile = ((((item_position + 0.5) / len(pile), len(piles_list) - pile_position), item) for pile_position, pile in enumerate(piles_list) for item_position, item in enumerate(pile))
    shuffled_pile = [item for score, item in sorted(scored_pile)]
    return shuffled_pile

To get the playlist for the above example, I simply need to call:

riffle_shuffle([['ABCe1.mp4', 'ABCe2.mp4'], ['XYZe1.mp4', 'XYZe2.mp4', 'XYZe3.mp4', 'XYZe4.mp4']])

This works fairly well most of the time. However, there are cases where results are non-optimal--two adjacent entries in the playlist are episodes from the same show. For example:

>>> riffle_shuffle([['ABCe1', 'ABCe2'], ['LMNe1', 'LMNe2', 'LMNe3'], ['XYZe1', 'XYZe2', 'XYZe3', 'XYZe4', 'XYZe5']])
['XYZe1', 'LMNe1', 'ABCe1', 'XYZe2', 'XYZe3', 'LMNe2', 'XYZe4', 'ABCe2', 'LMNe3', 'XYZe5']

Notice that there are two episodes of 'XYZ' that appear side-by-side. This situation can be fixed trivially (manually swap 'ABCe1' with 'XYZe2').

I am curious to know if there are better ways to interleave, or perform riffle shuffle, on multiple lists of varying lengths. I would like to know if you have solutions that are simpler, more efficient, or just plain elegant.


Solution proposed by belisarius (thanks!):

import itertools
def riffle_shuffle_belisarius(piles_list):
    def grouper(n, iterable, fillvalue=None):
        args = [iter(iterable)] * n
        return itertools.izip_longest(fillvalue=fillvalue, *args)
    if not piles_list:
        return []
    piles_list.sort(key=len, reverse=True)
    width = len(piles_list[0])
    pile_iters_list = [iter(pile) for pile in piles_list]
    pile_sizes_list = [[pile_position] * len(pile) for pile_position, pile in enumerate(piles_list)]
    grouped_rows = grouper(width, itertools.chain.from_iterable(pile_sizes_list))
    grouped_columns = itertools.izip_longest(*grouped_rows)
    shuffled_pile = [pile_iters_list[position].next() for position in itertools.chain.from_iterable(grouped_columns) if position is not None]
    return shuffled_pile

Example run:

>>> riffle_shuffle_belisarius([['ABCe1', 'ABCe2'], ['LMNe1', 'LMNe2', 'LMNe3'], ['XYZe1', 'XYZe2', 'XYZe3', 'XYZe4', 'XYZe5']])
['XYZe1', 'LMNe1', 'XYZe2', 'LMNe2', 'XYZe3', 'LMNe3', 'XYZe4', 'ABCe1', 'XYZe5', 'ABCe2']

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

欲拥i 2024-11-03 08:46:48

确定性解决方案(即非随机)

按剧集数量的递减顺序对节目进行排序。

选择最大的并排列一个矩阵,其列数与这一集的集数相对应,按以下方式填写:

A   A   A   A   A   A  <- First show consist of 6 episodes
B   B   B   B   C   C  <- Second and third show - 4 episodes each
C   C   D   D          <- Third show 2 episodes

然后按列收集

{A,B,C}, {A,B,C}, {A,B,D}, {A,B,D}, {A,C}, {A,C} 

然后加入

{A,B,C,A,B,C,A,B,D,A,B,D,A,C,A,C}

,现在分配序号

{A1, B1, C1, A2, B2, C2, A3, B3, D1, A4, B4, D2, A5, C3, A6, C4}

编辑

您的案例

[['A'] * 2, ['L'] * 3, ['X'] * 5])

X  X  X  X  X
L  L  L  A  A

-> {X1, L1, X2, L2, X3, L3, X4, A1, X5, A2}

< strong>编辑2

由于这里没有Python,也许Mathematica代码可能会有一些用处:

l = {, , ,};                                 (* Prepare input *)
l[[1]] = {a, a, a, a, a, a};
l[[2]] = {b, b, b, b};
l[[3]] = {c, c, c, c};
l[[4]] = {d, d};
le = Length@First@l;

k = DeleteCases[                              (*Make the matrix*)
   Flatten@Transpose@Partition[Flatten[l], le, le, 1, {Null}], Null];

Table[r[i] = 1, {i, k}];                      (*init counters*)
ReplaceAll[#, x_ :> x[r[x]++]] & /@ k         (*assign numbers*)

->{a[1], b[1], c[1], a[2], b[2], c[2], a[3], b[3], d[1], a[4], b[4], 
   d[2], a[5], c[3], a[6], c[4]}

A deterministic solution (ie not random)

Sort your shows by decreasing number of episodes.

Select the biggest and arrange a matrix with the number of columns corresponding to the number of episodes of this one, filled in the following way:

A   A   A   A   A   A  <- First show consist of 6 episodes
B   B   B   B   C   C  <- Second and third show - 4 episodes each
C   C   D   D          <- Third show 2 episodes

Then collect by columns

{A,B,C}, {A,B,C}, {A,B,D}, {A,B,D}, {A,C}, {A,C} 

Then Join

{A,B,C,A,B,C,A,B,D,A,B,D,A,C,A,C}

And now assign sequential numbers

{A1, B1, C1, A2, B2, C2, A3, B3, D1, A4, B4, D2, A5, C3, A6, C4}

Edit

Your case

[['A'] * 2, ['L'] * 3, ['X'] * 5])

X  X  X  X  X
L  L  L  A  A

-> {X1, L1, X2, L2, X3, L3, X4, A1, X5, A2}

Edit 2

As no Python here, perhaps a Mathematica code may be of some use:

l = {, , ,};                                 (* Prepare input *)
l[[1]] = {a, a, a, a, a, a};
l[[2]] = {b, b, b, b};
l[[3]] = {c, c, c, c};
l[[4]] = {d, d};
le = Length@First@l;

k = DeleteCases[                              (*Make the matrix*)
   Flatten@Transpose@Partition[Flatten[l], le, le, 1, {Null}], Null];

Table[r[i] = 1, {i, k}];                      (*init counters*)
ReplaceAll[#, x_ :> x[r[x]++]] & /@ k         (*assign numbers*)

->{a[1], b[1], c[1], a[2], b[2], c[2], a[3], b[3], d[1], a[4], b[4], 
   d[2], a[5], c[3], a[6], c[4]}
阪姬 2024-11-03 08:46:48

我的尝试:

program, play = [['ABCe1.mp4', 'ABCe2.mp4'], 
                 ['XYZe1.mp4', 'XYZe2.mp4', 'XYZe3.mp4', 'XYZe4.mp4', 
                  'XYZe5.mp4', 'XYZe6.mp4', 'XYZe7.mp4'],
                 ['OTHERe1.mp4', 'OTHERe2.mp4']], []
start_part = 3
while any(program):
    m = max(program, key = len)
    if (len(play) >1 and 
        play[-1][:start_part] != m[0][:start_part] and 
        play[-2].startswith(play[-1][:start_part])):
        play.insert(-1, m.pop(0))
    else:
        play.append(m.pop(0))

print play

My try:

program, play = [['ABCe1.mp4', 'ABCe2.mp4'], 
                 ['XYZe1.mp4', 'XYZe2.mp4', 'XYZe3.mp4', 'XYZe4.mp4', 
                  'XYZe5.mp4', 'XYZe6.mp4', 'XYZe7.mp4'],
                 ['OTHERe1.mp4', 'OTHERe2.mp4']], []
start_part = 3
while any(program):
    m = max(program, key = len)
    if (len(play) >1 and 
        play[-1][:start_part] != m[0][:start_part] and 
        play[-2].startswith(play[-1][:start_part])):
        play.insert(-1, m.pop(0))
    else:
        play.append(m.pop(0))

print play
奈何桥上唱咆哮 2024-11-03 08:46:48

这将确保节目的两个连续剧集之间至少有 1 个且不超过 2 个其他剧集:

  • 虽然有超过 3 个节目,但将两个最短(即具有最少剧集)的节目首尾相连。
  • 设 A 为最长的节目,B 和 C 为另外两个。
  • 如果 B 比 A 短,则在末尾填充 None
  • 如果 C 比 A 短,则在开头填充 None
  • 随机播放列表为 < code>[x for x in itertools.chain(zip(A,B,C)) if x is not None]

This would ensure that there is at least 1 and no more than 2 other episodes between two successive episodes of a show:

  • While there are more than 3 shows, chain two shortest (i.e. having least episodes) shows together end-to-end.
  • Let A be the longest show and B and C the other two.
  • If B is shorter than A, pad it with None's at the end
  • If C is shorter than A, pad it with None's at the beginning
  • Shuffled playlist is [x for x in itertools.chain(zip(A,B,C)) if x is not None]
嘦怹 2024-11-03 08:46:48

这将确保真正的随机播放,即每次都有不同的结果,并且尽可能没有连续的项目。

您询问的人可能会返回一些(1、2)个结果,但受您的请求限制。

from random import choice, randint
from operator import add

def randpop(playlists):
    pl = choice(playlists)
    el = pl.pop(randint(0, len(pl) -1))
    return pl, el

def shuffle(playlists):
    curr_pl = None
    while any(playlists):
        try:
            curr_pl, el = randpop([pl for pl in playlists if pl and pl != curr_pl])
        except IndexError:
            break
        else:
            yield el
    for el in reduce(add, playlists):
        yield el
    raise StopIteration

if __name__ == "__main__":
    sample = [
        'A1 A2 A3 A4'.split(),
        'B1 B2 B3 B4 B5'.split(),
        'X1 X2 X3 X4 X5 X6'.split()
        ]
    for el in shuffle(sample):
        print(el)

编辑:

给定的剧集顺序是强制性的,只需简化 randpop 函数即可:

def randpop(playlists):
    pl = choice(playlists)
    el = pl.pop(0)
    return pl, el

This will ensure true shuffle i.e. a different result each time, with no contiguous items as much as possible.

The one you ask probably could return a few (1, 2) results limited by your requests.

from random import choice, randint
from operator import add

def randpop(playlists):
    pl = choice(playlists)
    el = pl.pop(randint(0, len(pl) -1))
    return pl, el

def shuffle(playlists):
    curr_pl = None
    while any(playlists):
        try:
            curr_pl, el = randpop([pl for pl in playlists if pl and pl != curr_pl])
        except IndexError:
            break
        else:
            yield el
    for el in reduce(add, playlists):
        yield el
    raise StopIteration

if __name__ == "__main__":
    sample = [
        'A1 A2 A3 A4'.split(),
        'B1 B2 B3 B4 B5'.split(),
        'X1 X2 X3 X4 X5 X6'.split()
        ]
    for el in shuffle(sample):
        print(el)

Edit:

Given episodes order is mandatory just simplify randpop function:

def randpop(playlists):
    pl = choice(playlists)
    el = pl.pop(0)
    return pl, el
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文