您的示例数据和约束实际上仅允许一些解决方案,例如,您必须每隔一首歌曲播放John B.。我将假设您的实际完整播放列表实质上不是John B,并且随机分配了其他内容。
这是另一种随机方法。与@frostschutz的解决方案不同,它可以快速运行。但是,它不能保证结果符合您的标准。我还提出了第二种方法,该方法适用于您的示例数据-但我怀疑会对您的真实数据产生不好的结果。有了您的真实数据(混淆),我添加了方法3-这是一个统一的随机变量,只是它避免了同一位艺术家连续演唱两首歌曲。请注意,它只会对剩余歌曲的“ deck”进行5次“抽奖”,如果之后仍然面对重复的歌手,它将以任何方式输出该歌曲-这样可以保证程序实际上完成。
方法1
基本上,它会在每个点生成一个播放列表,询问“我还有哪些艺术家没有播放过歌曲?” 然后选择一个随机的艺术家,最后从该艺术家中随机选择一首歌曲。(也就是说,每个艺术家的权重均等,而不是与歌曲数量成正比。)
试试看您的实际播放列表,看看它是否比统一播放产生更好的结果。
使用方法:./script-file < input.m3u > output.m3u
请确保一定chmod +x
要这样做。请注意,它不能正确处理某些M3U文件顶部的签名行...但是您的示例没有。
#!/usr/bin/perl
use warnings qw(all);
use strict;
use List::Util qw(shuffle);
# split the input playlist by artist
my %by_artist;
while (defined(my $line = <>)) {
my $artist = ($line =~ /^(.+?) - /)
? $1
: 'UNKNOWN';
push @{$by_artist{$artist}}, $line;
}
# sort each artist's songs randomly
foreach my $l (values %by_artist) {
@$l = shuffle @$l;
}
# pick a random artist, spit out their "last" (remeber: in random order)
# song, remove from the list. If empty, remove artist. Repeat until no
# artists left.
while (%by_artist) {
my @a_avail = keys %by_artist;
my $a = $a_avail[int rand @a_avail];
my $songs = $by_artist{$a};
print pop @$songs;
@$songs or delete $by_artist{$a};
}
方法2
第二种方法是,您可以使用挑选歌曲最多的歌手(而不是我们挑选的最后一位歌手)来代替随机挑选歌手。该程序的最后一段变为:
# pick the artist with the most songs who isn't the last artist, spit
# out their "last" (remeber: in random order) song, remove from the
# list. If empty, remove artist. Repeat until no artists left.
my $last_a;
while (%by_artist) {
my %counts = map { $_, scalar(@{$by_artist{$_}}) } keys %by_artist;
my @sorted = sort { $counts{$b} <=> $counts{$a} } shuffle keys %by_artist;
my $a = (1 == @sorted)
? $sorted[0]
: (defined $last_a && $last_a eq $sorted[0])
? $sorted[1]
: $sorted[0];
$last_a = $a;
my $songs = $by_artist{$a};
print pop @$songs;
@$songs or delete $by_artist{$a};
}
该程序的其余部分保持不变。请注意,到目前为止,这并不是最有效的方法,但是对于任何理智大小的播放列表,它应该足够快。使用您的示例数据,所有生成的播放列表将以John B.歌曲开始,然后是Anna A.歌曲,然后是John B.歌曲。之后,它的可预测性要差得多(因为除John B.之外的每个人都只有一首歌)。请注意,这假定使用Perl 5.7或更高版本。
方法3
用法与前面的2相同。请注意0..4
,这是5次尝试最大笔数的来源。您可以增加尝试次数,例如,0..9
总共可以尝试10次。(0..4
= 0, 1, 2, 3, 4
,您实际上会注意到5个项目)。
#!/usr/bin/perl
use warnings qw(all);
use strict;
# read in playlist
my @songs = <>;
# Pick one randomly. Check if its the same artist as the previous song.
# If it is, try another random one. Try again 4 times (5 total). If its
# still the same, accept it anyway.
my $last_artist;
while (@songs) {
my ($song_idx, $artist);
for (0..4) {
$song_idx = int rand @songs;
$songs[$song_idx] =~ /^(.+?) - /;
$artist = $1;
last unless defined $last_artist;
last unless defined $artist; # assume unknown are all different
last if $last_artist ne $artist;
}
$last_artist = $artist;
print splice(@songs, $song_idx, 1);
}