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 +x
es 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..4
Version. Sie könnten die Anzahl der Versuche erhöhen, z. B. 0..9
insgesamt 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);
}