Xáo trộn tập tin ngẫu nhiên với một số ràng buộc bổ sung


12

Tôi có một danh sách nhạc lớn và trong khi một số nghệ sĩ có nhiều album thì những người khác chỉ có một bài hát. Tôi muốn sắp xếp danh sách phát để cùng một nghệ sĩ sẽ không phát hai lần liên tiếp hoặc các bài hát của anh ấy sẽ không kết thúc chủ yếu vào đầu hoặc cuối danh sách phát.

Danh sách phát ví dụ:

$ 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

Đầu ra từ sort -Rhoặc 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.

Những gì tôi đang mong đợi:

$ 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
Về mặt kỹ thuật, những gì bạn đang yêu cầu là ít ngẫu nhiên hơn , và nhiều cấu trúc hơn. Điều đó không phải là không thể, nhưng nó sẽ yêu cầu một tập lệnh (bash / awk / perl / python / etc).
goldilocks

Hoặc một sự ngẫu nhiên có cấu trúc :)
Teresa e Junior

Chính xác! Đây sẽ là một bài tập tốt trong perl hoặc python. Tôi nghĩ rằng nó sẽ là một vấn đề đau đầu với bash, mặc dù nó có thể hoạt động tốt với awk - tôi không biết awk đủ để nói.
goldilocks

Vì dường như không có bất kỳ công cụ nào để làm điều đó, một kịch bản dường như là cách để đi. Không phải là tôi lười biếng, mà là tôi hết ý tưởng.
Teresa e Junior

1
Bạn có thể thực hiện điều này bằng một thuật toán đơn giản: tạo danh sách phát bằng cách chọn lần lượt một bài hát ngẫu nhiên của từng nghệ sĩ (trong đó lần lượt cũng có thể được chọn ngẫu nhiên nhưng không có sự lặp lại của nghệ sĩ). Khi tất cả các bài hát của một nghệ sĩ đã cạn kiệt, hãy bắt đầu xen kẽ các bài hát của các nghệ sĩ còn lại (lần lượt xen kẽ giữa các bài hát đó) với danh sách nhạc hiện có theo cách để giảm thiểu sự phụ thuộc các bài hát của cùng một nghệ sĩ. Tiếp tục lặp lại cho đến khi bạn hoàn thành. Tôi xin lỗi vì tôi không có thời gian để đưa nó vào một kịch bản thực tế; Tôi chỉ nghĩ rằng nó có thể hữu ích để giúp bạn tự lăn.
Joseph R.

Câu trả lời:


5

Nếu tôi phải áp dụng cách xáo trộn đó vào một cỗ bài, tôi nghĩ rằng trước tiên tôi phải xáo trộn bộ bài, sau đó hiển thị các thẻ liên tiếp trước mắt tôi và xử lý từ trái sang phải, bất cứ nơi nào có câu lạc bộ hoặc trái tim liền kề .. . di chuyển tất cả trừ một trong số chúng ngẫu nhiên ở một nơi khác (mặc dù không bên cạnh một cái khác cùng loại).

Ví dụ, với một bàn tay như

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

Sau khi xáo trộn cơ bản:

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

hai nhóm spades liền kề, chúng ta cần di chuyển 1, 2 và 3. Đối với 1, các lựa chọn là:

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

Chúng tôi chọn ngẫu nhiên một trong số 4. Sau đó, chúng tôi lặp lại quy trình cho 2 và 3.

Thực hiện trong perlđó sẽ là:

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'

Nó sẽ tìm ra giải pháp với các nghệ sĩ không liền kề nếu nó tồn tại (trừ khi hơn một nửa số bài hát là của cùng một nghệ sĩ), và phải được thống nhất AFAICT.


Khi thử ba tập lệnh khác nhau (perl và bash), tất cả chúng đều xáo trộn danh sách nhạc tôi để lại trên pastebin mà không để lại các bài hát liền kề, nhưng dường như bạn làm điều đó theo cách thông minh hơn. Bên cạnh đó, chỉ có bạn hoạt động hoàn hảo trên ví dụ John B. , điều này chắc chắn làm cho nó cho một câu trả lời tốt nhất. Tôi đã hứa với derobert sẽ chấp nhận câu trả lời của anh ấy, vì anh ấy rất kiên nhẫn và hữu ích với tôi, và cách tiếp cận thứ 3 của anh ấy cũng rất tốt. Vì vậy, tôi sẽ cho bạn câu trả lời hay nhất và tiền thưởng cho anh ấy, và tôi hy vọng anh ấy không giận tôi :)
Teresa e Junior

