Vos données et contraintes d'exemple ne permettent en fait que quelques solutions - vous devez jouer John B. toutes les autres chansons, par exemple. Je vais supposer que votre liste de lecture complète n'est pas essentiellement John B, avec d'autres choses aléatoires pour le casser .
Ceci est une autre approche aléatoire. Contrairement à la solution de @ frostschutz, elle s'exécute rapidement. Cependant, cela ne garantit pas un résultat qui correspond à vos critères. Je présente également une deuxième approche, qui fonctionne sur vos données d'exemple, mais je pense que cela produira de mauvais résultats sur vos données réelles. Ayant vos vraies données (obscurcies), j'ajoute l'approche 3 - qui est un aléatoire uniforme, sauf qu'elle évite deux chansons du même artiste d'affilée. Notez qu'il ne fait que 5 "tirages" dans le "deck" des chansons restantes, si après cela, il est toujours confronté à un artiste en double, il sortira cette chanson de toute façon - de cette façon, sa garantie que le programme se terminera réellement.
Approche 1
Fondamentalement, il génère une liste de lecture à chaque point, demandant "de quels artistes ai-je encore des chansons non jouées?" Puis choisir un artiste au hasard, et enfin une chanson au hasard de cet artiste. (C'est-à-dire que chaque artiste est pondéré de manière égale, non proportionnellement au nombre de chansons.)
Essayez-le sur votre liste de lecture réelle et voyez si elle produit de meilleurs résultats que uniformément aléatoire.
Utilisation:./script-file < input.m3u > output.m3u
assurez-vous chmod +x
bien sûr. Notez qu'il ne gère pas correctement la ligne de signature qui se trouve en haut de certains fichiers M3U ... mais votre exemple ne l'a pas.
#!/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};
}
Approche 2
Dans une deuxième approche, au lieu de choisir un artiste au hasard , vous pouvez utiliser choisir l'artiste avec le plus de chansons, qui n'est pas non plus le dernier artiste que nous avons choisi . Le dernier paragraphe du programme devient alors:
# 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};
}
Le reste du programme reste le même. Notez que ce n'est de loin pas le moyen le plus efficace de le faire, mais il devrait être assez rapide pour les listes de lecture de toute taille saine. Avec vos données d'exemple, toutes les listes de lecture générées commenceront par une chanson de John B., puis une chanson d'Anna A., puis une chanson de John B. Après cela, c'est beaucoup moins prévisible (comme tout le monde sauf John B. a une chanson). Notez que cela suppose Perl 5.7 ou une version ultérieure.
Approche 3
L'utilisation est la même que la précédente. Notez la 0..4
partie, c'est de là que viennent les 5 essais max. Vous pourriez augmenter le nombre d'essais, par exemple, 0..9
donner 10 au total. ( 0..4
= 0, 1, 2, 3, 4
, ce que vous remarquerez est en fait 5 articles).
#!/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);
}