amnesty_puppy has asked for the wisdom of the Perl Monks concerning the following question:

Hey, I'm trying to Perl some BIO past papers (a British programming contest) but I have some results that I can't explain. The program has to take in a string where b, i and o are functions and have coefficients and groupings such as b3(io) does b then io 3 times. Full explanation is here (question two):

BIO Past Papers

my script so far is as follows, you can probaby work out what I'm trying to do here:

#!/usr/bin/perl # Shuffles cards @pack = (1, 2, 3, 4, 5, 6, 7, 8); sub b { push(@pack, shift @pack); } sub i { my ($a, $b, $c, $d, $e, $f, $g, $h) = @pack; @pack = ($e, $a, $f, $b, $g, $c, $h, $d); } sub o { my ($a, $b, $c, $d, $e, $f, $g, $h) = @pack; @pack = ($a, $e, $b, $f, $c, $g, $d, $h); } sub parse { my ($string) = @_; print "\nParse $string: "; foreach $offset (0..(length($string)-1)) { $char = substr($string, $offset, 1); if ($char =~ /b|i|o/) { print "bio "; &{$char}; } elsif ($char =~ /\d/) { print "digit "; # foreach (1..$char) { &parse(substr($string, $offset+1)); +} } elsif ($char =~ /\(/) { print "open "; &parse(substr($string, $offset+1, (rindex($string, ")", $o +ffset)-$offset)+1) ); } elsif ($char eq ")") { print "close "; } } } chomp($shuffle = <STDIN>); &parse($shuffle); print "\n@pack\n";#

I don't know why this prints:

oib(oib) Parse oib(oib): bio bio bio open Parse o: bio bio bio bio close 1 4 5 8 2 3 6 7

when it should print

oib(oib) Parse oib(oib): bio bio bio open Parse oib: bio bio bio # whatever here

Firstly the string doesn't seem to get passed correctly, and then it parses it wrong anyway (too many bios). Anyone wanna try this one?

Thanks, Dom.

