Перемішуйте файл випадковим чином з деякими додатковими обмеженнями


12

У мене величезний список відтворення музики, і хоча деякі виконавці мають багато альбомів, інші мають лише одну пісню. Я хотів сортувати список відтворення, щоб той самий виконавець не грав два рази поспіль, або його пісні не закінчуватимуться переважно на початку чи в кінці списку відтворення.

Приклад відтворення:

$ cat /tmp/playlist.m3u
Anna A. - Song 1
Anna A. - Song 2
I--Rock - Song 1
John B. - Song 1
John B. - Song 2
John B. - Song 3
John B. - Song 4
John B. - Song 5
Kyle C. - Song 1
U--Rock - Song 1

Вихід з sort -Rабо shuf:

$ sort -R /tmp/playlist.m3u
Anna A. - Song 1 #
U--Rock - Song 1
Anna A. - Song 2 # Anna's songs are all in the beginning.
John B. - Song 2
I--Rock - Song 1
John B. - Song 1
Kyle C. - Song 1
John B. - Song 4 #
John B. - Song 3 #
John B. - Song 5 # Three of John's songs in a row.

Що я очікую:

$ some_command /tmp/playlist.m3u
John B. - Song 1
Anna A. - Song 1
John B. - Song 2
I--Rock - Song 1
John B. - Song 3
Kyle C. - Song 1
Anna A. - Song 2
John B. - Song 4
U--Rock - Song 1
John B. - Song 5

13
Технічно те, що ви просите, - це менше випадковості та більшої структури. Це не неможливо, але для цього знадобиться сценарій (bash / awk / perl / python / тощо).
goldilocks

Або структурована випадковість :)
Teresa e Junior

Саме так! Це було б гарною вправою в перлі чи пітоні. Я думаю, це буде головний біль з баштом, хоча це може спрацювати з awk - я не знаю, про awk досить добре, щоб сказати.
goldilocks

Оскільки, здається, не існує жодних інструментів для цього, сценарій, здається, є шляхом. Справа не в тому, що я ледачий, але я поза ідеями.
Teresa e Junior

1
Ви можете зробити це за допомогою простого алгоритму: складіть список відтворення, вибравши випадкову пісню кожного виконавця по черзі (де ходу можна також рандомізувати, але без повторення виконавця). Коли всі пісні одного виконавця вичерпані, починайте переплутати пісні рештою виконавців (знову ж таки, чергуючи їх по черзі) із наявним списком відтворення таким чином, щоб мінімізувати суміжність пісень того самого виконавця. Продовжуйте повторювати, поки не закінчите. Мені шкода, що у мене немає часу, щоб перетворити це на фактичний сценарій; Я просто подумав, що це може бути корисно, щоб допомогти вам зробити свій власний.
Джозеф Р.

Відповіді:


5

Якби мені довелося застосувати цю перетасовку до колоди гральних карт, я думаю, я спершу перемішуватиму колоду, а потім показувати карти підряд перед очима та обробляти зліва направо, де б не було сусідніх клубів чи серця. ... перемістити всіх, крім одного, навмання кудись інше (хоча не поруч із іншим того ж типу).

Наприклад, рукою, як

🂡 🂢 🂣 🂤 🂥 🂦 🂧 🂨 🂱 🂲 🂳 🃁 🃂 🃃 🃑 🃒

Після основного перемішування:

🂣 🃑 🂲 🂦 🂳 🃁<🂧 🂡 🂨>🃂<🂤 🂢>🃃 🂱 🂥 🃒
                   1  2       3

дві групи суміжних лопат, нам потрібно перенести 1, 2 і 3. Для 1 варіантами є:

🂣 🃑 🂲 🂦 🂳 🃁 🂧 🂡 🂨 🃂 🂤 🂢 🃃 🂱 🂥 🃒
    ↑        ↑                    ↑        ↑

Вибираємо один навмання з тих 4. Потім повторюємо процес для 2 і 3.

Реалізовано в perlцьому:

