11 July 2022

Aesthetics and a fast-growing sequence

Esthetic numbers

This week's task 1 is to write a script to find out if a given number is an Esthetic Number. An Esthetic Number is one where successive digits differ by 1.

We may deduce:

  • They were named by an American
  • The digits may differ by ±1 

There seems little option but to start at one end and compare each digit to the next one in the direction of travel.  The evidence from past challenges is that substr() is faster than pulling them out with a regular expression, so let's use that.

Method 1 - fast but non-compliant

We can comply with the instruction by declaring a number unaesthetic as soon as we find two consecutive digits which differ by >1. If we treat the given number as a string, we don't need to worry about exceeding Perl's maximum integer: we're only ding arithmetic on 0 .. 9.

Method 2 - matching Mohammad's example output

Mohammad gives us as an example:

120 is not an esthetic number as |1 - 2| != |2 - 0| != 1

It is unfortunately not very clear whether the 'as' clause would be the same for 1207. Would he give up after the 0, or continue to the end:

1207 is not an esthetic number as |1 - 2| != |2 - 0| != 1
1207 is not an esthetic number as |1 - 2| != |2 - 0| != |0 - 7| != 1

I thought it was safer to assume he might mean the second of these, so I have given two solutions, one which just gives the first unesthetic pair and the other which parses the entire number.  But this is slightly tricky as each '=' or '!=' (except the last) depends on 3 digits. For example, in my second example above, the first '!=' depends on 1, 2 and 0.

As a toxic example, 

2468 is not an esthetic number as |2 - 4| = |4 - 6| = |6 - 8| != 1

Note that the only '!=' comes at the end.

Of course, for any reasonable-sized number the difference in speed is unmeasurably small.

Sylvester's sequence

I had rather hoped that this was discovered by the eponymous cat:

but it seems that James Joseph Sylvester was responsible.  The sequence start with 2, 3, and each subsequent term is the product of all the preceding ones, plus one.

A little reflection reveals that:

  • This is going to get big as fast as a pumpkin
  • So we'd better resort to Math::BigInt
We can save microseconds by noting that term n really only depends on term n -1. Specifically:

s(n) = (s(n-1) - 1) * s(n-1) + 1

And once we have conquered the challenge of expressing that in BigInt-ian:

$sylvester[$j] = Math::BigInt->new(0)  # zero
->badd($sylvester[$j - 1])     # plus the preceding term
->bsub(Math::BigInt->new(1))   # minus 1 
->bmul($sylvester[$j - 1])     # times the preceding one
->badd(Math::BigInt->new(1));  # plus 1

we have our solution.  We are asked for the first 10 terms, and the tenth turns out to be 165506647324519964198468195444439180017513152706377497841851388766535868639572406808911988131737645185443, which confirms the decision to use BigInt.







No comments:

Post a Comment