Zufälliges Mischen von Dateien mit einigen zusätzlichen Einschränkungen

12

Ich habe eine riesige Musikwiedergabeliste und während einige Künstler viele Alben haben, haben andere nur einen Song. Ich wollte die Wiedergabeliste sortieren, damit derselbe Künstler nicht zweimal hintereinander abgespielt wird oder seine Songs meist am Anfang oder Ende der Wiedergabeliste landen.

Beispiel-Playlist:

$ 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

Ausgabe von sort -Roder 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.

Was ich erwarte:

$ 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
Teresa und Junior
quelle
13
Technisch gesehen verlangen Sie weniger Zufälligkeit und mehr Struktur. Dies ist nicht unmöglich, erfordert jedoch ein Skript (bash / awk / perl / python / etc).
Goldlöckchen
Oder eine strukturierte Zufälligkeit :)
Teresa e Junior
Genau! Dies wäre eine gute Übung in Perl oder Python. Ich denke, es würde Kopfschmerzen mit Bash verursachen, obwohl es mit awk gut funktionieren könnte - ich kenne awk nicht gut genug, um es zu sagen.
Goldlöckchen
Da es dafür keine Tools zu geben scheint, scheint ein Skript der richtige Weg zu sein. Es ist nicht so, dass ich faul bin, aber mir fehlen die Ideen.
Teresa e Junior
1
Möglicherweise können Sie dies mit einem einfachen Algorithmus tun: Erstellen Sie die Wiedergabeliste, indem Sie einen zufälligen Song für jeden Künstler auswählen (wobei der Zug auch zufällig gewählt werden kann, jedoch ohne Wiederholung des Künstlers). Wenn alle Songs eines Interpreten erschöpft sind, beginnen Sie damit, die Songs der verbleibenden Interpreten (ebenfalls abwechselnd) mit der vorhandenen Wiedergabeliste zu verschachteln, um die Nachbarschaft von Songs desselben Interpreten zu minimieren. Wiederholen Sie dies, bis Sie fertig sind. Es tut mir leid, dass ich keine Zeit habe, dies in ein tatsächliches Skript umzuwandeln. Ich dachte nur, es könnte nützlich sein, dir zu helfen, deine eigenen zu rollen.
Joseph R.

Antworten:

5

Wenn ich dieses Mischen auf ein Kartenspiel anwenden müsste, würde ich wahrscheinlich zuerst das Kartenspiel mischen und dann die Karten in einer Reihe vor meinen Augen anzeigen und von links nach rechts verarbeiten, wo immer sich benachbarte Clubs oder Herzen befinden. Englisch: www.mjfriendship.de/en/index.php?op...41&Itemid=32 Verschieben Sie alle bis auf einen zufällig an einen anderen Ort (wenn auch nicht neben einen anderen Typ).

Zum Beispiel mit einer Hand wie

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

Nach dem Grundmischen:

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

Für zwei Gruppen benachbarter Pik müssen wir 1, 2 und 3 verschieben. Für 1 stehen folgende Optionen zur Auswahl:

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

Wir wählen eine zufällig aus diesen 4 aus. Dann wiederholen wir den Vorgang für 2 und 3.

Umgesetzt perlwäre:

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'

Es wird eine Lösung mit nicht benachbarten Künstlern finden, wenn es existiert (es sei denn, mehr als die Hälfte der Songs sind vom selben Künstler) und sollte einheitlich AFAICT sein.

Stéphane Chazelas
quelle
Nachdem Sie die drei verschiedenen Skripte (Perl und Bash) ausprobiert haben, mischen sie alle die Wiedergabeliste, die ich im Pastebin hinterlassen habe, ohne benachbarte Songs zu hinterlassen, aber Ihre scheinen es schlauer zu machen. Außerdem funktioniert nur bei Ihnen das Beispiel von John B. einwandfrei , was es zweifellos zu einer optimalen Antwort macht. Ich habe derobert versprochen, seine Antwort anzunehmen, da er so geduldig und hilfsbereit für mich war und sein dritter Ansatz auch sehr gut ist. Also gebe ich dir die beste Antwort und das Kopfgeld, und ich hoffe, er wird nicht sauer auf mich :)
Teresa e Junior
7