shuf list | perl -e '
  @songs = map {/(.*?)-/; [$1,$_]} <>;
  for ($i = 0; $i < @songs; $i++) {
    if (($author = $songs[$i]->[0]) eq $previous) {
      my @reloc_candidates, $same;
      for($j = 0; $j < @songs; $j++) {
        # build a list of positions where we could move that song to
        if ($songs[$j]->[0] eq $author) {$same = 1} else {
          push @reloc_candidates, $j unless $same;
          $same = 0;
        }
      }
      push @reloc_candidates, $j unless $same;

      if (@reloc_candidates) {
        # now pick one of them at random:
        my $chosen = $reloc_candidates[int(rand(@reloc_candidates))];
        splice @songs, $chosen - ($chosen > $i), 0, splice @songs, $i, 1;
        $i -= $chosen > $i;
      }
    }
    $previous = $author;
  }
  print map {$_->[1]} @songs'

Вона знайде рішення з сусідніми виконавцями, якщо вони існують (якщо більше половини пісень від того самого виконавця), і вони повинні бути рівномірними AFAICT.


Спробувавши три різних сценаріїв (perl та bash), усі вони змішують список відтворення, який я залишив на пастбіні, не залишаючи суміжних пісень, але ваш, здається, робить це більш розумним чином. Окрім того, лише твій відмінно працює на прикладі Джона Б. , що, безперечно, робить це найкращою відповіддю. Я пообіцяв Дероберту прийняти його відповідь, оскільки він був такий терплячий і корисний для мене, і його третій підхід теж дуже хороший. Тож я дам вам найкращу відповідь та щедрість до нього, і я сподіваюся, що він не розсердиться на мене :)
Teresa e Junior

7

Ваші приклади даних та обмеження насправді дозволяють лише декілька рішень - ви повинні грати на Джон Б. будь-яку іншу пісню, наприклад. Я припускаю, що ваш фактичний повний список відтворення, по суті, не Джон Б, з випадковими іншими речами, щоб розбити його .

Це ще один випадковий підхід. На відміну від рішення @ 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);
}

@TeresaeJunior Ви спробували дві програми на фактичних даних, і побачите, чи вам до душі? (І ось, дивлячись на це, це дуже "Fhk Hhck" важкий ... Я додам підхід 3)
derobert

Деякі артисти насправді грають двічі поспіль (ви можете це перевірити sed 's/ - .*//' output.m3u | uniq -d). Чи можете ви пояснити, чи потрібно, щоб деякі виконавці не закінчилися на початку чи в кінці списку відтворення?
Teresa e Junior

Підхід 1 дійсно дозволяє два (або більше) поспіль. Підхід 2 не робить. Підхід 3 (збирається його відредагувати) також не робить (ну, переважно). Підхід 2 безумовно обтяжує початок відтворення найпоширеніших артистів. Підходу 3 не буде.
derobert

1
@TeresaeJunior Я радий, що третій працював! Я не впевнений, яким саме був би підхід 4, але було б страшно ...
derobert

1
@JosephR. Підхід # 3 робить використовувати кількість пісень кожного виконавця в якості ваги неявно, вибираючи випадкову пісню. Чим більше пісень у виконавця, тим більше шансів вибрати художника. №1 - це єдиний, який не важить за кількістю пісень.
derobert

2

Якщо ви не заперечуєте, що це жахливо неефективно ...

while [ 1 ]
do
    R="`shuf playlist`"
    D="`echo "$R" | sed -e 's/ - .*//' | uniq -c -d`"
    if [ "$D" == "" ]
    then
        break
    #else # DEBUG ONLY:
    #    echo --- FAIL: ---
    #    echo "$D"
    #    echo -------------
    fi
done

echo "$R"

Він просто продовжує котитися і котити, поки не з’явиться результат, у якого немає двох і більше Джонсів поспіль. Якщо у вашому списку відтворення так багато Джона, що такого поєднання не існує або вкрай малоймовірно, що він буде прокатуватися, добре, він зависне.

Приклад результату з вашим вкладом:

