몇 가지 추가 제약 조건으로 파일을 임의로 셔플


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 / etc) 스크립트가 필요합니다.
goldilocks

또는 구조적 임의성 :)
Teresa e Junior

바로 그거죠! 이것은 펄이나 파이썬에서 좋은 운동이 될 것입니다. 나는 비록 그것이 떠들썩한 파티와 두통이있을 거라고 생각 수도 내가 충분히 말을하기 AWK 모른다 - AWK와 함께 잘 작동합니다.
goldilocks

이를 수행 할 도구가없는 것 같으므로 스크립트가 진행되는 것 같습니다. 게으른 게 아니라 아이디어가 없습니다.
Teresa e Junior

1
간단한 알고리즘으로이 작업을 수행 할 수 있습니다. 각 아티스트가 무작위로 노래를 선택하여 재생 목록을 만듭니다 (턴은 무작위로 지정할 수 있지만 아티스트 반복없이). 한 아티스트의 모든 노래가 소진되면 같은 아티스트의 노래 인접성을 최소화하는 방식으로 나머지 아티스트의 노래를 기존 재생 목록과 함께 인터리빙하기 시작합니다. 끝날 때까지 계속 반복하십시오. 이것을 실제 스크립트에 넣을 시간이 없어서 죄송합니다. 난 당신이 자신의 롤을 도울 수 있다고 생각했습니다.
Joseph R.

답변:


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)를 시도하면 모두 인접한 노래를 남기지 않고 pastebin에 남은 재생 목록을 섞습니다. 그러나보다 현명한 방식으로하는 것 같습니다. 게다가, 당신의 것만이 John B. 예제에서 완벽하게 작동합니다 . 나는 참을성이 있고 나에게 도움이 되었기 때문에 derobert가 그의 대답을 받아들이겠다고 약속했으며, 그의 세 번째 접근법도 매우 좋습니다. 그래서 나는 당신에게 그에게 최고의 답변과 현상금을 줄 것이다. 그리고 그가 나에게 화 내지 않기를 바란다. :)
Teresa e Junior

7

예제 데이터 및 제약 조건은 실제로 몇 가지 솔루션 만 허용합니다. 예를 들어 다른 노래마다 John B.를 연주해야합니다. 나는 당신의 실제 전체 재생 목록이 본질적으로 John B 가 아니라고 가정 할 것 입니다.

이것은 또 다른 무작위 접근 방식입니다. @frostschutz의 솔루션과 달리 빠르게 실행됩니다. 그러나 기준과 일치하는 결과를 보장하지는 않습니다. 또한 예제 데이터에서 작동하는 두 번째 방법을 제시하지만 실제 데이터에 나쁜 결과를 초래할 것으로 생각됩니다. 실제 데이터 (난독 처리됨)를 가짐으로써 동일한 아티스트가 두 곡을 연속으로 피하는 것을 제외하고는 접근 방식 3을 추가했습니다. 그것은 남아있는 노래의 "데크"에 5 번의 "드로우"만 만들고, 그 후에도 여전히 복제 아티스트와 직면하면 그 노래를 출력 할 것입니다. 이런 식으로 프로그램은 실제로 프로그램이 끝날 것입니다.

접근법 1

기본적으로 각 지점에서 재생 목록을 생성하여 "아직 어떤 아티스트에게 아직 재생되지 않은 노래가 있습니까?" 그런 다음 임의의 아티스트를 선택하고 마침내 해당 아티스트의 임의의 노래를 선택하십시오. 즉, 각 아티스트는 노래 수에 비례하지 않고 동일하게 가중치를 적용합니다.

실제 재생 목록에서 시도해보고 균일하게 무작위보다 더 나은 결과를 얻을 수 있는지 확인하십시오.

사용법 :./script-file < input.m3u > output.m3uchmod +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};
}

나머지 프로그램은 동일하게 유지됩니다. 이 방법이 지금까지 가장 효율적인 방법은 아니지만, 모든 크기의 재생 목록에 대해서는 충분히 빠릅니다. 예제 데이터를 사용하여 생성 된 모든 재생 목록은 John B. 노래, Anna A. 노래, John B. 노래로 시작합니다. 그 후에는 John B.를 제외한 모든 사람이 하나의 노래를 남겼으므로 예측하기가 훨씬 어렵습니다. 이것은 Perl 5.7 이상을 가정합니다.

접근법 3

사용법은 이전 2와 동일합니다.이 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 (편집하려고 함)도 (주로) 좋습니다. 이 접근 확실히 가장 일반적인 아티스트 재생 목록의 무게 시작합니다. 접근법 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이 너무 많아 이러한 조합이 존재하지 않거나 롤링 될 가능성이 거의없는 경우 중단됩니다.

입력 결과의 예 :

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.
-------------

무한정 중단되는 경우 원인을 파악하는 데 도움이됩니다.


아이디어가 마음에 들지만 스크립트는 거의 15m 동안 실행되어 적절한 조합을 찾을 수 없습니다. John의 노래가 너무 많지는 않지만 재생 목록이 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"

John 예제에서 John은 항상 first_artist를 먼저 추가하려고하기 때문에 last_artist가됩니다. 따라서 두 명의 다른 아티스트가 그 사이에 있으면 트리플 존을 피하기 위해 하나를 시작 부분에 추가하고 다른 사람을 끝 부분에 추가하는 것이 현명하지 않습니다. 따라서 기본적으로 다른 모든 아티스트가 John이되어야하는 목록을 사용하면 원하는 것보다 더 많은 실패가 발생합니다.


이 bash 스크립트에 감사드립니다. 내가 정말로 이해하고 마음대로 수정할 수있는 유일한 것입니다!
Teresa e Junior
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.