いくつかの追加の制約を適用してファイルをランダムに混在させます。

いくつかの追加の制約を適用してファイルをランダムに混在させます。

私は巨大な音楽プレイリストを持っています。同じアーティストが連続して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.m3uchmod +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);
}

答え2

このシャッフリングをカードデッキに適用する必要がある場合は、最初にデッキを混ぜてからカードを前に並べ、隣接するクラブやハートがあるかどうかにかかわらず、左から右に処理します。 。 。そのうちの1つを除くすべてのアイテムを別の場所にランダムに移動します(同じタイプの他の場所に隣接するわけではありません)。

例えば片手で

答え3

あまり非効率的だと思わないなら...

while [ 1 ]
do
    R="`shuf playlist`"
    D="`echo "$R" | sed -e 's/ - .*//' | uniq -c -d`"
    if [ "$D" == "" ]
    then
        break
    #else # DEBUG ONLY:
    #    echo --- FAIL: ---
    #    echo "$D"
    #    echo -------------
    fi
done

echo "$R"

連続して2つ以上のJohnがない結果が得られるまでスクロールし続けます。プレイリストにゾーンが多すぎて、そのような組み合わせが存在しないか、ローリングする可能性がほとんどない場合は停止します。

入力結果の例:

John B. - Song 4
Kyle C. - Song 1
Anna A. - Song 2
John B. - Song 3
Anna A. - Song 1
John B. - Song 1
U--Rock - Song 1
John B. - Song 2
I--Rock - Song 1
John B. - Song 5

デバッグ行のコメントを削除すると、失敗した理由がわかります。

--- FAIL: ---
      3 John B.
-------------
--- FAIL: ---
      2 John B.
      2 John B.
-------------

無制限に停止すると、原因を特定するのに役立ちます。

答え4

もう一つの方法は Bash を使うことです。プレイリストをランダムな順序で読み取り、行が重複している場合はリストの反対側の端に挿入しようとし、単一の重複項目を別々に保持して別の場所に再挿入します。 3つの重複項目(最初、最後、予約済みの項目が同じ)があると失敗し、そのエラー項目がリストの最後に追加されます。ほとんどの時間にアップロードする幅広いリストを処理できるようです。

#!/bin/bash

first_artist=''
last_artist=''
bad_artist=''
bad_line=''
result=''
bad_result=''

while read line
do
    artist=${line/ - */}
    line="$line"$'\n'

    if [ "$artist" != "$first_artist" ]
    then
        result="$line""$result"
        first_artist="$artist"

        # special case: first = last
        if [ "$last_artist" == '' ]
        then
            last_artist="$artist"
        fi

        # try reinserting bad
        if [ "$bad_artist" != '' -a "$bad_artist" != "$first_artist" ]
        then
            first_artist="$bad_artist"
            result="$bad_line""$result"
            bad_artist=''
            bad_line=''
        fi
    elif [ "$artist" != "$last_artist" ]
    then
        result="$result""$line"
        last_artist="$artist"

        # try reinserting bad
        if [ "$bad_artist" != '' -a "$bad_artist" != "$last_artist" ]
        then
            last_artist="$bad_artist"
            result="$result""$bad_line"
            bad_artist=''
            bad_line=''
        fi
    else
        if [ "$bad_artist" == '' ]
        then
            bad_artist="$artist"
            bad_line="$line"
        else
            # first, last and bad are the same artist :(
            bad_result="$bad_result""$line"
        fi
    fi
done < <(shuf playlist)

# leftovers?
if [ "$bad_artist" != '' ]
then
    bad_result="$bad_result""$bad_line"
fi

echo -n "$result"
echo -n "$bad_result"

もっと賢いかもしれません... Johnの例では、Johnは常に最初のアーティストを最初に追加しようとします。したがって、真ん中に2人の異なるアーティストがいる場合、トリプルゾーンを避けるために1つを先頭に追加し、もう1つを最後に追加するのは十分賢くありません。したがって、デフォルトでは、他のすべてのアーティストがJohnである必要があるリストの場合、予想よりも多くの失敗が発生します。

関連情報