I dati e i vincoli di esempio in realtà consentono solo alcune soluzioni: ad esempio, devi suonare John B. ogni altro brano. Presumo che la tua playlist completa effettiva non sia essenzialmente John B, con altre cose casuali per romperlo .
Questo è un altro approccio casuale. A differenza della soluzione di @frostschutz, funziona rapidamente. Tuttavia, non garantisce un risultato che soddisfi i tuoi criteri. Presento anche un secondo approccio, che funziona con i tuoi dati di esempio, ma sospetto che produrrà risultati negativi sui tuoi dati reali. Avendo i tuoi dati reali (offuscati), aggiungo l'approccio 3, che è un caso uniforme, tranne che evita due canzoni dello stesso artista di fila. Nota che crea solo 5 "disegni" nel "mazzo" di brani rimanenti, se dopo che è ancora di fronte a un artista duplicato, emetterà comunque quel brano, in questo modo, è garantito che il programma finirà effettivamente.
Approccio 1
Fondamentalmente, genera una playlist in ogni punto, chiedendo "da quali artisti ho ancora canzoni non riprodotte?" Quindi scegliere un artista a caso, e infine una canzone a caso di quell'artista. (Cioè, ogni artista è ponderato allo stesso modo, non in proporzione al numero di canzoni.)
Fai una prova sulla tua playlist reale e vedi se produce risultati migliori di quelli uniformemente casuali.
Utilizzo:./script-file < input.m3u > output.m3u
assicurati chmod +x
, ovviamente. Nota che non gestisce correttamente la riga della firma che si trova nella parte superiore di alcuni file M3U ... ma il tuo esempio non lo aveva.
#!/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};
}
Approccio 2
Come secondo approccio, invece di scegliere un artista a caso , puoi usare scegli l'artista con il maggior numero di canzoni, che non è anche l'ultimo artista che abbiamo scelto . L'ultimo paragrafo del programma diventa quindi:
# 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};
}
Il resto del programma rimane lo stesso. Nota che questo non è di gran lunga il modo più efficiente per farlo, ma dovrebbe essere abbastanza veloce per playlist di qualsiasi dimensione sana. Con i tuoi dati di esempio, tutte le playlist generate inizieranno con una canzone John B., quindi una canzone Anna A., quindi una canzone John B. Dopodiché, è molto meno prevedibile (poiché a tutti tranne John B. è rimasta una canzone). Si noti che ciò presuppone Perl 5.7 o versioni successive.
Approccio 3
L'utilizzo è lo stesso del precedente 2. Nota la 0..4
parte, ecco da dove proviene il massimo di 5 tentativi. Potresti aumentare il numero di tentativi, ad esempio, 0..9
darebbe 10 in totale. ( 0..4
= 0, 1, 2, 3, 4
, Che si noterà è in realtà 5 articoli).
#!/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);
}