7

Dữ liệu ví dụ và các ràng buộc của bạn thực sự chỉ cho phép một vài giải pháp mà bạn phải chơi John B. mọi bài hát khác chẳng hạn. Tôi sẽ giả sử danh sách phát đầy đủ thực tế của bạn không phải là John B, với những thứ khác ngẫu nhiên để phá vỡ nó .

Đây là một cách tiếp cận ngẫu nhiên khác. Không giống như giải pháp của @ frostschutz, nó chạy rất nhanh. Tuy nhiên, nó không đảm bảo một kết quả phù hợp với tiêu chí của bạn. Tôi cũng trình bày một cách tiếp cận thứ hai, hoạt động trên dữ liệu mẫu của bạn nhưng tôi nghi ngờ sẽ tạo ra kết quả xấu trên dữ liệu thực của bạn. Có dữ liệu thực của bạn (bị xáo trộn), tôi thêm cách tiếp cận 3, đây là một cách ngẫu nhiên thống nhất, ngoại trừ việc nó tránh hai bài hát của cùng một nghệ sĩ liên tiếp. Lưu ý rằng nó chỉ tạo ra 5 "bản vẽ" trong "bộ bài" còn lại, nếu sau đó nó vẫn phải đối mặt với một nghệ sĩ trùng lặp, thì dù sao đi nữa, bài hát này sẽ đảm bảo rằng chương trình sẽ thực sự kết thúc.

Cách tiếp cận 1

Về cơ bản, nó tạo ra một danh sách nhạc theo từng thời điểm, hỏi "những nghệ sĩ nào tôi vẫn có những bài hát chưa được phát từ đó?" Sau đó chọn một nghệ sĩ ngẫu nhiên, và cuối cùng là một bài hát ngẫu nhiên từ nghệ sĩ đó. (Nghĩa là, mỗi nghệ sĩ đều có trọng số như nhau, không tương xứng với số lượng bài hát.)

Hãy dùng thử danh sách phát thực tế của bạn và xem liệu nó có mang lại kết quả tốt hơn so với ngẫu nhiên thống nhất không.

Cách sử dụng:./script-file < input.m3u > output.m3u Hãy chắc chắn với chmod +xnó, tất nhiên. Lưu ý rằng nó không xử lý dòng chữ ký nằm ở đầu một số tệp M3U đúng cách ... nhưng ví dụ của bạn không có điều đó.

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

Cách tiếp cận 2

Như một cách tiếp cận thứ hai, thay vì chọn một nghệ sĩ ngẫu nhiên , bạn có thể sử dụng chọn nghệ sĩ có nhiều bài hát nhất, người cũng không phải là nghệ sĩ cuối cùng chúng tôi chọn . Đoạn cuối của chương trình sau đó trở thành:

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

Phần còn lại của chương trình vẫn giữ nguyên. Lưu ý rằng điều này cho đến nay không phải là cách hiệu quả nhất để làm điều này, nhưng nó phải đủ nhanh cho danh sách phát ở bất kỳ kích thước lành mạnh nào. Với dữ liệu mẫu của bạn, tất cả các danh sách phát được tạo sẽ bắt đầu bằng một bài hát John B., sau đó là bài hát Anna A., sau đó là bài hát John B. Sau đó, điều đó ít được dự đoán hơn (như mọi người trừ John B. chỉ còn một bài hát). Lưu ý rằng điều này giả định Perl 5.7 trở lên.

Cách tiếp cận 3

Cách sử dụng giống như trước 2. Lưu ý 0..4phần, đó là nơi 5 lần thử tối đa đến từ. Bạn có thể tăng số lần thử, ví dụ: 0..9sẽ cho tổng số 10 lần. ( 0..4= 0, 1, 2, 3, 4, mà bạn sẽ nhận thấy thực sự là 5 mục).

#!/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 bạn đã thử hai chương trình trên dữ liệu thực tế và xem liệu đó có phải là sở thích của bạn không? (Và, wow, nhìn vào đó, nó rất "Fhk Hhck" nặng ... Tôi sẽ thêm một cách tiếp cận 3)
derobert

Một số nghệ sĩ thực sự chơi hai lần liên tiếp (bạn có thể kiểm tra nó sed 's/ - .*//' output.m3u | uniq -d). Và bạn có thể vui lòng giải thích nếu nó quan tâm đến một số nghệ sĩ không kết thúc vào đầu hoặc cuối danh sách phát?
Teresa e Junior

