Colin Crain › Perl Weekly Review #153

Monday, Mar 28, 2022| Tags: perl

( …continues from previous week. )

Welcome to the Perl review pages for Week 153 of The Weekly Challenge! Here we will take the time to discuss the submissions offered up by the team, factor out some common methodologies that came up in those solutions, and highlight some of the unique approaches and unusual code created.


●︎ Why do we do these challenges?

I suppose any reasonable answer to that question would be from a field as wide ranging and varied as the people who choose to join the team. One thing, though, is clear: it’s not a competition, and there are no judges, even if there is a “prize” of sorts. About that – I think of it more as an honorarium periodically awarded to acknowledge the efforts we make towards this strange goal. So there’s no determination to find the fastest, or the shortest, or even, in some abstract way, the best way to go about things, although I’m certain the participants have their own aspirations and personal drives. As Perl is such a wonderfully expressive language, this provides quite a bit of fodder to the core idea of TMTOWTDI, producing a gamut of varied techniques and solutions.

Even the tasks themselves are often open to a certain amount of discretionary interpretation. What we end up with is a situation where each participant is producing something in the manner they find the most interesting or satisfying. Some team members focus on carefully crafted complete applications, thoroughly vetting input data and handling every use case they can think up. Others choose to apply themselves to the logic of the underlying puzzle and making it work in the most elegant way they can. Some eschew modules they would ordinarily reach for, others embrace them, bringing to light wheels perhaps invented years ago that happen to exactly solve the problem in front of them today.

I’ve been considering this question for some time and have found one binding commonality between all of us out solving these challenges each week, in that however we normally live our lives, the task in front of us more than likely has nothing to do with any of that. And I think this has great value. We all do what we do, in the real world, and hopefully we do it well. The Weekly Challenge provides us with an opportunity to do something germane to that life yet distinctly different; if we only do the things we already know how to do then we will only do the same things over and over. This is where the “challenge” aspect comes into play.

So we can consider The Weekly Challenge as providing a problem space outside of our comfort zone, as far out from that comfort as we wish to take things. From those reaches we can gather and learn things, pick and choose and bring what we want back into our lives. Personally, I think that’s what this whole thing is about. YMMV.


Every week there is an enormous global collective effort made by the team, analyzing and creatively coding the submissions, and that effort deserves credit due.

And that, my friends, is why I’m here, to try and figure out ways to do just that.

So, here we are then. I’m ready — let’s get to it and see what we can find.


For Additional Context…

before we begin, you may wish to revisit either the pages for the original tasks or the summary recap of the challenge. But don’t worry about it, the challenge text will be repeated and presented as we progress from task to task.

Oh, and one more thing before we get started:

Getting in Touch with Us

Email › Please feel free to email me (Colin) with any feedback, notes, clarifications or whatnot about this review.

GitHub › Submit a pull request to us for any issues you may find with this page.

Twitter › Join the discussion on Twitter!

I’m always curious as to what the people think of these efforts. Everyone here at the PWC would like to hear any feedback you’d like to give.

...So finally, without further ado...

•       Task 1       •       Task 2       •       BLOGS       •



TASK 1

Left Factorials

Submitted by: Mohammad S Anwar

Write a script to compute Left Factorials of 1 to 10. Please refer OEIS A003422 for more information.

    Expected Output:
    1, 2, 4, 10, 34, 154, 874, 5914, 46234, 409114

about the solutions

Abigail, Adam Russell, Alexander Pankoff, Athanasius, Bruce Gray, Colin Crain, Dave Jacoby, Duncan C. White, E. Choroba, Flavio Poletti, Jorg Sommrey, Laurent Rosenfeld, Lubos Kolouch, Matthew Neleigh, Mohammad S Anwar, Pete Houston, Peter Campbell Smith, PokGoPun, Robert DiCicco, Roger Bell_West, Ulrich Rieke, and W. Luis Mochan

When I first approached this problem, I had a little trouble visualizing exactly what it was about. After a few missteps, and a bit of additional confusion thrown on top over a conflicting and separate definition for a different concept using identical nomenclature, I ended up with my own rephrasing of the definition:

Consider the factorials as an ordered sequence of values starting at 0 and incrementing by 1. The Left Factorial is the sum of all values in this sequence for indices less than, but not including, the value in question.

So L(4) = 3! + 2! + 1! + 0!

The left factorial, then, for a given value is the sum of those factorials less than the given index.

On one level, we can compute the solutions for each of these relevant values and then add them up. However, the factorials, taken as a list, maintain relations to each other, with each increasing index adding one additional multiplicand to the growing value of the new factorial added. So there’s room for more clever algorithms than this naive example.

There were 22 submissions for the first task this past week.

there’s MORE than ONE FACTOR to consider…

