Given: A list of data items of all the same type (call this @l), and a subroutine c() that takes in that data type and returns either 1 or 0 if the item should be removed or not, respectively.

Find: The perl golf lines necessary to not only produce array @r, the list of items that are removed, but to also to remove those items from @l itself (or at least, to have @l contain items from @a that were not in @r). Order must be maintained in both arrays. Assume that 'use strict' is in play, and you certainly may use additional variables. If you need a test case, use @r=(0..25), with sub c { (($_[0] % 3) == 0); } (e.g. remove multiples of 3).

Update: There's an easy way to do this by going through the list twice. However, for a harder challenge assume that the results of c() may depend on the time when processed, and thus, you only want to evaluate c() once for each item on the original list. (And thanks tye for updates.)


Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain

Replies are listed 'Best First'.
Re: Golf: List spliting
by danger (Priest) on Apr 20, 2001 at 23:50 UTC

    Well, if I understand your alternate spec, you'll accept a routine that takes an original array @a and produces two arrays holding the elements for which c() is false and the elements for which c() is true respectively. The following produces two array refs with 33 chars in the body S:

    my @a = (0 .. 25); sub c{$_[0] % 3 == 0 } sub S{my@t;push@{$t[c($_)]},$_ for@_;@t} my($l,$r)=S(@a); print "@$l\n"; print "@$r\n";

    Following tye's lead of passing in a ref to c(), it grows to 43 chars:

    my @a = (0 .. 25); sub c{$_[0] % 3 == 0 } sub S{my@t;push@{$t[$_[0]($_)]},$_ for@{$_[1]};@t} my($l,$r)=S(\&c,\@a); print "@$l\n"; print "@$r\n";

    Note, the sub deref in that one (sans ->) requires 5.6+

(tye)Re: Golf: List spliting
by tye (Sage) on Apr 20, 2001 at 21:59 UTC
    my @l= (0..25); sub c{0==$_[0]%3} sub S{my($c,$l,@r)=@_;@$l=grep&$c($_)?!push@r,$_:1,@$l;@r} my @r= S(\&c,\@l); print "\@r=(@r)\n\@l=(@l)\n"; __END__ Output: @r=(0 3 6 9 12 15 18 21 24) @l=(1 2 4 5 7 8 10 11 13 14 16 17 19 20 22 23 25)

    51 characters for the middle. I could shorten it by hard-coding &$c() as c() but that doesn't seem proper.

            - tye (but my friends call me "Tye")
Re: Golf: List spliting
by premchai21 (Curate) on Apr 21, 2001 at 00:58 UTC
    My try; slow, not very short, but interesting (uses one of my favorite modules):
    use strict; use Quantum::Superpositions qw(any); my @r = (0..25); sub c { ($_[0]%3)==0; } ## sub S{my@Q=@_;@_=grep{c($_)}@Q;@Q=grep{any(@_)ne$_}@Q;@_} ## print S(@r);
    50 chars for the sub body, 36 for the use line.
Re: Golf: List spliting
by MeowChow (Vicar) on Apr 21, 2001 at 02:05 UTC
    Do we have to use a sub?
    my@r;@l=grep{c($_)?0*push@r,$_:1}@l;
    36 total.

    blargh: never mind, I just noticed that this is essentially the same thing as Tye's, unwrapped, and with an extra character :)

       MeowChow                                   
                   s aamecha.s a..a\u$&owag.print

      Can we give &c a prototype of ($)? my@r;@l=grep c$_?!push@r,$_:1,@l; 33 characters. Update: or: my@r;@l=grep!(c$_&&push@r,$_),@l; also 33 characters. Update: or: my@r;@l=grep{!c$_||!push@r,$_}@l; also 33 characters. (and I'm having a hard time counting)

              - tye (but my friends call me "Tye")
Re: Golf: List spliting
by satchboost (Scribe) on Apr 20, 2001 at 21:46 UTC
    I may be missing something, but wouldn't the following do the job?

    my $i = 0; while ($i < @r) { if (r($r[$i]) { push @l, (splice @r, $i, 1); next; } $i++; }
      That might do it, but the idea with perl golf is to produce a solution that uses the fewest number of characters that does do the solution. Most of these tend to be 'easy' problems but the trick is to make the 'program' as short as possible.
      Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain