私は巨大な音楽プレイリストを持っています。同じアーティストが連続して2回再生されない、または彼の曲がほとんどプレイリストの最初または最後に出ないようにプレイリストを並べ替えたいと思います。
プレイリストの例:
$ cat /tmp/playlist.m3u
Anna A. - Song 1
Anna A. - Song 2
I--Rock - Song 1
John B. - Song 1
John B. - Song 2
John B. - Song 3
John B. - Song 4
John B. - Song 5
Kyle C. - Song 1
U--Rock - Song 1
sort -R
または次の出力shuf
:
$ sort -R /tmp/playlist.m3u
Anna A. - Song 1 #
U--Rock - Song 1
Anna A. - Song 2 # Anna's songs are all in the beginning.
John B. - Song 2
I--Rock - Song 1
John B. - Song 1
Kyle C. - Song 1
John B. - Song 4 #
John B. - Song 3 #
John B. - Song 5 # Three of John's songs in a row.
何を期待していますか?
$ some_command /tmp/playlist.m3u
John B. - Song 1
Anna A. - Song 1
John B. - Song 2
I--Rock - Song 1
John B. - Song 3
Kyle C. - Song 1
Anna A. - Song 2
John B. - Song 4
U--Rock - Song 1
John B. - Song 5
ベストアンサー1
あなたのサンプルデータと制約は実際にはいくつかの解決策しか受け入れません。たとえば、John B.を曲ごとに再生する必要があります。私はあなたの実際の完全なプレイリストが本質的にそうではないと仮定します。ジョン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.曲で始まります。それ以来、予測ははるかに困難でした(John 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);
}