[Wellington-pm] Select at random from a list

Douglas Bagnall douglas at paradise.net.nz
Mon Jul 17 05:26:11 PDT 2006


Grant McLean wrote:

> Perhaps it would work better if you maintained some state between
> selections.  For example, you could start by copying the contents of
> playlist.txt to a temporary file (writable by the server process), then
> each request would:
>
>  1. slurp all the entries from the temporary file into an array
>  2. make a random selection and remove that one from the array
>  3. write all the remaining items back to the temporary file
>
> At step 1, if the temporary file was found to be empty then the input
> could be taken from the original playlist file.

You could also do this with a closure.  Although my perl is rusty and
probably suspect, the following works:

------------------------------
use strict;
use warnings;

sub shuffle{
    my @all = @_;
    my @left;
    return sub {
	while (1){
	    return splice(@left, rand(@left), 1) if @left;
	    @left = @all;
	}
    }
}

my @playlist = qw{a b c d};

my $playlist = shuffle(@playlist);


for (1..12){
    print &$playlist()." ";
}

------------------------------

It will print something like:

a c d b d c b a d b a c

The effect is semantically equivalent to Grant's solution, but saves
state in the shuffle function rather than a temporary file.

The subroutine returned by shuffle() is stuck in shuffle's namespace, so
 it sees @left and @all even when it is called from outside (just like a
normal sub can see top-level variables).

@all and @left are initialised to the input list, but each time the
returned sub is called, @left is depleted by one item.  It refills when
empty, and the cycle continues.

This is still imperfect, however, if you don't want back-to-back
repeats.  If the list is shuffled first as "a c b d", then as "d c a b",
song d repeats.

so perhaps something like this would be better:

--------------------------------------

my $WAIT = 1;

sub shuffle2{
    my @mp3s = @_;
    return sub {
	my $next = splice(@mp3s, rand(@mp3s - $WAIT), 1);
	push (@mp3s, $next);
	return $next;
    }
}

my @playlist = qw{a b c d};

my $playlist = shuffle2(@playlist);

my ($a, $b) = ('', '');
my %count;
for (1..1000){
    $a = &$playlist();
    die "repeating!!" if ($a eq $b);
    #print $a;
    $b = $a;
    $count{$a}++;
}

while (my ($k, $v) = each %count) {
    print "$k: $v\n";
}

---------------------------

$WAIT determines how many intervening songs are necessary before a
repeat.  Obviously it can't be less than 1 or greater than @mp3s - 1,
and would probably be best determined dynamically.


regards,

douglas


More information about the Wellington-pm mailing list