Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Elegantly map fizz to buzz

by oiskuu (Hermit)
on May 15, 2016 at 15:45 UTC ( [id://1163085]=perlquestion: print w/replies, xml ) Need Help??

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

Just something elementary for a lazy weekend...

An interesting link to Simple Programming Exercises came up on the chatterbox. (Thank you.) Scanning that list, something quickly caught my eye. The problem #5. "Why Hello! I see Dr.Fizz and Dr.Buzz have been making rounds all over the world," I thought to myself, and proceeded to check out the perl solution.

#! /usr/bin/perl -wl # sum of the numbers in series 1..n that are multiple of either 3 or 5 use List::Util qw(sum sum0); sub f1 { sum0 grep { $_ % 3 == 0 or $_ % 5 == 0 } 1 .. shift; } sub f2 { sum map { $_ * (map $_*($_+1)/2, int $_[0]/abs)[0] } (3, 5, -15); } # while (<>) {...} for (1 .. 23) { print join ' ', $_, f1($_), f2($_); }
There's the naive approach and a more practical variant. I tried to be concise and expressive, but two issues bother me still. Embedded use of sub arguments ($_[0]) does not look smart; this can be improved with signatures I suppose.

What of the (map ..., $x)[0] construct, however? Using (sub{ ... })->($x) is even more circuitous. Just breaking out the terms probably makes it easier to grok, despite the repetition.

sub f3 { my $n = shift; my $t3 = 3 * int($n/3) * int($n/3 + 1) / 2; my $t5 = 5 * int($n/5) * int($n/5 + 1) / 2; my $t15 = 15 * int($n/15) * int($n/15 + 1) / 2; return $t3 + $t5 - $t15; }
How would you code this routine? Especially, I'm curious what the perl6 solutions might look like. Gimmé!

ps. Incidentally, Rosetta Code has a talk page for the FizzBuzz problem. That problem appears to have a problem of getting slightly out of hand.

Replies are listed 'Best First'.
Re: Elegantly map fizz to buzz
by Laurent_R (Canon) on May 15, 2016 at 18:01 UTC
    Hi,

    this is a Perl 6 solution using the reduction metaoperator, tested in the REPL interpreter:

    > say [+] grep {$_ %% 5 or $_ %% 3}, 1..10; 33
    or, as a subroutine, tested under the REPL:
    > sub sum3_5 (Int $max) { return [+] grep {$_ %% 5 or $_ %% 3}, 1..$ma +x}; sub sum3_5 (Int $max) { #`(Sub|320422744) ... } > say sum3_5 10; 33
    Another solution, using the reduce function:
    > say reduce {$^a + $^b}, grep {$_ %% 5 or $_ %% 3}, 1..10; 33
    Update: yet another one:
    > say reduce * + *, grep {$_ %% 5 or $_ %% 3}, 1..10; 33
      With two simplifications:
      say [+] grep * %% (5|3), 1..10

      Simplification #1. The code $_ %% 5 or $_ %% 3 can use a Junction instead: $_ %% (5|3). Junctions are designed mostly with the performance boost from automatic parallelism in mind but they can also aid readability.

      Simplification #2. The compiler recognizes when a Whatever pronoun is used as an argument of an operator. For almost all operators it makes that operation its own block, replacing the Whatever with It ($_). So these are equivalent:

      say [+] grep * %% (3|5), 1..10; say [+] grep { $_ %% (3|5) }, 1..10;

        Thank you, raiph, these are good ideas.

        Using a junction is indeed a nice idea, and does improve readability, but I doubt there can be a performance boost in this specific case, because I can't see how the compiler or the VM could run this in parallel (or if it does, it would need to add a duplicate removal phase, it seems to me that this might be too much for a compiler optimization).

        As for the * whatever pronoun, I should confess that I have been trying to use it from time to time, but am still uneasy with its syntax. Essentially, I haven't really figured out how the compiler can tell the difference between * as the multiply sign and * as the whatever pronoun, so that I have been using it so far with a try and error method, rather than with a true understanding of where and how this should work.

Re: Elegantly map fizz to buzz
by choroba (Cardinal) on May 15, 2016 at 18:10 UTC
    Recursion comes to mind:
    sub f3 { no warnings 'recursion'; $_[0] * !( $_[0] % 3 && $_[0] % 5 ) + ($_[0] && f3($_[0] - 1)) }

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
      > Recursion comes to mind:

      Obfuscation, too! ;-b

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

Re: Elegantly map fizz to buzz
by BrowserUk (Patriarch) on May 15, 2016 at 19:38 UTC

    Look Ma, no modules :)

    sub fzbz{ my$n; $_%3 && $_%5 or $n+=$_ for 1..pop; $n };; print fzbz(10);; 33 print fzbz(100);; 2418 print fzbz(1000);; 234168

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I knew I was on the right track :)
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Elegantly map fizz to buzz
by BrowserUk (Patriarch) on May 15, 2016 at 16:06 UTC

    See also The FizzBuzz Thing :(


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I knew I was on the right track :)
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Elegantly map fizz to buzz -- oneliner evil numbers and binary weight
by Discipulus (Canon) on May 15, 2016 at 21:36 UTC
    UPDATE while my solution works for the given max number (10), is utterly wrong for higher numbers.

    this is because multiples of 3 and 5 coincides with oeis serie A001969 just until 17 which is a prime number (as many in the serie) so cannot be multiple of 3 nor 5 (but i learned about binary weight..).

    pardon me, was late and i was searching some oneliner to write while waiting sleep coming upon me..

    what follow is original post:


    not so elegantly, but here my oneliner solution:

    perl -e "print eval join '+',grep{((scalar @{[((sprintf '%b',$_)=~/1/g +)]}))%2==0} 0..$ARGV[0];" 10 33
    is the shortened version of:
    perl -e "print eval join '+',grep{(0.5*(4*$_+1-(-1)**scalar @{[((sprin +tf '%b',$_)=~/1/g)]}))%2==0} 0..$ARGV[0];" 10 33

    The solution is based based on the fact that 0, 3, 5, 6, 9, 10, 12,.. is also the oeis serie A001969.

    Here my first attempt

    use strict; use warnings; my $max = $ARGV[0]||10; my $total; my $num=0; while ( evil_numbers($num)<= $max) {$total += evil_numbers($num);$num+ ++} print "$max gives a total of $total\n\n"; sub evil_numbers{ # https://oeis.org/A001969 my $n = shift; # nasty binary weight: https://oeis.o +rg/A000120 return 0.5 * (4 * $n + 1 - (-1) **scalar @{[((sprintf '%b',$n)=~ +/1/g)]} ); }

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Elegantly map fizz to buzz
by Anonymous Monk on May 16, 2016 at 00:07 UTC
    What of the (map ..., $x)[0] construct, however?
    If map doesn't work, we can try reduce :)
    use feature 'say'; use List::Util qw( product reduce ); sub f4 { my $n = shift; return reduce { my $x = int( $n / abs($b) ); $a + $b * $x * ($x + 1) / 2; } 0, @_, -product @_; } say f4( 10, 3, 5 ); say f4( 100, 3, 5 ); say f4( 1000, 3, 5 );

      Hi, I see you've attempted to generalize the problem by passing the moduli along as arguments. However, this complicates the matters considerably. Numbers multiple of some value form a set; all possible intersections of those form a powerset. E.g. for (3, 5, 7) you have the 305071, 305170, ..., 315171 subsets to consider.

      Here's my attempt at a generic version. I'm not quite sure if it's sound in principle. (Now where are the resident mathematicians?)

      #! /usr/bin/perl -wl use feature qw( signatures ); no warnings qw( experimental::signatures ); use List::Util qw( sum0 product reduce any ); sub gcd($a, $b) { !$b ? $a : gcd($b, $a % $b) } sub lcm { reduce { $a * $b / gcd($a, $b) } @_ } sub fk1($max, @A) { sum0 grep { my $x = $_; any {$x % $_ == 0} @A } 1 .. $max } sub fk2($max, @A) { sum0 map { (parity($_) || -1) * sum_every_nth($max, lcm_select($_, @A)) } 1 .. (1<<@A)-1 } sub sum_every_nth($max, $n) { int($max/$n) * int($max/$n + 1) / 2 * $n } sub lcm_select($sel, @A) { lcm @A[grep {$sel>>$_ & 1} 0..$#A] } sub parity { unpack "%1b*", pack "J", shift } my @A = (3, 5, 74017, 74027, 74047, 74077); print join ' ', 0+$_, fk1($_, @A), fk2($_, @A) while <>;
      Again, the naive version and a smarter one. Subseries are summed with alternating signs according to cardinality: that way each number is tallied exactly once in the end.

      Where the guarantee is given that moduli are mutually prime, one can use product() instead of lcm(). Then, if the limit $max is much greater than lcm(@A), one may reduce it first: e.g. with lcm(3, 5) you have a wheel15 going on. Etc. Talk about trivial exercises...

        to generalize the problem by passing the moduli along as arguments.

        If I've understood the task correctly, I think you're working the math too hard:

        sub x{ my $v=shift; my $t=0; $t += !!($v%$_) for @_; return $t!=@_ };; sub fn{ my$n; x($_, @_) and $n+=$_ for 1 .. shift; $n };; print fn( 10, 3, 5 );; 33 print fn( 10, 3, 5, 7 );; 40 print fn( 100000, 3, 5, 7, 74017, 74027, 74047, 74077 );; 2714660445

        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". I knew I was on the right track :)
        In the absence of evidence, opinion is indistinguishable from prejudice.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1163085]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2024-04-19 20:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found