Seus dados e restrições de exemplo permitem apenas algumas soluções - você deve tocar John B. todas as outras músicas, por exemplo. Vou assumir que sua lista de reprodução completa não é essencialmente John B, com outras coisas aleatórias para separá-la .
Essa é outra abordagem aleatória. Ao contrário da solução do @ frostschutz, ele roda rapidamente. Entretanto, não garante um resultado que atenda aos seus critérios. Apresento também uma segunda abordagem, que funciona com seus dados de exemplo - mas suspeito que produzirá maus resultados em seus dados reais. Tendo seus dados reais (ofuscados), adiciono a abordagem 3 - que é aleatória uniforme, exceto que evita duas músicas seguidas pelo mesmo artista. Observe que ele só faz 5 "empates" no "deck" das músicas restantes, se depois disso ainda for confrontado com um artista duplicado, ele produzirá essa música de qualquer maneira - dessa forma, é garantido que o programa realmente será concluído.
Abordagem 1
Basicamente, ele gera uma lista de reprodução a cada momento, perguntando "de quais artistas ainda tenho músicas não reproduzidas?" Em seguida, escolha um artista aleatório e, finalmente, uma música aleatória desse artista. (Ou seja, cada artista tem o mesmo peso, não na proporção do número de músicas.)
Experimente a sua lista de reprodução real e veja se ela produz melhores resultados do que uniformemente aleatória.
Uso:./script-file < input.m3u > output.m3u
Certifique-se chmod +x
disso, é claro. Observe que ele não lida com a linha de assinatura que está no topo de alguns arquivos M3U corretamente ... mas seu exemplo não tinha 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 segunda abordagem, em vez de escolher um artista aleatório , você pode usar o artista com mais músicas, que também não é o último artista que escolhemos . O parágrafo final do programa passa a ser:
# 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 restante 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. Com seus dados de exemplo, todas as listas de reprodução geradas começarão com uma música de John B., depois uma música de Anna A. e, em seguida, uma música de John B. Depois disso, é muito menos previsível (já que todos, exceto John B., têm uma música). Observe que isso pressupõe Perl 5.7 ou posterior.
Abordagem 3
O uso é o mesmo que o anterior 2. Observe a 0..4
parte, é daí que 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
, que você notará na verdade são 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);
}