Ihre Beispieldaten und Einschränkungen lassen tatsächlich nur wenige Lösungen zu - Sie müssen beispielsweise John B. für jeden zweiten Song spielen. Ich gehe davon aus, dass es sich bei Ihrer tatsächlichen vollständigen Wiedergabeliste nicht im Wesentlichen um John B handelt .

Dies ist ein weiterer zufälliger Ansatz. Im Gegensatz zur @ frostschutz-Lösung läuft sie schnell. Es wird jedoch kein Ergebnis garantiert, das Ihren Kriterien entspricht. Ich präsentiere auch einen zweiten Ansatz, der mit Ihren Beispieldaten funktioniert - aber ich vermute, dass Ihre realen Daten zu schlechten Ergebnissen führen werden. Wenn Ihre realen Daten (verschleiert) vorliegen, füge ich Ansatz 3 hinzu. Dies ist ein einheitlicher Zufall, mit der Ausnahme, dass zwei Songs desselben Künstlers hintereinander vermieden werden. Beachten Sie, dass es nur 5 "Draws" in das "Deck" der verbleibenden Songs macht. Wenn es danach immer noch mit einem doppelten Interpreten konfrontiert wird, wird das Lied trotzdem ausgegeben - auf diese Weise wird garantiert, dass das Programm tatsächlich beendet wird.

Ansatz 1

Grundsätzlich wird an jedem Punkt eine Wiedergabeliste erstellt, in der gefragt wird, von welchen Künstlern ich noch nicht abgespielte Songs habe. Wählen Sie dann einen zufälligen Künstler und schließlich ein zufälliges Lied von diesem Künstler. (Das heißt, jeder Künstler wird gleich gewichtet, nicht proportional zur Anzahl der Songs.)

Probieren Sie Ihre eigentliche Wiedergabeliste aus und prüfen Sie, ob sie bessere Ergebnisse liefert als die gleichmäßig zufälligen.

Verwendung: Stellen./script-file < input.m3u > output.m3u Sie sicher, dass chmod +xes selbstverständlich ist. Beachten Sie, dass die Signaturzeile, die sich oben in einigen M3U-Dateien befindet, nicht ordnungsgemäß verarbeitet wird. In Ihrem Beispiel war dies jedoch nicht der Fall.

#!/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};
}

Ansatz 2

Als zweiter Ansatz, statt einen zufälligen Künstler auswählen , können Sie den Künstler mit den meisten Songs auswählen, die auch wir wählten nicht der letzte Künstler ist . Der letzte Absatz des Programms wird dann:

# 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};
}

Der Rest des Programms bleibt gleich. Beachten Sie, dass dies bei weitem nicht die effizienteste Methode ist, aber für Wiedergabelisten jeder vernünftigen Größe schnell genug sein sollte. Mit Ihren Beispieldaten beginnen alle generierten Wiedergabelisten mit einem John B.-Song, einem Anna A.-Song und einem John B.-Song. Danach ist es viel weniger vorhersehbar (da außer John B. noch ein Song übrig ist). Beachten Sie, dass dies Perl 5.7 oder höher voraussetzt.

Ansatz 3

Die Verwendung ist die gleiche wie bei der vorherigen 0..4Version. Sie könnten die Anzahl der Versuche erhöhen, z. B. 0..9insgesamt 10 geben. ( 0..4= 0, 1, 2, 3, 4, Sie werden feststellen, dass es sich tatsächlich um 5 Artikel handelt).