Colin Crain, Matthew Neleigh, Roger Bell_West, Bruce Gray, Abigail, Mohammad S Anwar, Lubos Kolouch, W. Luis Mochan, Adam Russell, PokGoPun, and Jorg Sommrey

There were quite a range of techniques brought to bear on the problem, however the most common, economical, approaches were to capitalize on the computation of previous elements in producing the next, as each can be developed into the following in a consistent way. Sometimes the production of values was embedded in the idea of producing the sequence, but we also saw structural separation, with the use of caches to avoid computational repetition. Of course the repetion itself is hardly the end of the world, amounting to hundred of operations rather than millions of billions, so the shortest solutions just recomputed whatever was required.

Colin Crain

  additional languages: Raku

  blog writeup: Facts Left on the Table by the Front Door - Programming Excursions in Perl and Raku

We’ll start us off today with my own submission. I make a random selection and it stands to reason that my number will come up sometimes.

I do my best not to play favorites. I do retain editorial discretion, but I also try to restrict my power to the service of the narrative. Keeping you engaged is, after all, the tricky bit.

So what did I do? As touched on in the introduction, each new element of the sequence adds one more factorial than the previous, and each new factorial added is the product of the previous factorial and the next higher number. So to compute the next element in the sequence, we need to keep track of only three numbers:

  • the last value in our growing left factorial sequence
  • the value of last factorial we calculated, and
  • the base of the last factorial we calculated

At each step, working backwards up this list, we:

  • increment the base by one
  • multiply the factorial by that to make the next factorial
  • add this new factorial to the last list value to create the new element

By keeping these values properly updated we can keep cranking out as many left factorials as we want. It’s fun, for some definitions of fun, so I kept on going. The advantage of doing things this way is that each new element requires just a couple of additions and a product; we don’t keep recomputing the individual factorials over and over.

One last note is because the factorials grow rapidly, we run out of integer at

! 22 = 2,561,327,494,111,820,314

However because we keep our step-wise computational footprint small, applying the bigint pragma to allow ourselves arbitrarily-sized integers has no great impact on the processing resources. We can effectively go as far as we want:

! 1000 = 402,790,454, 129,684,136,968,124,369,179,059,211,066,230,340, 814,333,875,534,732,490,137,389,971,453,530,257, 834,729,524,153,470,039,842,675,785,530,571,260, 496,816,973,897,917,840,067,466,731,231,090,427, 980,441,039,328,369,617,046,320,988,822,321,806, 331,297,934,459,170,543,661,198,500,120,585,035, 795,085,833,286,238,420,519,712,073,583,879,282, 425,846,829,094,851,665,019,998,848,816,833,714, 219,704,581,783,936,354,401,378,148,203,630,105, 398,875,606,581,189,836,855,161,843,667,260,953, 503,142,584,307,135,040,357,121,277,200,535,303, 489,725,839,621,761,048,442,907,072,978,722,502, 368,306,412,562,875,842,972,049,339,112,885,444, 775,909,503,613,257,263,083,390,264,455,593,922, 422,529,369,431,118,032,631,742,558,720,263,660, 260,357,267,951,709,605,570,340,190,346,083,442, 302,480,141,979,187,310,960,364,916,499,234,307, 663,742,207,632,736,413,034,131,830,360,882,630, 789,606,151,000,253,995,702,701,472,413,540,755, 707,390,554,304,190,944,220,951,290,300,194,050, 675,762,996,631,830,911,183,977,312,364,968,063, 270,503,097,427,155,877,640,454,977,001,850,018, 498,051,740,860,804,843,264,282,030,032,906,924, 527,877,925,200,098,028,474,723,686,264,644,145, 999,968,150,928,496,710,489,938,749,063,992,449, 766,833,677,942,475,821,619,414,972,298,916,924, 755,307,573,879,667,860,841,873,351,869,436,978, 785,662,395,165,750,834,518,536,598,933,216,551, 301,901,613,701,145,508,439,007,793,653,521,743, 174,685,254,160,699,597,470,819,070,584,174,650, 419,573,045,629,293,641,455,300,174,518,442,106, 252,839,984,409,630,689,586,721,196,818,371,427, 766,601,460,561,794,428,078,429,673,965,172,051, 532,376,738,477,699,052,199,414,493,694,104,140, 984,140,780,001,899,010,983,640,712,852,791,596, 815,502,175,604,362,639,462,613,053,790,808,551, 489,499,619,991,171,417,679,593,843,139,520,499, 236,340,414,154,312,436,880,445,631,367,852,091, 082,350,869,211,432,218,341,643,661,879,013,695, 558,309,745,157,747,990,568,149,431,396,636,644, 196,097,386,573,880,427,460,775,937,151,813,397, 945,493,074,474,467,361,685,630,041,394,331,320, 075,100,891,309,427,960,687,594,226,934,752,166, 115,243,023,149,507,421,484,619,854,636,959,105, 831,992,796,845,068,625,788,389,433,894,561,761, 146,077,133,713,010,208,565,214,943,081,088,637, 001,250,239,177,758,196,947,672,518,770,799,530, 274,826,787,510,343,915,001,221,410,554,763,665, 639,196,205,421,250,142,581,238,453,883,141,625, 528,016,903,824,365,818,537,640,384,724,208,223, 295,526,314,020,589,045,966,038,090,575,553,724, 635,424,608,550,152,158,398,375,634,012,908,507, 702,136,635,597,449,455,843,933,079,728,415,267, 858,875,601,801,690,397,170,990,588,719,648,531, 409,285,603,238,091,779,379,999,615,606,611,107, 074,183,840,614,496,825,002,697,607,509,211,067, 748,675,665,301,202,497,846,647,311,580,285,573, 814,429,018,177,249,160,844,484,259,841,012,592, 312,754,189,406,241,246,434,924,328,621,494,682, 022,198,137,725,427,915,769,382,167,943,955,038, 243,392,710,404,246,305,525,277,498,873,440,262, 772,727,601,626,407,542,504,985,981,593,086,850, 310,175,052,532,805,418,564,448,235,764,619,186, 683,198,802,218,861,893,243,574,080,688,558,756, 417,455,613,570,273,121,541,638,995,145,894,561, 079,999,213,500,076,101,248,781,500,642,979,091, 198,044,736,325,729,107,144,579,768,273,776,743, 716,439,557,581,557,253,878,204,647,391,176,582, 851,167,312,182,026,188,795,156,620,056,856,503, 340,092,247,479,478,684,738,621,107,994,804,323, 593,105,039,052,556,442,336,528,920,420,940,314

