いくつかの追加の制約を使用してファイルをランダムにシャッフルします


12

私には巨大な音楽プレイリストがあり、多くのアルバムを持っているアーティストもいれば、1曲しか持っていないアーティストもいます。プレイリストを並べ替えて、同じアーティストが連続して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

13
技術的には、求めているのはランダム性が少なく、構造が多いことです。不可能ではありませんが、(bash / awk / perl / python / etc)スクリプトが必要です。
goldilocks

または構造化されたランダム性 :)
テレサeジュニア

丁度!これは、perlまたはpythonで適切な演習になります。awkではうまくいくかもしれませんが、bashでは頭痛の種になると思います-awkで十分に言えません。
-goldilocks

それを行うためのツールはないように思われるため、スクリプトを使用する方法があるようです。私が怠けているわけではありませんが、私はアイデアがありません。
テレサeジュニア

1
簡単なアルゴリズムでこれを行うことができます。各アーティストが順番にランダムな曲を選択することでプレイリストを作成します(アーティストの繰り返しなしで順番もランダム化できます)。1人のアーティストによるすべての曲が使い果たされたら、同じアーティストによる曲の隣接を最小限に抑えるような方法で、残りのアーティストによる曲を交互に既存のプレイリストと交互に開始します。完了するまで繰り返してください。これを実際のスクリプトにパンする時間がないのは残念です。私はあなたがあなた自身のものを転がすのを助けることが役に立つかもしれないとちょうど思いました。
ジョセフR.

回答:


5

そのシャッフルをトランプのデッキに適用しなければならなかった場合、最初にデッキをシャッフルし、次に目の前にカードを並べて表示し、隣接するクラブやハートがある場所であれば左から右に処理します。 。それらのうちの1つを除くすべてをランダムに別の場所に移動します(ただし、同じタイプの別のものの隣ではありません)。

たとえば、次のような手で

🂡 🂢 🂣 🂤 🂥 🂦 🂧 🂨 🂱 🂲 🂳 🃁 🃂 🃃 🃑 🃒

基本的なシャッフル後:

🂣 🃑 🂲 🂦 🂳 🃁<🂧 🂡 🂨>🃂<🂤 🂢>🃃 🂱 🂥 🃒
                   1  2       3

隣接するスペードの2つのグループ、1、2、および3を再配置する必要があります。1の場合、選択肢は次のとおりです。

🂣 🃑 🂲 🂦 🂳 🃁 🂧 🂡 🂨 🃂 🂤 🂢 🃃 🂱 🂥 🃒
    ↑        ↑                    ↑        ↑

それら4つからランダムに1つを選択します。その後、2と3についてプロセスを繰り返します。

その中に実装されperlます:

shuf list | perl -e '
  @songs = map {/(.*?)-/; [$1,$_]} <>;
  for ($i = 0; $i < @songs; $i++) {
    if (($author = $songs[$i]->[0]) eq $previous) {
      my @reloc_candidates, $same;
      for($j = 0; $j < @songs; $j++) {
        # build a list of positions where we could move that song to
        if ($songs[$j]->[0] eq $author) {$same = 1} else {
          push @reloc_candidates, $j unless $same;
          $same = 0;
        }
      }
      push @reloc_candidates, $j unless $same;

      if (@reloc_candidates) {
        # now pick one of them at random:
        my $chosen = $reloc_candidates[int(rand(@reloc_candidates))];
        splice @songs, $chosen - ($chosen > $i), 0, splice @songs, $i, 1;
        $i -= $chosen > $i;
      }
    }
    $previous = $author;
  }
  print map {$_->[1]} @songs'

隣接していないアーティストが存在する場合(同じアーティストの曲の半分を超えない場合)、統一されたAFAICTである場合を除き、ソリューションを見つけます。