Cách tiếp cận 1 thực sự cho phép hai (hoặc nhiều hơn) liên tiếp. Cách tiếp cận 2 không. Cách tiếp cận 3 (sắp sửa chỉnh sửa) cũng không (tốt, chủ yếu). Cách tiếp cận 2 chắc chắn có trọng lượng bắt đầu danh sách phát của các nghệ sĩ phổ biến nhất. Cách tiếp cận 3 sẽ không.
derobert

1
@TeresaeJunior Tôi rất vui vì người thứ ba đã làm việc! Tôi không chắc chính xác cách tiếp cận 4 sẽ là gì, nhưng nó sẽ đáng sợ ...
derobert

1
@JosephR. Cách tiếp cận số 3 không sử dụng số lượng bài hát của mỗi nghệ sĩ như một trọng số ngầm, bằng cách chọn một bài hát ngẫu nhiên. Nghệ sĩ càng có nhiều bài hát, nghệ sĩ đó càng được chọn. # 1 là người duy nhất không cân nhắc theo số lượng bài hát.
derobert

2

Nếu bạn không phiền thì nó không hiệu quả ...

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"

Nó chỉ tiếp tục lăn và lăn cho đến khi có kết quả không có hai hoặc nhiều John liên tiếp. Nếu có quá nhiều John trong danh sách phát của bạn mà sự kết hợp như vậy không tồn tại hoặc cực kỳ khó có thể được tung ra, thì nó sẽ bị treo.

Kết quả ví dụ với đầu vào của bạn:

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

Nếu bạn bỏ ghi chú các dòng gỡ lỗi, nó sẽ cho bạn biết lý do tại sao nó thất bại:

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

Điều đó sẽ giúp xác định nguyên nhân trong trường hợp nó bị treo vô thời hạn.


Tôi thích ý tưởng này, nhưng kịch bản đã chạy được gần 15m và không thể tìm thấy một sự kết hợp phù hợp. Không phải là tôi có quá nhiều bài hát của John, nhưng danh sách nhạc có hơn 7000 dòng và dường như nó được sortthiết kế như thế nào .
Teresa e Junior

1
Về hiệu suất, shufxáo trộn danh sách phát nhanh hơn 80 lần so với sort -R. Tôi cũng không biết điều đó! Tôi sẽ để nó chạy trong 15 phút với shuf, cơ hội sẽ cao hơn!
Teresa e Junior

Để gỡ lỗi, echo "$D"trước khi if. Điều đó sẽ cho bạn biết những bản sao nào đã ngăn kết quả được chọn. Điều đó sẽ cho bạn biết nơi để tìm kiếm vấn đề. (Chỉnh sửa: Đã thêm mã gỡ lỗi có thể vào câu trả lời.)
frostschutz

DEBUG luôn hiển thị khoảng 100 dòng, nhưng từ các nghệ sĩ ngẫu nhiên, vì vậy có vẻ như rất nhiều nghệ sĩ đang gây ra vấn đề. Tôi nghĩ rằng nó không thực sự có thể với sorthoặc shuf.
Teresa e Junior

1

Một cách tiếp cận khác sử dụng Bash. Nó đọc danh sách phát theo thứ tự ngẫu nhiên, cố gắng chèn dòng ở đầu kia của danh sách nếu nó trùng lặp và đặt một bản sao đơn sang một bên để đặt lại nó ở một nơi khác. Sẽ thất bại nếu có ba bản sao (đầu tiên, cuối cùng và đặt sang một bên giống hệt nhau) và nó sẽ nối các mục xấu đó vào cuối danh sách. Nó dường như có thể giải quyết danh sách mở rộng mà bạn đã tải lên hầu hết thời gian.

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

Nó có thể thông minh hơn ... trong ví dụ John của bạn, John thường sẽ trở thành người cuối cùng bởi vì nó luôn cố gắng nối thêm First_artist trước. Vì vậy, nếu có hai nghệ sĩ khác ở giữa, sẽ không đủ thông minh để nối một người vào đầu và người kia đến cuối để tránh ba John. Vì vậy, với các danh sách về cơ bản yêu cầu mọi nghệ sĩ khác phải là John, bạn sẽ nhận được nhiều thất bại hơn mức bạn nên làm.


Cảm ơn bạn cho kịch bản bash này. Đó là người duy nhất tôi thực sự có thể hiểu và sửa đổi theo ý muốn!
Teresa e Junior
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.