Replies are listed 'Best First'.
Re: Shuffling cards
by blazar (Canon) on Feb 22, 2005 at 14:30 UTC
    Unfortunately I don't have the time to see what this is really about and to delve into your code in its entirety. I have a few general remarks that hopefully will help you anyway...

    Update: eventually I checked the rules and I provide some minimal but complete working example here: 433664.

    #!/usr/bin/perl # Shuffles cards
    Always -yes: always!-
    use strict; use warnings;
    in all but most trivial code (e.g. one-liners). This does not address your specific issue, but applies to any issue...
    @pack = (1, 2, 3, 4, 5, 6, 7, 8);
    As a side note, since you use .. below, I can't see why you don't use it here too...
    sub b { push(@pack, shift @pack); }
    Hmmm, while occasionally it can be handy to have a sub manipulating some "global" data, parameter passing is generally a much better solution. It's what a function is for.

    I suggest you read something about @_, e.g. perlsub.

    sub i { my ($a, $b, $c, $d, $e, $f, $g, $h) = @pack; @pack = ($e, $a, $f, $b, $g, $c, $h, $d); } sub o { my ($a, $b, $c, $d, $e, $f, $g, $h) = @pack; @pack = ($a, $e, $b, $f, $c, $g, $d, $h); }
    Ditto as above. But then you may also use slicing instead. In that case you may also "generate" the subscripts rather than hardcoding them, but I agree that in this case you wouldn't earn much and probably hardcoding them is the best choice...

    Also, it is always recommended to avoid using $a and $b as general purpose variables. This can lead to confusion and gotchas (see perlvar).

    sub parse { my ($string) = @_; print "\nParse $string: "; foreach $offset (0..(length($string)-1)) { $char = substr($string, $offset, 1);
    I think that most people here would write
    for (string =~ /./g) { # ...
    or
    for (split //, $string) { # ...
    instead. As a side note I would also have (a local()) $_ instead of $string and so I'd have
    for (/./g) { # ...
    or
    for (split //) { # ...
    respectively.
    if ($char =~ /b|i|o/) { print "bio "; &{$char}; }
    Ouch!
    • Using symrefs is a Very Bad Thing(TM). In some cases it is necessary, but then you must really know what you're doing. It may seem safe to you to do so in this case, but if you get used to use them where they're not really needed they're likely to eventually bite you in the neck!!
    • In any case the &-form of sub call should not be used nowadays unless you know what you're doing. It most likely won't do what you think it does, see perlsub.
    elsif ($char =~ /\d/) { print "digit "; # foreach (1..$char) { &parse(substr($string, $offset+1)); +}
    Ditto as above. Also, apart that the line is commented out, do you really want to have 1..$char?!?

    Update: never mind, there's nothing wrong a priori with 1..$char above, only you now have foreach (1..$char) but you use $offset as if it were the loop iterator, in the loop.

    Well, I couldn't go past this point in your code in detail, but fundamentally it seems to me that the same cmts as above apply. I will look into the rules and maybe provide some minimal sample code as time permits. (But then I'm sure others will do!)

      Wow, thanks for the review - pointed out a lot of things I need to look over. Some of the bits I did because it's a timed exercise (working on global var + symrefs) but seems I made a lot of time-wasting mistakes as well (and just bad mistakes :D).

      As to 1..$char? iterate $char times, what's wrong with that?

      Anyway.. <runs off to improve code>

        Wow, thanks for the review - pointed out a lot of things I need to look over. Some of the bits I did because it's a timed exercise (working on global var + symrefs) but seems I made a lot of time-wasting mistakes as well (and just bad mistakes :D).
        Incidentally using a hash, e.g. a dispatch table takes only a few more efforts than symrefs.
        As to 1..$char? iterate $char times, what's wrong with that?
        You're right, my mistake, I just corrected the original post.
Re: Shuffling cards
by blazar (Canon) on Feb 22, 2005 at 15:43 UTC
    The program has to take in a string where b, i and o are functions and have coefficients and groupings such as b3(io) does b then io 3 times.
    I forgot to mention in the other reply (and it seems to me to be an important bit, so that it deserves being pinpointed separately) that if grouping is involved, and if nested groupings are allowed, then basically you have to match balanced text which is not so trivial. In this case you may either adopt a ready made solution (e.g. Text::Balanced) or roll your own...
Re: Shuffling cards
by Anonymous Monk on Feb 22, 2005 at 17:14 UTC
    This is how I would do it. My solution doesn't have the size of the deck hardcoded.
    #!/usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; use integer; sub b {@_[1 .. @_ - 1, 0]} sub o { @_[map({$_, $_ + @_ / 2 + @_ % 2} 0 .. @_ / 2 - 1), @_ % 2 ? @_ / +2 : ()] } sub i { @_[map({$_ + @_ / 2, $_} 0 .. @_ / 2 - 1), @_ % 2 ? @_ + 1 : ()] } my $bal; $bal = qr /\( [^()]* (?: (??{$bal}) [^()]* )* \)/x; sub shuffle { my ($str, @deck) = @_; 1 while $str =~ s/([0-9]+)(?:([bio])|($bal))/($2 || $3) x $1/eg; $str =~ s/[^bio]+//g; foreach my $c (split //, $str) { no strict 'refs'; @deck = &$c(@deck) } @deck; } my @deck = 1 .. 8; is_deeply([shuffle ("i2o", @deck)], [5, 6, 7, 8, 1, 2, 3, 4 +]); is_deeply([shuffle ("3(b2(oi))", @deck)], [1, 6, 3, 2, 4, 5, 7, 8 +]); is_deeply([shuffle ("b", @deck)], [2, 3, 4, 5, 6, 7, 8, 1 +]); is_deeply([shuffle ("i", @deck)], [5, 1, 6, 2, 7, 3, 8, 4 +]); is_deeply([shuffle ("o", @deck)], [1, 5, 2, 6, 3, 7, 4, 8 +]); is_deeply([shuffle ("bioib", @deck)], [6, 1, 8, 3, 2, 5, 4, 7 +]); is_deeply([shuffle ("3i", @deck)], [8, 7, 6, 5, 4, 3, 2, 1 +]); is_deeply([shuffle ("2io", @deck)], [7, 8, 5, 6, 3, 4, 1, 2 +]); is_deeply([shuffle ("8b", @deck)], [1, 2, 3, 4, 5, 6, 7, 8 +]); is_deeply([shuffle ("b3oi", @deck)], [6, 2, 7, 3, 8, 4, 1, 5 +]); is_deeply([shuffle ("2(io)", @deck)], [6, 2, 5, 1, 8, 4, 7, 3 +]); is_deeply([shuffle ("b2(ib)o", @deck)], [2, 3, 1, 6, 7, 8, 5, 4 +]); is_deeply([shuffle ("3(i2(io)o)", @deck)], [3, 4, 1, 2, 7, 8, 5, 6 +]); is_deeply([shuffle ("4(4(io)2b)", @deck)], [5, 4, 3, 6, 8, 1, 2, 7 +]); is_deeply([shuffle ("5(6(bi)7(io))", @deck)], [1, 2, 8, 4, 5, 3, 7, 6 +]); is_deeply([shuffle ("2(3(4(io)b)b)b", @deck)], [1, 6, 4, 3, 5, 2, 8, 7 +]); __END__ ok 1 ok 2 ok 3 ok 4 ok 5 ok 6 ok 7 ok 8 ok 9 ok 10 ok 11 ok 12 ok 13 ok 14 ok 15 ok 16 1..16
    The answer to question 2b is 12 17 2 7 13 18 3 8 14 19 4 9 15 20 5 10 16 1 6 11. For 2c, the answers are 3 out riffles, 6 in riffles and 8 breaks.

    The answer to 2d is "yes". It can be easily seen by constructing a graph with a nod for each possible permutation of the deck. Make a directed edge from node v to node w, if an in riffle shuffles the permutation associated with v to the permutation associated with w. Edges having the same begin and end point are fine. If repeatedly performing an in shuffle would not lead to the being situation, the graph will be without loops. But a directed graph without loops must contains sinks (nodes with incoming edges, and no outgoing edges). But a sink would mean a permutation that can't be shuffled. However, any permutation can be shuffled. Ergo, the graph must contain loops, and repeatedly shuffling will cause the begin permutation to reappear. This is true for any repeated shuffle based on a fixed permutation.

Re: Shuffling cards
by blazar (Canon) on Feb 23, 2005 at 12:55 UTC
    Hey, I'm trying to Perl some BIO past papers (a British programming contest) but I have some results that I can't explain. The program has to take in a string where b, i and o are functions and have coefficients and groupings such as b3(io) does b then io 3 times. Full explanation is here (question two):

    BIO Past Papers

    Ok, I finally checked the full explanation. Here's my sample code to solve the assignment:
    #!/usr/bin/perl -l use strict; use warnings; my @pack=1..8; my %disp; @disp{qw/b i o/}=map { my $t=$_; sub { @_[@$t] }; } [1..7,0], [4,0,5,1,6,2,7,3], [0,4,1,5,2,6,3,7]; sub parse { for (@_) { my $c; s/[()]/ $& eq '(' ? '(=' . $c++ . '=' : '=' . --$c . '=)' /ge; s/(\d)([bio])/$2 x $1/ge; 1 while s/(\d) \(=(\d+)= (.+?) =\2=\)/$3 x $1/gex; } } while (<>) { parse $_; @pack=$disp{$_}->(@pack) for /./g; print "@pack"; } __END__
    Please note that:
    1. I don't do any input validation, for the rules partly explicitly partly implicitly state that I must trust it,
    2. this is certainly not the most efficient way to do this, but it is IMHO adequate to the nature of the problem, as of the description,
    3. Also, the sub parse() is by no means necessary, but I wanted to keep it separate it from the main loop.

        HTH.

      Yours would fail if you have 10 or more nested parens.
        Yours would fail if you have 10 or more nested parens.
        It won't any more, I made the minimal correction that solved this and I hope you will apologize me for not having pointed out explicitly having done so (but it's really a one-char correction).

        Incidentally, to be fair I'm not really sure if with input strings limited to 20 chars (as of the rules!!) you can have more than 10 nested parens and thinking about it one more second... no, you can't!