Bazı ek kısıtlamalarla rastgele dosya karıştır


12

Çok büyük bir müzik çalma listem var ve bazı sanatçıların çok sayıda albümü varken, bazılarında sadece bir şarkı var. Çalma listesini sıralamak istedim, böylece aynı sanatçı arka arkaya iki kez çalmayacak veya şarkıları çoğunlukla çalma listesinin başında veya sonunda bitmeyecek.

Örnek oynatma listesi:

$ 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

Dan Çıktı sort -Rya 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.

Ne bekliyorum:

$ 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
Teknik olarak, istediğiniz şey daha az rastgelelik ve daha fazla yapıdır. İmkansız değil, ancak bir (bash / awk / perl / python / etc) komut dosyası gerektirecektir.
goldilocks

Veya yapılandırılmış bir rastgelelik :)
Teresa e Junior

Kesinlikle! Bu perl veya python için iyi bir egzersiz olacaktır. Her ne kadar awk ile iyi çalışsa da bash ile baş ağrısı olacağını düşünüyorum - söylemek için yeterince awk bilmiyorum.
goldilocks

Bunu yapmak için herhangi bir araç olmadığı için, bir senaryo gitmek için bir yol gibi görünüyor. Tembel olduğumdan değil, ama fikirlerim bitti.
Teresa e Junior

1
Bunu basit bir algoritma ile yapabilirsiniz: sırayla her sanatçı tarafından rastgele bir şarkı seçerek çalma listesi yapın (sıranın da rastgele, ancak sanatçı tekrarı olmadan). Bir sanatçının tüm şarkıları tükendiğinde, aynı sanatçının şarkılarının bitişikliğini en aza indirecek şekilde mevcut çalma listesi ile kalan sanatçıların şarkılarını (yine sırayla sırayla dönüşümlü olarak) eklemeye başlayın. İşiniz bitene kadar tekrarlamaya devam edin. Üzgünüm, bunu gerçek bir senaryoya dönüştürmek için zamanım yok; Sadece kendi başınıza dönmenize yardımcı olmanın yararlı olabileceğini düşündüm.
Joseph R.

Yanıtlar:


5

Eğer bu karıştırma işlemini bir oyun kartının desteğine uygulamak zorunda kalırsam, önce desteyi karıştırır, sonra kartları gözlerimden önce arka arkaya görüntüler ve bitişik kulüpler ya da kalbin olduğu her yerde soldan sağa işlerim. bunlardan biri hariç hepsini rastgele başka bir yere taşıyın (aynı türden bir başkasının yanında olmasa da).

Örneğin,

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

Temel karıştırma işleminden sonra:

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

bitişik maçalardan oluşan iki grup, 1, 2 ve 3'ü yeniden konumlandırmamız gerekir. 1 için seçenekler şunlardır:

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

Bunlardan 4 birini rastgele seçiyoruz. Ardından işlemi 2 ve 3 için tekrarlıyoruz.

Uygulanan perlolurdu:

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'

Varsa bitişik olmayan sanatçılarla bir çözüm bulur (şarkıların yarısından fazlası aynı sanatçıdan değilse) ve tek tip AFAICT olmalıdır.


Üç farklı komut dosyasını (perl ve bash) denedikten sonra, hepsi bitişik şarkıları bırakmadan macunbin üzerinde bıraktığım çalma listesini karıştırıyor, ancak sizinki daha akıllı bir şekilde yapıyor gibi görünüyor. Ayrıca, sadece sizinki John B. örneğinde mükemmel bir şekilde çalışır , bu da şüphesiz en iyi cevap için yapar. Derobert'e cevabını kabul edeceğine söz verdim, çünkü bana çok sabırlı ve yardımcı oldu ve 3. yaklaşımı da çok iyi. Bu yüzden sana en iyi cevabı ve ödülünü vereceğim ve umarım bana kızmaz :)
Teresa e Junior

