RogerBW's Blog

Perl Weekly Challenge 109: Chowla Squares 20 April 2021

I’ve been doing the Perl Weekly Challenges. The latest involved another numerical sequence and permutations. (Note that this is open until 25 April 2021.)

TASK #1 › Chowla Numbers

Write a script to generate first 20 Chowla Numbers, named after Sarvadaman D. S. Chowla, a London born Indian American mathematician. It is defined as:

C(n) = (sum of divisors of n) - 1 - n

"Divisors" appears to mean what I'd normally call "all factors" (as distinct from "prime factors"); I think this may be a difference between British and American English, or possibly a change in maths teaching over time. So anyway this is OEIS A048050… and I have now written some Perl code which uses an HTTP client library simply to fetch and parse an OEIS page, but let's do this the proper way.

To get the divisors of a number, I can either prime factorise it and then build up combinations of those, or just trial-divide. Since we don't need to consider the factor of n that is n, the easiest (though certainly not the most efficient) approach is to trial-divide, or more to the point trial-modulus, from 2 up to n/2 (rounded down).

Thus Perl:

sub chowla {
  my $count=shift;
  my @a;
  foreach my $n (1..$count) {
    push @a,sum(0,grep {$n % $_ == 0} (2..int($n/2)));
  }
  return \@a;
}

That initial "0" is because the sum of an empty list is, quite properly, undefined.

Raku looks the same, but I thought I'd try out take/gather.

sub chowla($count) {
  my @a;
  for 1..$count -> $n {
    push @a,sum(gather {
      take 0;
      for (2..floor($n/2)) {
        take $_ if $n %% $_
      }
    });
  }
  return @a;
}

The sympy library for Python has a divisors method, but I already have the code and I'm trying to avoid external dependencies. Ruby's grep is called select and I must try to remember this. In Rust I made the loops explicit.

fn chowla(count: usize) -> Vec<usize> {
    let mut a: Vec<usize>=vec![];
    for n in 1..=count {
        let mut s=0;
        for i in 2..=n/2 {
            if n % i == 0 {
                s += i
            }
        }
        a.push(s);
    }
    return a;
}

There's another approach which I thought of after I'd written this code. If one were building the sequence at greater length and trying to minimise execution time, I suspect that a Sieve of Eratosthenes-like approach would make sense: have a running total for each target number, and add the divisors to each total in turn, so 2 would be added to the totals for 4, 6, 8, …; then 3 to the totals for 6, 9, 12, …; and so on. The output sequence is just the list of totals. That would avoid all the modulus operations; it still looks like O(N^2), but it shouldn't particularly use more memory either. I've coded that as well, with this drop-in replacement function:

sub chowla_sieve {
  my $count=shift;
  my @a=(0) x $count;
  foreach my $n (2..int($count/2)) {
    for (my $k=$n*2;$k<=$count;$k+=$n) {
      $a[$k-1]+=$n;
    }
  }
  return \@a;
}

With the first-20 problem as presented it speeds things up only by a factor of about 2.6 (from 30-odd to 12-odd microseconds on my test machine). First 100 is speeded up by a factor of 6.5, first 1,000 by about 40, and first 10,000 by about 270.

TASK #2 › Four Squares Puzzle

You are given four squares as below with numbers named a,b,c,d,e,f,g.

Write a script to place the given unique numbers in the square box so that sum of numbers in each box is the same.

I won't duplicate the ASCII art here. This comes down to: arrange seven numbers into order a..g such that

  • a + b == b + c + d
  • b + c + d == d + e + f
  • d + e + f == f + g

One could probably deploy some cleverness in solving the system of equations to reduce the search space, but I didn't bother: I ran a simple permutation and tested the results.

In Perl that meant Algorithm::Permute; that's breaking my usual guideline about avoiding the use of non-core modules, but I've written permutor code before (for the Weekly Challenge, even) and I couldn't be bothered to do it again. One hiccup: because of the way the code block is enclosed, I can't abandon the permutation when a solution is found as I can in the other languages.

