Riproduzione casuale dei file in modo casuale con alcuni vincoli aggiuntivi


12

Ho un'enorme playlist musicale e, mentre alcuni artisti hanno molti album, altri hanno solo una canzone. Volevo ordinare la playlist in modo che lo stesso artista non suonasse due volte di seguito, o le sue canzoni non finissero principalmente all'inizio o alla fine della playlist.

Esempio di playlist:

$ 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

Uscita da sort -Ro 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.

Cosa mi aspetto:

$ 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
Tecnicamente, ciò che stai chiedendo è meno casualità e più struttura. Non è impossibile, ma richiederà uno script (bash / awk / perl / python / etc).
Riccioli d'oro

O una casualità strutturata :)
Teresa e Junior

Esattamente! Questo sarebbe un buon esercizio in perl o python. Penso che sarebbe un mal di testa con bash, anche se potrebbe funzionare bene con awk - non lo so abbastanza bene da dire.
Riccioli d'oro

Dal momento che non sembrano esserci strumenti per farlo, uno script sembra essere la strada da percorrere. Non è che sono pigro, ma sono senza idee.
Teresa e Junior,

1
Potresti riuscire a farlo con un semplice algoritmo: crea la playlist selezionando una canzone a caso da ciascun artista a turno (dove anche il turno può essere randomizzato ma senza ripetizione dell'artista). Quando tutte le canzoni di un artista sono state esaurite, inizia a interfogliare le canzoni degli artisti rimanenti (di nuovo, alternandoli a loro volta) con la playlist esistente in modo da ridurre al minimo l'adiacenza delle canzoni dello stesso artista. Continua a ripetere fino a quando non hai finito. Mi dispiace di non avere tempo di inserirli in una sceneggiatura vera e propria; Ho solo pensato che potesse essere utile per aiutarti a realizzare il tuo.
Joseph R.

Risposte:


5

Se dovessi applicare quel rimescolamento a un mazzo di carte da gioco, penso che prima mescolerei il mazzo, quindi visualizzerei le carte in fila davanti ai miei occhi e procederò da sinistra a destra, ovunque ci siano fiori o cuori adiacenti. spostare tutti tranne uno a caso da qualche altra parte (anche se non accanto a un altro dello stesso tipo).

Ad esempio, con una mano come

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

Dopo il mescolamento di base:

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

due gruppi di picche adiacenti, dobbiamo spostare 1, 2 e 3. Per 1, le scelte sono:

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

Ne scegliamo uno a caso tra quelli 4. Quindi ripetiamo il processo per 2 e 3.

Implementato in perlquesto sarebbe:

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'

Troverà una soluzione con artisti non adiacenti se esiste (a meno che più della metà delle canzoni siano dello stesso artista) e dovrebbe essere AFAICT uniforme.


Dopo aver provato i tre diversi script (perl e bash), tutti mescolano la playlist che ho lasciato su pastebin senza lasciare brani adiacenti, ma il tuo sembra farlo in un modo più intelligente. Inoltre, solo il tuo funziona perfettamente sull'esempio di John B. , che senza dubbio lo rende una risposta migliore. Ho promesso a derobert di accettare la sua risposta, dato che era così paziente e disponibile con me, e anche il suo terzo approccio è molto buono. Quindi ti darò la migliore risposta e la generosità per lui, e spero che non si arrabbi con me :)
Teresa e Junior,

7

I dati e i vincoli di esempio in realtà consentono solo alcune soluzioni: ad esempio, devi suonare John B. ogni altro brano. Presumo che la tua playlist completa effettiva non sia essenzialmente John B, con altre cose casuali per romperlo .

Questo è un altro approccio casuale. A differenza della soluzione di @frostschutz, funziona rapidamente. Tuttavia, non garantisce un risultato che soddisfi i tuoi criteri. Presento anche un secondo approccio, che funziona con i tuoi dati di esempio, ma sospetto che produrrà risultati negativi sui tuoi dati reali. Avendo i tuoi dati reali (offuscati), aggiungo l'approccio 3, che è un caso uniforme, tranne che evita due canzoni dello stesso artista di fila. Nota che crea solo 5 "disegni" nel "mazzo" di brani rimanenti, se dopo che è ancora di fronte a un artista duplicato, emetterà comunque quel brano, in questo modo, è garantito che il programma finirà effettivamente.

Approccio 1

Fondamentalmente, genera una playlist in ogni punto, chiedendo "da quali artisti ho ancora canzoni non riprodotte?" Quindi scegliere un artista a caso, e infine una canzone a caso di quell'artista. (Cioè, ogni artista è ponderato allo stesso modo, non in proporzione al numero di canzoni.)

Fai una prova sulla tua playlist reale e vedi se produce risultati migliori di quelli uniformemente casuali.

Utilizzo:./script-file < input.m3u > output.m3u assicurati chmod +x, ovviamente. Nota che non gestisce correttamente la riga della firma che si trova nella parte superiore di alcuni file M3U ... ma il tuo esempio non lo aveva.

#!/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};
}

Approccio 2