John B. - Song 4
Kyle C. - Song 1
Anna A. - Song 2
John B. - Song 3
Anna A. - Song 1
John B. - Song 1
U--Rock - Song 1
John B. - Song 2
I--Rock - Song 1
John B. - Song 5

Якщо ви відменюєте рядки налагодження, він скаже вам, чому це не вдалося:

--- FAIL: ---
      3 John B.
-------------
--- FAIL: ---
      2 John B.
      2 John B.
-------------

Це повинно допомогти визначити причину, якщо вона зависає нескінченно.


Ідея мені подобається, але сценарій працює майже 15 м, і не вдалося знайти відповідну комбінацію. Справа не в тому, що у мене занадто багато пісень Джона, але список відтворення - це понад 7000 рядків, і, здається, як sortвоно задумане.
Teresa e Junior

1
Щодо продуктивності, shufперетасовує список відтворення в 80 разів швидше, ніж sort -R. Я теж цього не знав! Я залишу його на 15 хвилин shuf, шанси вищі!
Teresa e Junior

Налагоджувати, echo "$D"перш ніж if. Це повинно сказати вам, які дублікати не дозволили вибрати результат. Це повинно сказати вам, де шукати проблему. (Редагувати: Додано можливий код налагодження у відповідь.)
frostschutz

DEBUG завжди показує близько 100 рядків, але від випадкових виконавців, тому, здається, багато виконавців викликають проблему. Я думаю, що це не дуже можливо з sortабо shuf.
Teresa e Junior

1

Ще один підхід із використанням Bash. Він читає список відтворення у випадковому порядку, намагається вставити рядок на іншому кінці списку, якщо це дублікат, і відкладає один дуп в сторону, щоб знову вставити його в інше місце. Він не вдається, якщо є потрійні дублікати (перший, останній та відмінити однакові), і вони додадуть ці погані записи до самого кінця списку. Здається, вдалося вирішити обширний список, який ви завантажували більшу частину часу.

#!/bin/bash

first_artist=''
last_artist=''
bad_artist=''
bad_line=''
result=''
bad_result=''

while read line
do
    artist=${line/ - */}
    line="$line"$'\n'

    if [ "$artist" != "$first_artist" ]
    then
        result="$line""$result"
        first_artist="$artist"

        # special case: first = last
        if [ "$last_artist" == '' ]
        then
            last_artist="$artist"
        fi

        # try reinserting bad
        if [ "$bad_artist" != '' -a "$bad_artist" != "$first_artist" ]
        then
            first_artist="$bad_artist"
            result="$bad_line""$result"
            bad_artist=''
            bad_line=''
        fi
    elif [ "$artist" != "$last_artist" ]
    then
        result="$result""$line"
        last_artist="$artist"

        # try reinserting bad
        if [ "$bad_artist" != '' -a "$bad_artist" != "$last_artist" ]
        then
            last_artist="$bad_artist"
            result="$result""$bad_line"
            bad_artist=''
            bad_line=''
        fi
    else
        if [ "$bad_artist" == '' ]
        then
            bad_artist="$artist"
            bad_line="$line"
        else
            # first, last and bad are the same artist :(
            bad_result="$bad_result""$line"
        fi
    fi
done < <(shuf playlist)

# leftovers?
if [ "$bad_artist" != '' ]
then
    bad_result="$bad_result""$bad_line"
fi

echo -n "$result"
echo -n "$bad_result"

Це може бути розумнішим ... у вашому прикладі Джона, Джон, як правило, буде дотримуватися останнього артиста, тому що завжди намагається спочатку додати команду first_artist. Отже, якщо між ними потрапляють двоє інших художників, це не досить розумно, щоб додати одного до початку, а другого до кінця, щоб уникнути потрійного Джона. Отож, зі списками, які вимагають, щоб кожен інший художник був Джоном, ви отримуєте більше відмов, ніж слід.


Дякую за цей сценарій баш. Це єдиний, кого я можу реально зрозуміти та змінити за бажанням!
Teresa e Junior
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.