Front page | perl.fwp |
Postings from January 2002
Re: even.pl solutions
Thread Previous
|
Thread Next
From:
fun_with_perl
Date:
January 30, 2002 12:14
Subject:
Re: even.pl solutions
Message ID:
a39k2j$31u$1@post.home.lunix
In article <694BB7191495D51183A9005004C0B05452DBB0@ir-exchange-srv.ir.com.au>,
Andrew.Savige@ir.com writes:
> Ton, if you can find the time, I would really appreciate a little
> write-up on your unorthodoxy too. How come you knew about it,
> why did you choose it instead of $. in your solution, and so on.
>
The thinking, heh ? Ok.
When i hear even/odd, I directly think about the last digit in a binary
representation. You can use "&" between terms to demand every last digit
is 1, and "|" between terms to demand every last digit is 0.
For counting characters the "tr" operator is well known, and can be written
even shorter as "y". You can make several variations to switch between even
and odd results too: put "~" in front to toggle the bits, put "c" behind to
count the other set of chars, put a "\n" inside the body to get one extra
or add a "-l" on the commandline to get an automatic chop.
Then there is the even/odd linenumber. Again there are many ways to do that.
You can try to use the scalar ".." operator to flip-flop (everything I tried
there was too long), use -aF.* and pick up the lines from @F (long !), read
<> once more inside the loop (unfortunately that causes the loop to restart
reading from STDIN if the number of lines is odd), and of course there is $.
and the bit-flipped version ~$.
And less well known, there is $|-- and its opposite --$|. I discovered these
once by accident when i tried to always print a line flushed while keeping
$| to it's old value and not using a block with local:
$|++;
print "whatever";
$|--;
This turned out to NOT work, and while investigating why, i saw that $| can
only be 0 or 1, which made me realise that -1 will become 1, and that you can
use --$| as a flip-flop.
$|-- and --$| have one big advantage over $. and ~$. which is that they are
already 0 or 1 and to get the last digit of the binary expansion, at the end
a "&" with 1 will have to happen. $| can directly play the role of this "1".
(the "&" with 1 is also the reason that combining with "|" tends to be a bit
long, since it needs () around the expression because "&" has higher priority).
Another nice thing is that you can use them in something like "for<>", because
they don't depend on the current linenumber (all reading is finished by the
time the for body gets its turn).
The next step was to combine it all into a program. Since y/// does not do
variable interpolation, it will need an eval. And the string to be evalled
can probably best be constructed instead of written since the target expression
is quite long and very repetitive. Two obvious ways of constructing are s///
and array substitution in a string after setting $, (i never knew about the
glob trick). So my first attempt was:
-nl ($a=aeiouy)=~s!.*?!|y/$&//c&1|!g;eval"--\$|$a|print"
The eval is applied on:
--$||y///c&1||y/a//c&1||y///c&1||y/e//c&1||y///c&1||y/i//c&1||y///c&1||y/o//c&1||y///c&1||y/u//c&1||y///c&1||y/y//c&1||y///c&1||print
obviously this is way too long. One of the reasons is that I had to write the
$&, so i decided to instead try to put the y/// "inbetween" the characters
instead of around. look at the output of this:
perl -le '$_=aeiouy;s!!//&y/!g;print'
//&y/a//&y/e//&y/i//&y/o//&y/u//&y/y//&y/
So now all the remains to be done is to somehow use the // you get at the
start and the y/ at the end. and there are two missing jobs: skipping lines
and counting the total length. Also i'm free to use lots of other chars than
/ if that happens to be useful for the thing i'm going to add.
At this point it becomes a question of combining all variations mentioned
above to get to a short solution. I found nothing shorter than these two:
-ln ($a=aeiouy)=~s!!--&~y-!g;eval"\$|$a--c"&&print
-n ($a=aeiouy)=~s!!//c&y/!g;eval"~$.&$a//c"&&print
The first one is nice in that it uses the -- at the start to attach to the $|
to get $|--, the second is nice in that it uses the //c at the start as the
"1" with which we need to "&" anyways. Since they were equal length, i sent
the first one, because I thought it looked weirder.
Two things are pretty frustrating about this solution:
1) It doesn't use the "print" you can get for free with -p
Things like "next" also don't work, since the -p loop has that
annoying implied "continue". The best I could think of was conditionally
squashing $_ to "". So i went several times through perlfunc, but found
nothing useful. I completely forgot to go through perlop, where I might
have found that $_ x= :-(
2) It would be so much nicer if I could use $_ instead of $a because it
would allow to start to be three chars shorter:
$_=aeiouy;s!!//c&y/!g
the closest I got with that idea is:
$_=aeiouy;s!!--&~y-!g;print eval"grep\$|$_\n--c,<>"
(hard newline), which is again 50 chars.
And while writing this text, i in fact found:
$_=aeiouy;s!!\n\$|--&y-!g;print eval"grep$_--c,<>"
and
-n ($a=aeiouy)=~s!!\n\$|--&y-!g;eval"$a--c"&&print
(hard newline) which are 49 (damnit).
Thread Previous
|
Thread Next