Come secondo approccio, invece di scegliere un artista a caso , puoi usare scegli l'artista con il maggior numero di canzoni, che non è anche l'ultimo artista che abbiamo scelto . L'ultimo paragrafo del programma diventa quindi:

# 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};
}

Il resto del programma rimane lo stesso. Nota che questo non è di gran lunga il modo più efficiente per farlo, ma dovrebbe essere abbastanza veloce per playlist di qualsiasi dimensione sana. Con i tuoi dati di esempio, tutte le playlist generate inizieranno con una canzone John B., quindi una canzone Anna A., quindi una canzone John B. Dopodiché, è molto meno prevedibile (poiché a tutti tranne John B. è rimasta una canzone). Si noti che ciò presuppone Perl 5.7 o versioni successive.

Approccio 3

L'utilizzo è lo stesso del precedente 2. Nota la 0..4parte, ecco da dove proviene il massimo di 5 tentativi. Potresti aumentare il numero di tentativi, ad esempio, 0..9darebbe 10 in totale. ( 0..4= 0, 1, 2, 3, 4, Che si noterà è in realtà 5 articoli).

#!/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 hai provato i due programmi sui dati reali e vedi se uno di questi è di tuo gradimento? (E, wow, guardando questo, è molto "Fhk Hhck" pesante ... Ho intenzione di aggiungere un approccio 3)
derobert

Alcuni artisti in realtà suonano due volte di seguito (puoi verificarlo con sed 's/ - .*//' output.m3u | uniq -d). E potresti spiegare se si occupa di alcuni artisti che non finiscono all'inizio o alla fine della playlist?
Teresa e Junior,

L'approccio 1 in effetti consente due (o più) di fila. L'approccio 2 no. Anche l'approccio 3 (in procinto di modificarlo) non lo fa (beh, principalmente). Approach 2 appesantisce sicuramente l'inizio della playlist degli artisti più comuni. L'approccio 3 no.
derobert

1
@TeresaeJunior Sono contento che il terzo abbia funzionato! Non sono sicuro di quale approccio 4 sarebbe stato, ma sarebbe spaventoso ...
derobert

1
@JosephR. Approccio 3 # non utilizzare il numero di canzoni di ogni artista, come un peso-implicitamente, con la scelta di un brano a caso. Più canzoni ha un artista, più è probabile che l'artista venga scelto. Il numero 1 è l'unico che non pesa per il numero di brani.
derobert

2

Se non ti dispiace che sia terribilmente inefficiente ...

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"

Continua semplicemente a rotolare e rotolare fino a quando non si ottiene un risultato che non ha due o più John di fila. Se ci sono così tanti Johns nella tua playlist che una tale combinazione non esiste o è estremamente improbabile che venga lanciata, beh, si bloccherà.

Esempio di risultato con il tuo input:

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

Se decommenta le righe di debug, ti dirà perché non è riuscito:

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

Ciò dovrebbe aiutare a determinare la causa nel caso in cui si blocchi indefinitamente.


Mi piace l'idea, ma la sceneggiatura è in corso da quasi 15m e non sono riuscito a trovare una combinazione adatta. Non è che ho troppe canzoni di John, ma la playlist è composta da più di 7000 righe e sembra che sia stata sortprogettata.
Teresa e Junior,

1
Per quanto riguarda le prestazioni, shufmescola la playlist 80 volte più velocemente di sort -R. Neanche io lo sapevo! Lo lascerò attivo per 15 minuti con shuf, le probabilità sono più alte!
Teresa e Junior,

Per eseguire il debug, echo "$D"prima del if. Ciò dovrebbe dirti quali duplicati hanno impedito la scelta del risultato. Questo dovrebbe dirti dove cercare il problema. (Modifica: Aggiunto possibile codice di debug alla risposta.)
frostschutz

DEBUG mostra sempre circa 100 righe, ma di artisti casuali, quindi sembra che molti artisti stiano causando il problema. Penso che non sia davvero possibile con sorto shuf.
Teresa e Junior,

1

Un altro approccio con Bash. Legge la playlist in ordine casuale, tenta di inserire la riga all'altra estremità della lista se è una copia duplicata e mette da parte un singolo duplicato per reinserirlo in un altro posto. Non riesce se ci sono tripli duplicati (primo, ultimo e messo da parte identici) e aggiungerà quelle voci cattive alla fine dell'elenco. Sembra essere in grado di risolvere la lunga lista che hai caricato la maggior parte del tempo.

#!/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"

Potrebbe essere più intelligente ... nel tuo esempio John, John di solito si atterrà a essere l'ultimo_artista perché cerca sempre di aggiungere prima il primo_artista. Quindi se ci sono altri due artisti nel mezzo, non è abbastanza intelligente aggiungere uno all'inizio e l'altro alla fine per evitare il triplo John. Quindi, con le liste che praticamente richiedono che ogni altro artista sia John, ottieni più fallimenti di quanto dovresti.


Grazie per questo script bash. È l'unico che posso davvero capire e modificare a piacimento!
Teresa e Junior,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.