สุ่มไฟล์โดยมีข้อ จำกัด เพิ่มเติม


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

หรือแบบแผนโครงสร้าง :)
เทเรซาอีจูเนียร์

แน่นอน! นี่จะเป็นการออกกำลังกายที่ดีใน perl หรือ python ฉันคิดว่ามันจะปวดหัวกับทุบตีแม้ว่ามันอาจจะทำงานได้ดีกับ awk - ฉันไม่รู้ awk ดีพอที่จะพูด
goldilocks

เนื่องจากดูเหมือนจะไม่มีเครื่องมือใด ๆ ที่จะทำเช่นนั้นสคริปต์ดูเหมือนจะเป็นหนทางไป ไม่ใช่ว่าฉันขี้เกียจ แต่ฉันไม่ได้คิด
Teresa e Junior

1
คุณสามารถทำได้ด้วยอัลกอริธึมง่ายๆ: สร้างเพลย์ลิสต์โดยเลือกเพลงแบบสุ่มของศิลปินแต่ละคนตามลำดับ (ซึ่งสามารถสุ่มแบบเทิร์นได้ แต่ไม่มีการซ้ำซ้อนของศิลปิน) เมื่อเพลงทั้งหมดของศิลปินคนหนึ่งหมดลงให้เริ่มต้นการรวมเพลงโดยศิลปินที่เหลือ (อีกครั้งสลับระหว่างเพลงเหล่านั้น) กับเพลย์ลิสต์ที่มีอยู่ในลักษณะที่จะลดการติดเพลงจากศิลปินเดียวกัน ทำซ้ำจนกว่าคุณจะเสร็จสิ้น ฉันขอโทษที่ฉันไม่มีเวลาที่จะทำให้เรื่องนี้เป็นสคริปต์จริง ฉันแค่คิดว่ามันอาจจะมีประโยชน์ในการช่วยให้คุณม้วนตัวเอง
โจเซฟอาร์

คำตอบ:


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 และทุบตี) พวกเขาทั้งหมดสลับเพลงที่ฉันทิ้งไว้ใน Pastebin โดยไม่ทิ้งเพลงที่อยู่ติดกัน แต่ดูเหมือนว่าคุณจะทำอย่างฉลาดกว่า นอกจากนี้มีเพียงคุณเท่านั้นที่ทำงานอย่างสมบูรณ์แบบในตัวอย่างของJohn B.ซึ่งทำให้มันเป็นคำตอบที่ดีที่สุดอย่างไม่ต้องสงสัย ฉันสัญญาว่า Derobert จะยอมรับคำตอบของเขาเพราะเขาอดทนและเป็นประโยชน์กับฉันและวิธีที่ 3 ของเขาก็ดีเช่นกัน ดังนั้นฉันจะให้คำตอบที่ดีที่สุดและความโปรดปรานแก่เขาและฉันหวังว่าเขาจะไม่โกรธฉัน :)
Teresa e Junior

7

ข้อมูลและข้อ จำกัด ตัวอย่างของคุณจริง ๆ แล้วอนุญาตเพียงโซลูชันสองสามอย่างเท่านั้น - คุณต้องเล่น John B. ทุกเพลงอื่น ๆ เช่น ฉันจะถือว่ารายการเพลงเต็มรูปแบบที่เกิดขึ้นจริงของคุณไม่ได้เป็นหลักจอห์นบกับสิ่งอื่น ๆ สุ่มเพื่อทำลายมันได้

นี่เป็นอีกวิธีการสุ่ม ไม่เหมือนกับโซลูชันของ @ frostschutz มันทำงานได้อย่างรวดเร็ว อย่างไรก็ตามไม่รับประกันผลลัพธ์ที่ตรงกับเกณฑ์ของคุณ ฉันยังนำเสนอวิธีที่สองซึ่งทำงานกับข้อมูลตัวอย่างของคุณ - แต่ฉันสงสัยว่าจะให้ผลลัพธ์ที่ไม่ดีกับข้อมูลจริงของคุณ มีข้อมูลจริงของคุณ (ทำให้งง) ฉันเพิ่มวิธีที่ 3 ซึ่งเป็นแบบสุ่มยกเว้นมันหลีกเลี่ยงสองเพลงโดยศิลปินคนเดียวกันในแถว โปรดทราบว่ามันทำให้เพียง 5 "ดึง" ลงใน "สำรับ" ของเพลงที่เหลืออยู่เท่านั้นหากหลังจากนั้นยังคงต้องเผชิญกับศิลปินที่ซ้ำกันมันจะเอาท์พุทเพลงนั้นต่อไป - ด้วยวิธีนี้รับประกันได้ว่าโปรแกรมจะเสร็จจริงๆ

วิธีที่ 1

โดยพื้นฐานแล้วมันจะสร้างเพลย์ลิสต์ในแต่ละจุดโดยถามว่า "ฉันยังมีเพลงที่ยังไม่ได้เล่นหรือไม่ จากนั้นเลือกศิลปินสุ่มและในที่สุดก็เป็นเพลงสุ่มจากศิลปินคนนั้น (นั่นคือศิลปินแต่ละคนมีน้ำหนักเท่ากันไม่ได้สัดส่วนตามจำนวนเพลง)