7

Örnek verileriniz ve kısıtlamalarınız aslında sadece birkaç çözüme izin verir; örneğin, John B.'yi diğer tüm şarkılarda çalmanız gerekir. Gerçek tam çalma listenizin aslında John B olmadığını ve onu parçalayacak rastgele başka şeylerle olduğunu varsayacağım .

Bu başka bir rastgele yaklaşımdır. @ Frostschutz'un çözümünün aksine, hızlı çalışır. Ancak kriterlerinize uygun bir sonuç garanti etmez. Ayrıca, örnek verilerinizde çalışan ikinci bir yaklaşım da sunuyorum - ancak gerçek verilerinizde kötü sonuçlar üreteceğinden şüpheleniyorum. Gerçek verilerinize (gizlenmiş) sahipken, aynı sanatçı tarafından üst üste iki şarkıdan kaçınması dışında, üniform bir rastgele olan yaklaşım 3'ü ekliyorum. Kalan şarkıların "destesine" sadece 5 "çekim" yaptığını unutmayın, bundan sonra yine de yinelenen bir sanatçı ile karşı karşıya kalırsa, bu şarkıyı yine de çıkarır - bu şekilde, programın gerçekten biteceğini garanti eder.

Yaklaşım 1

Temel olarak, her noktada "hala hangi şarkılardan çalmadığım şarkılar var?" Sorusunu soran bir çalma listesi oluşturur. Sonra rastgele bir sanatçı ve son olarak o sanatçıdan rastgele bir şarkı seç. (Yani, her sanatçı, şarkı sayısıyla orantılı değil, eşit ağırlıktadır.)

Gerçek oynatma listenizde bir deneyin ve tekdüze rastgele olduğundan daha iyi sonuçlar üretip üretmediğine bakın.

Kullanımı:./script-file < input.m3u > output.m3uchmod +x Tabii ki emin olun . Bazı M3U dosyalarının üstündeki imza satırını düzgün işlemediğini unutmayın ... ancak örneğinizde bu yoktu.

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

Yaklaşım 2

İkinci bir yaklaşım olarak, rastgele bir sanatçı seçmek yerine , en çok şarkıyı seçen sanatçıyı kullanabilirsiniz ; Programın son paragrafı daha sonra:

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

Programın geri kalanı aynı kalır. Bunun bunu yapmanın en etkili yolu olmadığını, ancak herhangi bir aklı başında olan oynatma listeleri için yeterince hızlı olması gerektiğini unutmayın. Örnek verilerinizle, oluşturulan tüm çalma listeleri bir John B. şarkısı, sonra bir Anna A. şarkısı, sonra bir John B. şarkısı ile başlayacaktır. Bundan sonra, daha az tahmin edilebilir (John B. dışındaki herkesin bir şarkısı kaldığı için). Bunun Perl 5.7 veya üstü olduğunu varsaydığını unutmayın.

Yaklaşım 3

Kullanım önceki 2 ile aynıdır. Parçayı not edin 0..4, 5 denemenin maks. Deneme sayısını artırabilirsiniz, örneğin, 0..9toplam 10 verir. ( 0..4= 0, 1, 2, 3, 4, fark edeceğiniz aslında 5 öğedir).

#!/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 iki programı gerçek veriler üzerinde denediniz mi ve her ikisinin de beğeninize göre olup olmadığını gördünüz mü? (Ve vay, buna bakarak, çok "Fhk Hhck" ağır ... Bir yaklaşım ekleyeceğim 3)
derobert

Bazı sanatçılar aslında arka arkaya iki kez çalıyorlar (kontrol edebilirsiniz sed 's/ - .*//' output.m3u | uniq -d). Ve lütfen çalma listesinin başında veya sonunda bitmeyen bazı sanatçıların ilgilenip ilgilenmediğini açıklayabilir misiniz?
Teresa e Junior

