Aleatório arquivo aleatoriamente com algumas restrições adicionais

12

Eu tenho uma enorme lista de músicas e, enquanto alguns artistas têm muitos álbuns, outros têm apenas uma música. Eu queria classificar a playlist para que o mesmo artista não tocasse duas vezes seguidas, ou as músicas dele não terminariam principalmente no começo ou no final da playlist.

Lista de reprodução de exemplo:

$ 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

Saída de sort -R ou 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.

O que estou esperando:

$ 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
    
por Teresa e Junior 01.11.2013 / 17:41

4 respostas

5

Se eu tivesse que aplicar esse embaralhamento em um baralho de cartas, acho que primeiro embaralharia o baralho, depois mostraria as cartas em fila diante dos meus olhos e processaria da esquerda para a direita, onde quer que houvesse clubes adjacentes ou coração ... mova todos menos um daqueles aleatoriamente em outro lugar (embora não próximo a outro do mesmo tipo).

Por exemplo, com uma mão como

                                    
por 04.11.2013 / 22:20
7

Seus dados e restrições de exemplo, na verdade, apenas permitem algumas soluções - você deve tocar John B. todas as outras músicas, por exemplo. Suponho que sua playlist completa não seja essencialmente John B, com outras coisas aleatórias para dividi-la .

Esta é outra abordagem aleatória. Ao contrário da solução @ frostschutz, ela é executada rapidamente. Não garante um resultado que corresponda aos seus critérios. Eu também apresento uma segunda abordagem, que funciona em seus dados de exemplo - mas suspeito que produzirá resultados ruins em seus dados reais. Tendo seus dados reais (ofuscados), eu adiciono a abordagem 3 - que é um aleatório uniforme, exceto que evita duas músicas do mesmo artista em uma linha. Note que ele só faz 5 "draws" no "deck" das músicas restantes, se depois disso ele ainda for confrontado com um artista duplicado, ele produzirá a música de qualquer maneira - dessa forma, é garantido que o programa irá realmente terminar.

Abordagem 1

Basicamente, ele gera uma playlist em cada ponto, perguntando "de quais artistas eu ainda não tirei músicas?" Em seguida, escolher um artista aleatório e, finalmente, uma música aleatória desse artista. (Ou seja, cada artista é ponderado igualmente, não em proporção ao número de músicas).

Teste sua playlist real e veja se ela produz melhores resultados do que uniformemente aleatória.

Uso: ./script-file < input.m3u > output.m3u Certifique-se de chmod +x , é claro. Note que ele não manipula a linha de assinatura que está no topo de alguns arquivos M3U corretamente ... mas o seu exemplo não tem 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 uma segunda abordagem, em vez de escolher um artista aleatório , você pode usar escolher o artista com mais músicas, que também não é o último artista que escolhemos . O parágrafo final do programa torna-se então:

# 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 resto 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 sã. Com seus dados de exemplo, todas as playlists geradas começarão com uma música de John B., depois uma de Anna A. e uma de John B. Depois disso, é muito menos previsível (já que todos, exceto John B., ainda têm uma música). Observe que isso pressupõe o Perl 5.7 ou posterior.

Abordagem 3

O uso é o mesmo que o anterior 2. Observe a 0..4 parte, é de onde 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 , o que você notará é na verdade 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);
}
    
por 04.11.2013 / 17:41
2

Se você não se importa de ser horrivelmente ineficiente ...

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"

Ele continua rolando e rolando até encontrar um resultado que não tenha dois ou mais Johns seguidos. Se houver tantos Johns em sua lista de reprodução que tal combinação não exista ou seja extremamente improvável que seja rolada, bem, ela irá parar.

Exemplo de resultado com sua entrada:

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 você descomentar as linhas de depuração, ele informará por que ele falhou:

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

Isso deve ajudar a determinar a causa, caso ela seja interrompida indefinidamente.

    
por 01.11.2013 / 20:11
1

Outra abordagem usando o Bash. Ele lê a playlist em ordem aleatória, tenta inserir a linha no outro lado da lista, se for uma duplicata, e coloca um único dupe de lado para reinseri-lo em outro lugar. Ele falhará se houver duplicatas triplas (primeiro, último e reservado) e anexará as entradas incorretas no final da lista. Parece ser capaz de resolver a extensa lista que você enviou na maior parte do 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"

Poderia ser mais inteligente ... no seu exemplo de John, John geralmente vai ser o último_artista porque sempre tenta anexar o first_artist primeiro. Então, se tiver outros dois artistas no meio, não é inteligente o suficiente para acrescentar um ao início e outro ao final para evitar o triplo John. Então, com listas que basicamente exigem que todos os outros artistas sejam John, você tem mais falhas do que deveria.

    
por 04.11.2013 / 19:29