Ваші приклади даних та обмеження насправді дозволяють лише декілька рішень - ви повинні грати на Джон Б. будь-яку іншу пісню, наприклад. Я припускаю, що ваш фактичний повний список відтворення, по суті, не Джон Б, з випадковими іншими речами, щоб розбити його .
Це ще один випадковий підхід. На відміну від рішення @ frostschutz, він працює швидко. Однак це не гарантує результат, який відповідає вашим критеріям. Я також представляю другий підхід, який працює на ваших прикладних даних, але я підозрюю, що він дасть погані результати на ваші реальні дані. Маючи ваші реальні дані (затуманені), я додаю підхід 3 - який є рівномірним випадковим випадком, за винятком того, що він дозволяє уникнути двох пісень одного виконавця поспіль. Зауважте, що він робить лише 5 "малюнків" у "колоду" решти пісень, якщо після цього він все-таки зіткнеться з дублікатом виконавця, він все одно виведе цю пісню - таким чином, це гарантує, що програма насправді закінчиться.
Підхід 1
В основному, він створює список відтворення кожного разу, запитуючи "від яких виконавців у мене ще є неіграні пісні?" Потім вибирайте випадкового виконавця і, нарешті, випадкову пісню від цього виконавця. (Тобто кожен артист зважується рівномірно, не пропорційно кількості пісень.)
Спробуйте спробувати ваш фактичний список відтворення та побачите, чи він дає кращі результати, ніж рівномірно випадковий.
Використання:./script-file < input.m3u > output.m3u
переконайтеся chmod +x
, звичайно. Зверніть увагу, він не обробляє рядок підпису, який знаходиться у верхній частині деяких файлів M3U належним чином ... але у вашому прикладі цього не було.
#!/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};
}
Підхід 2
Як другий підхід, замість того, щоб вибрати випадкового виконавця , ви можете обрати виконавця з найбільшою кількістю пісень, який також не є останнім виконавцем, якого ми вибрали . Заключним пунктом програми стає:
# 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};
}
Решта програми залишається такою ж. Зауважте, що це далеко не найефективніший спосіб зробити це, але він повинен бути досить швидким для списків відтворення будь-якого розумного розміру. З ваших даних прикладу, всі створені списки відтворення починатимуться з пісні Джона Б., потім пісні Анни А., потім пісні Джона Б.. Після цього це набагато менш передбачувано (як у всіх, окрім Джона Б., залишилася одна пісня). Зауважте, що це передбачає Perl 5.7 або пізнішої версії.
Підхід 3
Використання те саме, що і попереднє. Зауважте, 0..4
частина, звідки походить максимум 5 спроб. Ви можете збільшити кількість спроб, наприклад, 0..9
дасть 10. ( 0..4
= 0, 1, 2, 3, 4
, що ви помітите, це насправді 5 предметів).
#!/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);
}