ลองเล่นเพลย์ลิสต์จริงของคุณดูว่ามันให้ผลลัพธ์ที่ดีกว่าการสุ่มอย่างสม่ำเสมอหรือไม่

การใช้งาน:./script-file < input.m3u > output.m3uให้แน่ใจว่าchmod +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);
}

@TesesaeJunior คุณลองทั้งสองโปรแกรมกับข้อมูลจริงและดูว่าคุณชอบหรือไม่? (และว้าวดูที่มันเป็นอย่างมาก "Fhk Hhck" หนัก ... ฉันจะเพิ่มวิธีการ 3)
Derobert

ศิลปินบางคนเล่นจริงสองครั้งติดต่อกัน (คุณสามารถตรวจสอบได้sed 's/ - .*//' output.m3u | uniq -d) และคุณช่วยอธิบายได้ไหมถ้ามันช่วยดูแลศิลปินบางคนที่ไม่ได้จบลงในตอนเริ่มต้นหรือตอนจบของเพลย์ลิสต์?
Teresa e Junior

วิธีการที่ 1 อนุญาตให้มีสองแถวขึ้นไป วิธีที่ 2 ไม่ได้ วิธีที่ 3 (เกี่ยวกับการแก้ไขใน) ไม่เช่นกัน (ส่วนใหญ่) วิธีที่ 2 แน่นอนน้ำหนักจุดเริ่มต้นของรายการเพลงโดยศิลปินส่วนใหญ่ที่พบบ่อย วิธีที่ 3 จะไม่
Derobert

1
@TesesaeJunior ฉันดีใจที่คนที่สามใช้งานได้! ผมไม่แน่ใจว่าสิ่งที่วิธีที่ 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"

มันแค่กลิ้งไปมาเรื่อย ๆ จนกว่าจะถึงผลลัพธ์ที่ไม่มี Johns สองตัวขึ้นไปในแถว หากมี Johns จำนวนมากในเพลย์ลิสต์ของคุณที่ไม่มีชุดค่าผสมดังกล่าวหรือไม่น่าจะถูกรีดมากมันจะหยุดทำงาน

ตัวอย่างผลลัพธ์จากข้อมูลที่คุณป้อน:

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 แล้วและไม่พบชุดค่าผสมที่เหมาะสม ไม่ใช่ว่าฉันมีเพลงของจอห์นมากเกินไป แต่เพลย์ลิสต์นั้นมีมากกว่า 7000 บรรทัดและดูเหมือนว่าจะเป็นวิธีการsortออกแบบ
Teresa e Junior

1
เกี่ยวกับประสิทธิภาพการทำงานshufshuffles เพลย์ลิส 80 sort -Rครั้งเร็วกว่า ฉันก็ไม่รู้เหมือนกัน! ฉันจะปล่อยให้มันทำงานเป็นเวลา 15 นาทีshufโอกาสสูงกว่า!
Teresa e Junior

เพื่อแก้ปัญหาก่อนecho "$D" ifที่ควรบอกคุณว่ารายการที่ซ้ำกันป้องกันผลจากการถูกเลือก นั่นควรบอกคุณว่าต้องค้นหาปัญหาที่ไหน (แก้ไข: เพิ่มรหัสการแก้ปัญหาที่เป็นไปได้ให้กับคำตอบ)
35632

DEBUG แสดงประมาณ 100 บรรทัดเสมอ แต่จากศิลปินสุ่มดังนั้นดูเหมือนว่าศิลปินจำนวนมากกำลังก่อให้เกิดปัญหา ฉันคิดว่ามันไม่ได้จริงๆไปได้ด้วยหรือsort shuf
Teresa e Junior

1

อีกวิธีใช้ Bash จะอ่านเพลย์ลิสต์ตามลำดับแบบสุ่มพยายามแทรกบรรทัดที่ปลายอีกด้านของรายการหากเป็นรายการที่ซ้ำกันและวาง dupe แยกไว้ข้างหนึ่งเพื่อใส่เข้าไปในที่อื่น มันจะล้มเหลวหากมีการทำซ้ำสามครั้ง (ครั้งแรกครั้งสุดท้ายและแยกกัน) และมันจะผนวกรายการที่ไม่ดีเหล่านั้นไว้ท้ายสุดของรายการ ดูเหมือนว่าจะสามารถแก้ไขรายการที่คุณอัปโหลดได้เป็นส่วนใหญ่

#!/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 มักจะยึดติดกับ last_artist เพราะมันพยายามที่จะผนวก first_artist ก่อนเสมอ ดังนั้นหากมันมีศิลปินอีกสองคนในระหว่างนั้นมันไม่ฉลาดพอที่จะผนวกหนึ่งไปยังจุดเริ่มต้นและอีกอันเพื่อสิ้นสุดเพื่อหลีกเลี่ยงสามจอห์น ดังนั้นด้วยรายการที่จำเป็นต้องมีศิลปินคนอื่น ๆ ทุกคนเป็นจอห์นคุณจะได้รับความล้มเหลวมากกว่าที่ควร


ขอบคุณสำหรับสคริปต์ทุบตีนี้ มันเป็นสิ่งเดียวที่ฉันสามารถเข้าใจและปรับเปลี่ยนได้ตามต้องการ!
Teresa e Junior
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.