#!/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);
}
derobert
quelle
@TeresaeJunior haben Sie die beiden Programme anhand der tatsächlichen Daten ausprobiert und festgestellt, ob Ihnen eines gefällt? (Und, wow, es ist sehr "Fhk Hhck" schwer ... Ich werde einen Ansatz 3 hinzufügen)
Derobert
Einige Künstler spielen tatsächlich zweimal hintereinander (Sie können es mit überprüfen sed 's/ - .*//' output.m3u | uniq -d). Und könntest du bitte erklären, ob es sich um Künstler handelt, die nicht am Anfang oder Ende der Wiedergabeliste landen?
Teresa e Junior
Ansatz 1 erlaubt tatsächlich zwei (oder mehr) hintereinander. Ansatz 2 tut dies nicht. Ansatz 3 (der gerade bearbeitet wird) funktioniert auch nicht (meistens). Ansatz 2 gewichtet definitiv den Anfang der Wiedergabeliste der gängigsten Künstler. Ansatz 3 wird nicht.
Derobert
1
@TeresaeJunior Ich bin froh, dass der dritte funktioniert hat! Ich bin nicht sicher, was Ansatz 4 gewesen wäre, aber es wäre beängstigend ...
Derobert
1
@ JosephR. Nähern Sie sich # 3 ist die Anzahl der Songs als gewicht implizit von jedem Künstler verwenden, durch einen zufälligen Song Kommissionierung. Je mehr Songs ein Künstler hat, desto wahrscheinlicher ist es, dass er ausgewählt wird. Nummer 1 ist die einzige, die nicht nach der Anzahl der Songs gewichtet wird.
Derobert
2

Wenn es Ihnen nichts ausmacht, schrecklich ineffizient zu sein ...

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"

Es rollt einfach weiter und weiter, bis es zu einem Ergebnis kommt, das nicht zwei oder mehr Johns hintereinander hat. Wenn Ihre Wiedergabeliste so viele Johns enthält, dass eine solche Kombination nicht existiert oder äußerst unwahrscheinlich ist, hängt sie.

Beispielergebnis mit Ihrer Eingabe:

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

Wenn Sie die Debug-Zeilen auskommentieren, erfahren Sie, warum dies fehlgeschlagen ist:

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

Dies sollte helfen, die Ursache für den Fall zu bestimmen, dass es auf unbestimmte Zeit hängt.

Frostschutz
quelle
Ich mag die Idee, aber das Skript läuft seit fast 15 Minuten und konnte keine passende Kombination finden. Es ist nicht so, dass ich zu viele Songs von John habe, aber die Wiedergabeliste besteht aus mehr als 7000 Zeilen, und es scheint so zu sein, wie sie sortentworfen wurde.
Teresa e Junior
1
In Bezug auf die Leistung wird shufdie Wiedergabeliste 80-mal schneller als gemischt sort -R. Das wusste ich auch nicht! Ich lasse es 15 Minuten laufen shuf, die Chancen stehen höher!
Teresa e Junior
Zum Debuggen echo "$D"vor dem if. Das sollte Ihnen sagen, welche Duplikate die Auswahl des Ergebnisses verhindert haben. Das sollte Ihnen sagen, wo Sie nach dem Problem suchen müssen. (Edit: Möglicher Debug-Code zur Antwort
hinzugefügt
DEBUG zeigt immer ungefähr 100 Zeilen an, jedoch von zufälligen Künstlern, so dass es den Anschein hat, als würden viele Künstler das Problem verursachen. Ich denke es ist mit sortoder nicht wirklich möglich shuf.
Teresa e Junior
1

Ein weiterer Ansatz mit Bash. Es liest die Wiedergabeliste in zufälliger Reihenfolge, versucht, die Zeile am anderen Ende der Liste einzufügen, wenn es sich um ein Duplikat handelt, und legt eine einzelne Kopie beiseite, um sie an einer anderen Stelle einzufügen. Es schlägt fehl, wenn es drei identische Duplikate gibt (erstes, letztes und beiseite gelegtes Duplikat), und diese fehlerhaften Einträge werden an das Ende der Liste angehängt. Es scheint in der Lage zu sein, die umfangreiche Liste zu lösen, die Sie die meiste Zeit hochgeladen haben.

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

Es könnte klüger sein ... in Ihrem John-Beispiel bleibt John normalerweise der letzte Künstler, weil er immer versucht, den ersten Künstler zuerst anzuhängen. Wenn es also zwei andere Künstler dazwischen gibt, ist es nicht klug genug, einen an den Anfang und den anderen an das Ende anzuhängen, um dem Dreifach-John auszuweichen. Bei Listen, bei denen grundsätzlich jeder andere Künstler John sein muss, kommt es also häufiger zu Fehlern, als Sie sollten.

Frostschutz
quelle
Vielen Dank für dieses Bash-Skript. Es ist das einzige, das ich wirklich verstehen und nach Belieben ändern kann!
Teresa e Junior