You know, if we wanted to know that.

    use bigint;

    my $num   = shift // 50;

    my @left  = (0,1);
    my $fact  = 1;
    my $count = 1;

    while ($count < $num) {
        $fact *= $count;
        push @left, $left[-1] + $fact;
        $count++;
    }

    say for @left;

Matthew Neleigh

Because the sum for an element in the sequence is those factorials less than but not equal to the value, at any given moment we’re computing the factorial one behind the current index, constructed out of the factorial two behind and a counter one behind. The bookkeeping is not hard but can be a little confusing before you get it right. So in Matthew’s code the counter $i is not the index for the left factorial, but rather that of the new factorial being added, one behind.

    sub n_left_factorials{
        my $n = int(shift());

        return(undef)
            unless($n > 0);

        my $i;
        my $factorial;
        my @left_factorials;

        # Initial setup

        push(@left_factorials, 1);
        $factorial = 1;

        for $i (1 .. ($n - 1)){
            # Multiply our running product by $i, then

            # store its sum with the previous sum

            $factorial *= $i;
            push(
                @left_factorials,
                $left_factorials[$#left_factorials] + $factorial
            );
        }

        return(@left_factorials);

    }

Roger Bell_West

  additional languages: Javascript, Kotlin, Lua, Postscript, Python, Raku, Ruby, Rust

  blog writeup: RogerBW’s Blog: The Weekly Challenge 153: Factoriality

Roger is a much more one-action-per-line kind of guy, and I respect that. The drama progreses downward in an unhurried style that also carries little fat to slow it down. Again we see the three variables: an iterator, the latest factorial, and a compounding sum which is the last value added to the left factorial list.

New values are simply pushed onto the list when constructed.

    sub leftfactorial {
      my $mx = shift;
      my @out;
      my $fact=1;
      my $sum=0;
      foreach my $i (0..$mx-1) {
        if ($i>0) {
          $fact *= $i;
        }
        $sum += $fact;
        push @out,$sum;
      }
      return \@out;
    }

Bruce Gray

  additional languages: Raku, Webassembly

Bruce has done something here that quite frankly I didn’t know was even possible from a core module.

In functional programming, there is a basic operation on a list called reduction. In it, much like map, we take a function and a list and apply the function one-by-one to the list elements. In reduce, however, the process is structured in such a way that each newly processed element is incorporated by the function into a common aggregate total, which is returned at the end as a single value. The familiar function sum is like this, for instance, where we can view the process as taking the first list element and then adding each remaining element to it, one-by-one, until when we arrive at the end of the list we end up with a summation of all the values within it.

The reduce function, sometimes called fold or some variant, can be considered an archetype for that sort of stepwise list processing, turning a list into a single value. By careful selection of the applied function, one can, as we just described, construct a perfectly-good sum — or product, minimum or concatenation for that matter. The sky is the limit.

There is a large collection of such functions in the List::Util module, starting with the prototype reduce.

However there is a slight variation on this processing available as well, the function reductions which acts in the same way, but instead of returning a single value, returns a list of the the accumulating values at intermediate steps along the way. This reductions function, which in Raku is known as the “triangle reduction operator”, is what I didn’t realize we had available.

So in our sum example, for a list (1, 2, 3, 4) we would return the list (1, 3, 6, 10), which is composed piecemeal as ((1), ((1) + 2), ((1+2) + 3), ((1+2+3) + 4)). Now you can start to see where I’m going with all this backstory. If we use multiplication instead of addition, then that list of partial products looks a whole lot like a list of factorials. And if we take that list and make a similar list of partial sums on that, we’re summing those factorials, adding one more value at each step.

Which is exactly what we want to do.

Check this out:

    use List::Util qw<reductions>;

    say join ', ', reductions { $a + $b }
                1, reductions { $a * $b } 1..9;

And that, my friends, is a piece of elegant functional programming.

Abigail

  additional languages: Awk, Bash, Basic, Bc, Befunge-93, C, Cobol, Csh, Erlang, Forth, Fortran, Go, Java, Lua, M4, Mmix, Node, Ocaml, Pascal, Php, Postscript, Python, R, Rexx, Ruby, Scheme, Sed, Sql, Tcl

We’ll follow that hit with a piece of astonishing brevity from Abigail.

Abigail has an uncanny ability to strip away artifice to reveal the essential logical core required to implement a solution. Here they use the fact that assignments return the thing being assigned to chain several operations.

A factorial is multiplied by an iterator value, which is returned to be added to a compounding sum which is the current left factorial to be printed. Progressing through a for loop produces the required number of values, with a trailing newline to finish the report.

    print   my $sum  = my $fac  = 1;
    print ' ', $sum +=    $fac *= $_ for 1 .. 9;
    print "\n";

Mohammad S Anwar

As clever as these acts of technical virtuosity are, there is a competing force, a drive to return to Earth and clearly present well-grounded logic in a clear framework. Two loops and a linear data flow do the job quite well.

Here’s Mohammad’s no-frills method:

    sub left_factorials {
        my ($i, $j) = @_;

        my $lf = [foo];
        foreach my $n ($i .. $j) {
            my $s = 0;
            foreach my $n (0 .. $n) {
                my $f = 1;
                $f = $f * $_ for 1..$n;
                $s = $s + $f;
            }

            push @$lf, $s;
        }

        return $lf;
    }

Lubos Kolouch

  additional languages: Php, Python

Lubos assembles each left factorial independently, but employs a cache of hashed factorial values to avoid repeating the computations.

Practically there is little difference in the processing, with only a variation in how exactly and where the intermediate data steps are stored.

    sub calculate_factorial {
        my $what = shift;

        return $fact_cache{$what} if $fact_cache{$what};

        # let's utilize the fact that we are processing the numbers in sequence

        $fact_cache{$what} = $what * $fact_cache{$what-1};

        return $fact_cache{$what};
    }

    sub calculate_left_fact {
        my $what = shift;

        return $left_fact_cache{$what} if $left_fact_cache{$what};

        # let's utilize the fact that we are processing the numbers in sequence

        $left_fact_cache{$what} = calculate_factorial($what-1) + $left_fact_cache{$what-1};

        return $left_fact_cache{$what};
    }

    sub get_left_factorials {
        my $what = shift;

        my @output;

        for (1..$what) {
           push @output, calculate_left_fact($_);
        }

        return \@output;
    }

W. Luis Mochan

  blog writeup: Perl Weekly Challenge 153 – W. Luis Mochán

Luis employs a similar organizational technique, constructing the next left factorial from the previous, and using the Memoize module to cache the return values from the two subroutines to avoid recalculating values when possible.

Memoize will remember the parameters in a subroutine call, and if it is called a second time with the same will simply return the same value without re-executing the code.

    use Memoize;
    use bigint;
    use Text::Wrap qw(wrap $columns $break);

    memoize qw(left_factorial factorial);
    die "Usage: ./ch-1.pl N\nto get the first N left factorials" unless @ARGV;
    my $N=shift;
    $columns=62; $break=qr/\s/;
    say wrap("", "    ", "The first $N left factorials are: ",
        join ", ", map {left_factorial($_)} (0..$N-1));
    sub left_factorial{
        my $n=shift;
        return factorial(0) if $n<=0;
        return factorial($n)+left_factorial($n-1);
    }
    sub factorial{
        my $k=shift;
        return 1 if $k<=0;
        return $k*factorial($k-1);
    }

Adam Russell

  blog writeup: Finding the Factorials and Factorions That Are Left

Adam has dredged up a very interesting sieve technique to produce the left factorials which is… different. The upper bound, he notes, is arbitrary and large enough to get the job done.

    use POSIX;
    use constant UPPER_BOUND => INT_MAX/1000;

    sub left_factorials_sieve{
        my($n) = @_;
        my @sieve = (0 .. UPPER_BOUND);
        my $x = 2;
        {
            my @sieve_indices = grep { $_ <= $x || $_ % $x == 0 } 0 .. @sieve - 1;
            @sieve = map{ $sieve[$_] } @sieve_indices;
            $x++;
            redo if $x <= $n;
        }
        return @sieve[1 .. @sieve - 1];
    }

    MAIN:{
        print join(", ", left_factorials_sieve(10)) . "\n";
    }

PokGoPun

PokeGoPun wisely points out the confusing status of having competing definitions in the real-world namespace for “left factorial” — the other being for an unrelated idea also known as the subfactorial, or derangement. But having cleared that up presents a quite unusual method for creating a string for the multiple products to manufacture a given factorial and then applying eval to treat the string as code. There truly is more than one way to do it, and this certainly is one of them.

    foreach my $n (1..10) {
        ### Left factorial which is known by the other name as subfactorial but it does not produce output similar to the example

        #$res .= sprintf "%s, ", eval(join(" + ", map{ &factorial($n) / &factorial($_) * (-1)**$_ } 0..$n ));

        ### The below seems to produce output similar to the example

        $res .= sprintf "%s, ", eval( join( " + ", map{&factorial($_)} 0..$n-1 ) );
    }
    $res =~ s/\D+$//;
    printf "%s\n", $res;

    sub factorial {
        my $n = shift;
        return $n==0 ? 1 : eval(join(" * ", 1..$n));
    }

Jorg Sommrey

Finally, we’ll wrap up with a strange little companion courtesy of Jorg.

Coroutines are a bit like subroutines you can step in an out of, preserving their own independant state whilst they patiently wait for you to require the next thing from them. In this code the generator coderef is returned like a closed iterator and on first calling execution travels to the first yield statement, where the preloaded first value for $left-factorial is returned, along with passing execution back to the main script. On the second and subsequent calls the coroutine picks up where it left off before, inside the while loop. Because we yield from inside the loop, when we return the next time it’s called we’re still in the loop, and will remain there on all subsequent calls. Each time we come back a new left factorial is assembled and returned.

These coroutines seem terribly interesting, but require thinking about processing in a very different way. They do not, for instance, run in on different processors, merely handing off processing from one execution space to another and back. Ultimately it might be nice to be able to park them over in a separate processor, sure, but again the that isn’t the central idea: execution is linear, but multiple states of separate processes maintain themselves independently and simultaneously.

    use bigint;
    use Coro::Generator;

    main: {
        my $left_factorial = gen_left_factorial();
        say $left_factorial->() for 1 .. $ARGV[0];
    }

    # Build a generator for left factorials

    sub gen_left_factorial {
        my ($index, $factorial, $left_factorial) = (0, 1, 1);

        generator {
            yield $left_factorial;
            yield $left_factorial += ($factorial *= ++$index) while 1;
        }
    }

Blogs and Additional Submissions in Guest Languages for Task 1:

Alexander Pankoff

  additional languages: Haskell, Raku

  blog writeup: Challenge 153 Task #1 - Factorials left, factorials right, factorials everywhere!

Athanasius

  additional languages: Raku

Dave Jacoby

  blog writeup: Luck is not a Factor!: Weekly Challenge #153 | Committed to Memory

Flavio Poletti

  additional languages: Raku

  blog writeup: PWC153 - Left Factorials - ETOOBUSY

Laurent Rosenfeld

  additional languages: Awk, Bc, C, Go, Julia, Kotlin, Lua, Pascal, Python, R, Raku, Ring, Ruby, Rust, Scala, Tcl

  blog writeup: Perl Weekly Challenge 153: Left Factorials and Factorions

Peter Campbell Smith

  blog writeup: !Task one is quite easy. Factorions are quite rare.

Robert DiCicco

  additional languages: Raku, Ruby, Tcl

Ulrich Rieke

  additional languages: C++, Haskell, Raku



TASK 2

Factorions

Submitted by: Mohammad S Anwar You are given an integer, $n.

Write a script to figure out if the given integer is factorion.

A factorion is a natural number that equals the sum of the factorials of its digits.

Example 1:

    Input: $n = 145
    Output: 1
    Since 1! + 4! + 5! => 1 + 24 + 120 = 145

Example 2:

    Input: $n = 123
    Output: 0
    Since 1! + 2! + 3! => 1 + 2 + 6 <> 123

about the solutions

Abigail, Adam Russell, Alexander Pankoff, Athanasius, Bruce Gray, Colin Crain, Dave Jacoby, Duncan C. White, E. Choroba, Flavio Poletti, Jorg Sommrey, Laurent Rosenfeld, Lubos Kolouch, Matthew Neleigh, Mohammad S Anwar, Olivier Delouya, Pete Houston, Peter Campbell Smith, PokGoPun, Robert DiCicco, Roger Bell_West, Ulrich Rieke, and W. Luis Mochan

The factorions are defined as the numbers equal to the sum of the factorials of their individual digits. As we’re talking about base-10 here — Wait, we are talking about base-10 here, aren’t we? We could extend the concept in other bases, but there is no suggestion that we do so in the task description — then the largest factorial we will be dealing with is for the digit 9.

9! = 362,880

This suggests there is an upper limit on the possible values for a factorion in base-10: there can be no factorials above 7 digits because the smallest 8-digit number — 10,000,000 — is larger than the factorial-digit-sum of the largest 8-digit number, 99,999,999.

9! + 9! + 9! + 9! + 9! + 9! + 9! + 9! = 2,903,040

So we can definitively say above 10 million we can stop looking, and in fact we can further modify this bound downward, as we shall see.

There were a total of 23 submissions for the second task this past week.

SEARCHING HAYSTACKS for NEEDLES

Robert DiCicco, Flavio Poletti, Athanasius, Laurent Rosenfeld, Alexander Pankoff, Dave Jacoby, Pete Houston, E. Choroba, Jorg Sommrey, and Peter Campbell Smith

There does not seem to be any way to predict the locations of the factorions, so it appears the only thing to do is break apart the candidate and sum up the parts as specified to see what happens. Call it experimental math. There was a large degree of self-similarity in the techniques.

Robert DiCicco

  additional languages: Raku, Ruby, Tcl

Robert will start us off today with a verbose solution of the the task as given. A number is input, broken into its digital components, the factorials taken with the help of the factorial function from ntheory, and summed. A comparison is made to the input and reported. Along the way, the various components of the process are collected to make a proper explanation of the reasoning, analogous to the examples given in the description.

    Input $n = 145
    1! + 4! + 5!  = 145
    Output: 1

Robert’s solution:

    my $ret = is_factorion($num);
    chop $outstr for 1..2;

    print "$outstr = $sumdigits\n";
    print "Output: $ret\n";

    sub is_factorion {
      my $n = shift;
      my @digits = split(//,$n);
      foreach my $x (@digits){

          $outstr .= "$x\! + ";
          $sumdigits += factorial($x);

      }

      ($sumdigits == $n) ?  1 :  0;
    }

Flavio Poletti

  additional languages: Raku

  blog writeup: PWC153 - Factorions - ETOOBUSY

Flavio provides two solutions to the task. Or rather, he provides a perfectly good solution to the task at hand — a simple query — and then goes on to expand this technique to search for all of the factorions.

    say is_factorion(shift // 145) ? 1 : 0;

    sub is_factorion ($n) {
       state $f = [ 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 ];
       $n == sum map { $f->[$_] } split m{}mxs, $n;
    }

This will satisfy the conditions of the challenge. Because there are only 10 factorials in play, he hard codes them into a list, and can access them by index. Reading from the right leftward, the number is split, mapped to its factorial, summed with the rest of the digit results, and finally compared to the original value, with the result of the comparison returned.

Neat.

But unsatisfied he goes on the find the rest. In his analysis in the writeup, he comes to the same conclusions I did above, narrowed down even more: that if the largest 7-digit value is 2540160 then no number larger need ever be considered. So he sets up a loop and tries them all. Computationally a few million values is peanuts in this modern world.

Athanasius

  additional languages: Raku

The monk also chooses a lookup for their 10 factorials, this time as a hash of constants, courtesy of Const::Fast. The values along the way are also, governed by a $VERBOSE switch, gathered and reported as in the examples given.

    printf "Input:  \$n = %d\n", $n_abs * $sign;

    my @digits   = split '', $n_abs;
    my $sum      = 0;
       $sum     += $_ for map { $FACTORIAL{ $_ } } @digits;
    my $is_factn = $sum == $n;

    printf "Output: %d\n", $is_factn ? 1 : 0;

Laurent Rosenfeld

  additional languages: Awk, Bc, C, Julia, Kotlin, Lua, Python, Raku, Ring, Ruby, Scala

  blog writeup: Perl Weekly Challenge 153: Left Factorials and Factorions

I like that Laurent went through the trouble of actually computing the factorials from 0 to 9 before caching the results in a lookup array. From a programing point of view this is not a best practice, but then from a programming point of view neither is recomputing the search for the factorions in the first place. Here at the challenge we live in a strange world not-quite grounded in everyday reality. Sometimes the constructs matter more than the particulars, as is the case here. We will choose to pretend, just for the moment, that we don’t just know the values for the factorials or have them easily available. However once computed we will make it so we don’t have to calculate them twice.

I like that people chose certain constraints to self-impose, according to their own purposes and preferences. It gives variety and allows us to explore different aspects to the challenge.

    sub fact {
        my $i = shift;
        my $prod = 1;
        $prod *= $_ for 2..$i;
        return $prod;
    }

    my @digit_fact = map {fact $_} 0..9;

    sub is_factorion {
        my $in = shift;
        my $sum = 0;
        $sum += $_ for map { $digit_fact[$_] } split //, $in;
        return $sum == $in;
        #say $sum;

    }
    for (1..50000) {
        say $_ if is_factorion($_)
    }

Alexander Pankoff

  additional languages: Haskell, Raku

  blog writeup: Challenge 153 Task #2 - Even more factorials and what the fuck are factorions?

Speaking of structure, Alexander takes things somewhat to the next level, as he builds two routines, one to manually check a number and the other to check whether it’s within the list of all factorions. I mean, there are only four, after all, and there can never be any other. Having solved the problem two ways he tests all candidate values from 1 to 100,000 using one of the two methods, selected at random.

It’s kind of a ridiculous Rube-Goldberg machine in the end, but I feel that technical sensibility wasn’t exactly Alexander’s highest priority.

It is, importantly, logically sound beneath the surface.

    sub run() {
        my $max = 100000;
        say "Factorions <= $max:";
        for ( 1 .. $max ) {
            my $fn = ( \&is_factorion, \&is_factorion_a014080 )[ int( rand(2) ) ];
            say $_ if $fn->($_);
        }
    }

    sub is_factorion($n) {
        my @digits                      = split( m//, $n );
        my $sum_of_factorials_of_digits = sum0( map { fac($_) } @digits );

        return $n == $sum_of_factorials_of_digits;
    }

    sub fac($n) {
        product( 1 .. $n );
    }

    sub is_factorion_a014080($n) {
        ## complete list of all factorians - see https://oeis.org/A014080

        state @A014080 = ( 1, 2, 145, 40585 );
        first { $n == $_ } @A014080;
    }

Dave Jacoby

  blog writeup: Luck is not a Factor!: Weekly Challenge #153 | Committed to Memory

In a side note, I’m always pleased to read the notes whenever we so these factorial problems, because everyone always seems to be super-excited about the numbers. Then I end up inevitably deflated a bit because it’s just the notation pulling the old switcharoo.

On the other hand Dave does seem genuinely excited by numbers, and this week brings to our attention that for challenge number 153 we have 5! + 4! + 3! + 2! + 1! = 153 and blastoff!

Now that’s the spirit!

Ok, fine, I added that blastoff stuff myself but I don’t think he’ll mind.

With helpers sum0() and product() from List::Util the logic flow is straighforward — the heavy lifting, if you want to call it that, is done for computing the factorial sum in factorion().

    for my $i (@ARGV) {
        my $f = is_factorion($i);
        say join "\t", '', $i, $f;
    }

    sub is_factorion ( $n ) {
        my $f = factorion($n);
        return $f == $n ? 1 : 0;
    }

    sub factorion ( $n ) {
        return sum0 map { factorial($_) } split //, $n;
    }

    sub factorial ( $n ) {
        return 1 if $n == 0;
        state $factorials ;
        if ( !$factorials->{$n} ) {
            $factorials->{$n} = product 1 .. $n;
        }
        return $factorials->{$n};
    }

Pete Houston

Pete makes a point of noting the module functions he isn’t using, preferring to write his own: List::Util::sum() and Math::Prime::Util::factorial(). That said the solutions we’re seeing to this task all have a quite a bit of self-similarity, and one of the larger variances we have is in the methods for aquiring and accessing the required factorials. Here Pete creates a state hash within the fac() function to either quickly return precomputed factorials or create new ones as required. To produce the factorials the definition is recursive, calling fac() for the previous value and multiplying it by the new factor. Which is novel, and neat.

    sub is_factorion {
        my $n = shift;
        return 0 unless defined ($n) && $n =~ /^[1-9][0-9]*$/;
        return $n == sum map { fac ($_) } split //, $n;
    }

    # Instead of List::Util::sum

    sub sum (@) {
        my $tot = shift;
        $tot += $_ for @_;
        return $tot;
    }

    # Instead of Math::Prime::Util::factorial

    sub fac {
        state $have = { 0 => 1 };
        my $i = shift;
        return $have->{$i} if $have->{$i};
        my $fac = $i * fac ($i - 1);
        $have->{$i} = $fac;
        return $fac;
    }

E. Choroba

Choroba uses a similar recurrence relation to generate his factorials, here placing the operation within a for loop. Starting with a kernal, each factorial created is then used as a base product to calculate the next. The products are hashed for constant-time access to the values. Slightly rearranged this time, again we see the actual factorion validation massaged into a single chained sequence of operations.

    use List::Util qw{ sum };

    {   my %factorial = (0 => 1);
        $factorial{$_} = $factorial{ $_ - 1 } * $_ for 1 .. 10;

        sub is_factorion ($n) {
            sum(@factorial{ split //, $n }) == $n
        }
    }

Jorg Sommrey

Jorg returns again this week to the sweet embrace, the seductive snare, of Math::Prime::Util, this time bringing in four functions: vecsum(), factorial(), vecnone() and todigits(). These are all somewhat familiar operations with unfamiliar names, specially tuned for number theory problems.

  • vecsum() is like sum0() for an an array of integers. Because a signed Int type is used under the hood larger values can be stored without a floating-point error
  • factorial() does what you think
  • vecnone {BLOCK} returns true if the BLOCK expression never evaluates to true for any of the list values, which again are required to be integers.
  • todigits() breaks a number up into an array of digits, much as split // has been used here elsewhere today.

The functions may change, but the essential processing remains the same.

    say 0 + factorion($ARGV[0], $base);

    sub factorion($n, $base=10) {
        $n == vecsum map factorial($_), todigits $n, $base;
    }

Peter Campbell Smith

  blog writeup: !Task one is quite easy. Factorions are quite rare.

I was musing earlier today about the scarcity of the factorions, and that for a script that spot-checks individual values how unlikely it would be to find one. Even taking into account the upper boundary and clearly stating that it was pointless to inquire about values over 2,540,160, a guess within that range would have only a 0.00016% chance of success. Those aren’t big-lottery prize odds, but certainly are well into the range of the smaller ones.

Apparently the same idea occured to Peter, as he notes:

I was tempted to submit

    say 'no';

which is nearly always correct

However he does go on to not only compute the factorions but to reproduce the formatting of the examples. The operations are all lovingly hand-made and inlined into the control flow. Of note his production of a lookup array of factorials computes only exactly what needs to be done, only making a single further multiplication from the previous value.

    for $n (1 .. 9) {
        $fac[$n] = $n * $fac[$n - 1];
    }

No waste, no fuss.

Here’s the remainder of his process:

       $sum = 0;
       $string1 = $string2 = '';

       # test for being a factorion

       while ($test =~ m|(\d)|g) {
       	$sum += $fac[$1];
       	$string1 .= qq[$1! + ];
       	$string2 .= qq[$fac[$1] + ];
       }

       # format output

       say qq[\nInput:  $test];
       $string1 =~ s|...$||;
       $string2 =~ s|...$||;
       if ($sum == $test) {
       	say qq[Output: 1 since $string1 => $string2 = $test];
       } else {
       	say qq[Output: 0 since $string1 => $string2 = $sum <> $test];
       }

Blogs and Additional Submissions in Guest Languages for Task 2:

Abigail

  additional languages: Awk, Bash, Bc, C, Go, Java, Lua, Node, Pascal, Python, R, Ruby, Scheme, Tcl

Adam Russell

  blog writeup: Finding the Factorials and Factorions That Are Left

Bruce Gray

  additional languages: Raku, Webassembly

Colin Crain

  additional languages: Raku

  blog writeup: Factory People in a Factory World - Programming Excursions in Perl and Raku

Flavio Poletti

  additional languages: Raku

  blog writeup: PWC153 - Factorions - ETOOBUSY

Lubos Kolouch

  additional languages: Php, Python

Roger Bell_West

  additional languages: Javascript, Kotlin, Lua, Postscript, Python, Raku, Ruby, Rust

  blog writeup: RogerBW’s Blog: The Weekly Challenge 153: Factoriality

Ulrich Rieke

  additional languages: C++, Haskell, Raku

W. Luis Mochan

  blog writeup: Perl Weekly Challenge 153 – W. Luis Mochán



 

 

 

 

_________ THE BLOG PAGES _________


That’s it for me this week, people! Warped by the rain, driven by the snow, resolute and unbroken by the torrential influx, by some miracle I somehow continue to maintain my bearings.

Looking forward to next wave, the perfect wave, I am: your humble servant.

But if Your Unquenchable THIRST for KNOWLEDGE is not SLAKED,

then RUN (dont walk!) to the WATERING HOLE

and FOLLOW these BLOG LINKS:

( …don’t think, trust your training, you’re in the zone, just do it … )

Adam Russell

Alexander Pankoff

Arne Sommer

Colin Crain

Dave Jacoby

Flavio Poletti

Laurent Rosenfeld

Luca Ferrari

Peter Campbell Smith

Roger Bell_West

W. Luis Mochan

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.

Contact with me