Seus dados e restrições de exemplo, na verdade, apenas permitem algumas soluções - você deve tocar John B. todas as outras músicas, por exemplo. Suponho que sua playlist completa não seja essencialmente John B, com outras coisas aleatórias para dividi-la .
Esta é outra abordagem aleatória. Ao contrário da solução @ frostschutz, ela é executada rapidamente. Não garante um resultado que corresponda aos seus critérios. Eu também apresento uma segunda abordagem, que funciona em seus dados de exemplo - mas suspeito que produzirá resultados ruins em seus dados reais. Tendo seus dados reais (ofuscados), eu adiciono a abordagem 3 - que é um aleatório uniforme, exceto que evita duas músicas do mesmo artista em uma linha. Note que ele só faz 5 "draws" no "deck" das músicas restantes, se depois disso ele ainda for confrontado com um artista duplicado, ele produzirá a música de qualquer maneira - dessa forma, é garantido que o programa irá realmente terminar.
Abordagem 1
Basicamente, ele gera uma playlist em cada ponto, perguntando "de quais artistas eu ainda não tirei músicas?" Em seguida, escolher um artista aleatório e, finalmente, uma música aleatória desse artista. (Ou seja, cada artista é ponderado igualmente, não em proporção ao número de músicas).
Teste sua playlist real e veja se ela produz melhores resultados do que uniformemente aleatória.
Uso: ./script-file < input.m3u > output.m3u
Certifique-se de chmod +x
, é claro. Note que ele não manipula a linha de assinatura que está no topo de alguns arquivos M3U corretamente ... mas o seu exemplo não tem isso.
#!/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};
}
Abordagem 2
Como uma segunda abordagem, em vez de escolher um artista aleatório , você pode usar escolher o artista com mais músicas, que também não é o último artista que escolhemos . O parágrafo final do programa torna-se então:
# 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};
}
O resto do programa permanece o mesmo. Observe que essa não é a maneira mais eficiente de fazer isso, mas deve ser rápida o suficiente para listas de reprodução de qualquer tamanho sã. Com seus dados de exemplo, todas as playlists geradas começarão com uma música de John B., depois uma de Anna A. e uma de John B. Depois disso, é muito menos previsível (já que todos, exceto John B., ainda têm uma música). Observe que isso pressupõe o Perl 5.7 ou posterior.
Abordagem 3
O uso é o mesmo que o anterior 2. Observe a 0..4
parte, é de onde vem o máximo de 5 tentativas. Você poderia aumentar o número de tentativas, por exemplo, 0..9
daria 10 no total. ( 0..4
= 0, 1, 2, 3, 4
, o que você notará é na verdade 5 itens).
#!/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);
}