(Since there are 8 possible solutions for the test case 1..7, and there will clearly be at least 2 for any case which has a solution at all (which I think is probably all cases), testing becomes mildly challenging.)

The notional variables $a to $d represent each box sum; they're calculated only as needed and assigned to actual variables only if they'll be used more than once, so only $b and $c actually exist.

sub foursquare {
  my @in=@_;
  my @sol;
  Algorithm::Permute::permute {
    my $b=$in[1]+$in[2]+$in[3];
    if ($in[0]+$in[1]==$b) {
      my $c=$in[3]+$in[4]+$in[5];
      if ($b==$c && $c == $in[5]+$in[6]) {
        @sol=@in;
      }
    }
  } @in;
  return \@sol;
}

Raku has the permutations method, and we can bail out of that iterator when we get a result.

sub foursquare(@in) {
  my @sol;
  for @in.permutations -> @t {
    my $b=@t[1]+@t[2]+@t[3];
    if (@t[0]+@t[1]==$b) {
      my $c=@t[3]+@t[4]+@t[5];
      if ($b==$c && $c == @t[5]+@t[6]) {
        @sol=@t;
        last;
      }
    }
  }
  return @sol;
}

Python has permutations in itertools which is a core module, and Ruby has permutation in the core language. Rust doesn't permute in core, but offers the Permutohedron crate, which insists on modifying the array in place; that's doubtless useful if you're doing computationally challenging stuff where performance is relevant, but not really important here.

This week felt to me like a return to form after some recent challenges that weren't particularly enjoyable. Thanks to Mohammed, who does this largely single-handedly. (Send him problems! I'm working on some of my own.)

Full code on github.


  1. Posted by RogerBW at 03:26pm on 26 April 2021

    For task 1, some people used the trial division to find two factors at a time, but at least among the bloggers nobody else came up with my sieve method for generating the full list of values at once. (I don't read the github repository, only the 15 or so people who blog about their code.)

    In task 2, it seems that permutation is indeed the way to go.

Add A Comment

Your Name
Your Email
Your Comment

Your submission will be ignored if any field is left blank, but your email address will not be displayed. Comments will be processed through markdown.

Search
Archive
Tags 1920s 1930s 1940s 1950s 1960s 1970s 1980s 1990s 2000s 2010s 3d printing action aeronautics aikakirja anecdote animation anime army astronomy audio audio tech base commerce battletech beer boardgaming book of the week bookmonth chain of command children chris chronicle church of no redeeming virtues cold war comedy computing contemporary cornish smuggler cosmic encounter coup covid-19 cycling dead of winter doctor who documentary drama driving drone ecchi economics espionage essen 2015 essen 2016 essen 2017 essen 2018 essen 2019 existential risk falklands war fandom fanfic fantasy feminism film firefly first world war flash point flight simulation food garmin drive gazebo genesys geodata gin gkp gurps gurps 101 gus harpoon historical history horror hugo 2014 hugo 2015 hugo 2016 hugo 2017 hugo 2018 hugo 2019 hugo 2020 hugo-nebula reread in brief avoid instrumented life julie enfield kickstarter learn to play leaving earth linux liquor lovecraftiana mecha men with beards museum music mystery naval non-fiction one for the brow opera parody perl perl weekly challenge photography podcast politics powers prediction privacy project woolsack pyracantha python quantum rail raku ranting raspberry pi reading reading boardgames social real life restaurant reviews romance rpg a day rpgs ruby rust science fiction scythe second world war security shipwreck simutrans smartphone south atlantic war squaddies stationery steampunk stuarts suburbia superheroes suspense television the resistance thirsty meeples thriller tin soldier torg toys trailers travel type 26 type 31 type 45 vietnam war war wargaming weather wives and sweethearts writing about writing x-wing young adult
Special All book reviews, All film reviews
Produced by aikakirja v0.1