サンプルデータと制約では、実際にはいくつかのソリューションしか許可されていません。たとえば、ジョンBを1曲おきに再生する必要があります。あなたの実際の完全なプレイリストは本質的にジョンBではなく、ランダムに他のものを分割すると仮定します。
これは別のランダムなアプローチです。@frostschutzのソリューションとは異なり、すぐに実行されます。ただし、条件に一致する結果を保証するものではありません。また、サンプルデータで機能する2番目のアプローチも紹介しますが、実際のデータでは悪い結果が生じると思われます。実際のデータ(難読化済み)を使用して、アプローチ3を追加します。これは、同じアーティストによる連続した2曲を避けることを除いて、一様なランダムです。残りの曲の「デッキ」に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
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.の歌で始まります。その後は、予測が難しくなります(ジョンB.を除く全員が1曲残っているため)。これは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);
}