3つの異なるスクリプト(perlとbash)を試してみると、それらはすべて、隣接する曲を残さずに、ペーストビンに残したプレイリストをシャッフルしますが、あなたはもっと賢い方法でそれを行うようです。さらに、John B.の例で完璧に機能するのはあなただけですこれは間違いなくベストアンサーになります。彼はとても辛抱強く助けてくれたので、彼の答えを受け入れることをデロバートに約束しました。彼の3番目のアプローチもとても良いです。だから私はあなたに最高の答えと彼への報奨金を差し上げます、そして彼が私に腹を立てないことを望みます:)
テレサeジュニア

7

サンプルデータと制約では、実際にはいくつかのソリューションしか許可されていません。たとえば、ジョンBを1曲おきに再生する必要があります。あなたの実際の完全なプレイリストは本質的にジョン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.の歌で始まります。その後は、予測が難しくなります(ジョン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);
}

@TeresaeJuniorは、実際のデータで2つのプログラムを試し、どちらがあなたの好みに合っているかを確認しましたか?(そして、すごい、それを見て、それは非常に「Fhk Hhck」重いです...私はアプローチ3を追加するつもりです)
デロバート

一部のアーティストは、実際に2回連続で再生します(確認するにはを使用できますsed 's/ - .*//' output.m3u | uniq -d)。また、プレイリストの最初または最後で終わっていないアーティストの世話をしてくれるかどうか説明していただけますか?
テレサジュニア

アプローチ1では、実際に2つ(またはそれ以上)を連続して使用できます。アプローチ2はそうではありません。アプローチ3(編集しようとしています)もしません(ほとんどの場合)。アプローチ2では、間違いなく最も一般的なアーティストがプレイリストの先頭に重みを付けます。アプローチ3はしません。
デロバート

1
@TeresaeJunior 3番目のものが働いてうれしいです!アプローチ4がどのようなものだったのか正確には
わかりませ

1
@JosephR。アプローチ#3 では、各アーティストの曲数を重みとして使用ます。暗黙的に、ランダムな曲を選択します。アーティストの曲が多いほど、そのアーティストが選ばれる可能性が高くなります。#1は、曲数で重み付けしない唯一のものです。
デロバート

2

あなたがそれが恐ろしく非効率であることを気にしないなら...

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 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.
-------------

これは、原因が無期限にハングする場合に原因を特定するのに役立ちます。


私はこのアイデアは気に入っていますが、スクリプトは15メートル近く実行されており、適切な組み合わせを見つけることができませんでした。ジョンの曲が多すぎるということではありませんが、プレイリストは7000行以上あり、どのようsortに設計されているようです。
テレサeジュニア

1
パフォーマンスに関してshufは、プレイリストをの80倍高速でシャッフルしsort -Rます。私もそれを知りませんでした!で15分間実行したままにしておきますshuf
テレサジュニア

デバッグするには、のecho "$D"前にif。これにより、どの重複が結果の選択を妨げているかがわかります。それは問題を探す場所を教えてくれるはずです。(編集:回答に可能なデバッグコードを追加しました。)
frostschutz

DEBUGには常に約100行が表示されますが、ランダムなアーティストによるものなので、多くのアーティストが問題を引き起こしているようです。sortまたはで実際には不可能だと思いますshuf
テレサeジュニア

1

Bashを使用する別のアプローチ。プレイリストをランダムな順序で読み取り、重複している場合はリストのもう一方の端に行を挿入しようとし、別の場所に再挿入するために単一のデュープを脇に置きます。三重の重複(最初、最後、および同じ設定)がある場合は失敗し、リストの最後にそれらの不良エントリが追加されます。ほとんどの場合、アップロードした広範なリストを解決できるようです。

#!/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"

それはもっと賢いかもしれません...あなたのジョンの例では、ジョンは通常、first_artistを最初に追加しようとするため、通常last_artistに固執します。したがって、間に2人のアーティストがいる場合、トリプルジョンを回避するために、1人を最初に追加し、もう1人を最後に追加するのは十分ではありません。したがって、基本的に他のすべてのアーティストがジョンである必要があるリストでは、必要以上の失敗が発生します。


このbashスクリプトをありがとう。本当に理解して自由に変更できるのはこれだけです!
テレサeジュニア
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.