Yaklaşım 1 aslında arka arkaya iki (veya daha fazla) izin verir. Yaklaşım 2 bunu yapmaz. Yaklaşım 3 (içinde düzenleme yapmak üzere) de (çoğunlukla, çoğunlukla) değildir. Yaklaşım 2 kesinlikle en yaygın sanatçıların çalma listesinin başlangıcına ağırlık verir. Yaklaşım 3 olmayacak.
derobert

1
@TeresaeJunior Üçüncüsünün çalışmasına sevindim! Tam olarak hangi yaklaşımın 4 olacağından emin değilim, ama korkutucu olurdu ...
derobert

1
@JosephR. Yaklaşım # 3 , her sanatçının şarkı sayısını ağırlık olarak kullanır - dolaylı olarak rastgele bir şarkı seçerek. Bir sanatçının şarkı sayısı arttıkça, sanatçının seçilme olasılığı da artar. # 1, şarkı sayısına göre ağırlık vermeyen tek kişidir.
derobert

2

Korkunç verimsiz olduğunu düşünmezseniz ...

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"

Üst üste iki veya daha fazla Johns olmayan bir sonuç gelene kadar yuvarlanmaya ve yuvarlanmaya devam eder. Oynatma listenizde böyle bir kombinasyonun bulunmadığı veya yuvarlanma olasılığı düşük olan çok fazla Johns varsa, asılacaktır.

Girişinizle ilgili örnek sonuç:

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

Hata ayıklama satırlarını kaldırırsanız, neden başarısız olduğunu size bildirir:

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

Bu, süresiz olarak asılı kalması durumunda sebebin belirlenmesine yardımcı olmalıdır.


Fikri beğendim, ama komut dosyası neredeyse 15 metreden fazla çalışıyor ve uygun bir kombinasyon bulamadı. John'dan çok fazla şarkım olduğu değil, çalma listesi 7000 satırdan fazla ve nasıl sorttasarlanmış gibi görünüyor .
Teresa e Junior

1
Performansla ilgili olarak shuf, oynatma listesini 80 kat daha hızlı karıştırır sort -R. Ben de bilmiyordum! 15 dakika boyunca çalışır halde bırakacağım shuf, şansı daha yüksek!
Teresa e Junior

Hata ayıklamak için echo "$D"önce if. Bu, hangi kopyaların sonucun seçilmesini engellediğini söylemelidir. Bu size sorunu nerede arayacağınızı söylemelidir. (Düzenle:
Cevaba

DEBUG her zaman yaklaşık 100 satır gösterir, ancak rastgele sanatçılardan, bu yüzden birçok sanatçı soruna neden oluyor gibi görünüyor. Bence sortveya ile gerçekten mümkün değil shuf.
Teresa e Junior

1

Bash kullanan başka bir yaklaşım. Çalma listesini rasgele sırayla okur, yineleniyorsa listenin diğer ucuna satırı eklemeye çalışır ve başka bir yere yeniden yerleştirmek için tek bir dupe ayırır. Üç kez yinelenirse başarısız olur (ilk, son ve aynı kenara koyun) ve bu kötü girişleri listenin sonuna ekler. Çoğu zaman yüklediğiniz kapsamlı listeyi çözebiliyor gibi görünüyor.

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

Daha akıllı olabilir ... John örneğinizde, John genellikle last_artist olmaya devam edecektir, çünkü her zaman önce first_artist'i eklemeye çalışır. Bu arada iki sanatçı daha alırsa, üçlü John'dan kaçınmak için birini başa, diğerini sonuna eklemek yeterince akıllı değildir. Yani temelde diğer tüm sanatçıların John olmasını gerektiren listelerde, gerekenden daha fazla başarısızlık elde edersiniz.


Bu bash senaryosu için teşekkürler. Gerçekten anlayabildiğim ve değiştirebildiğim tek kişi bu!
Teresa e Junior
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.