Skip to content

Summarize and say


Analyze a dictionary of results; map between dictionaries

A lambda for the Look & Say sequence composes with desc and the Do iterator to produce the Summarize & Say sequence.

A million integers in string form hashes with group and desc to a dictionary of 8002 unique seeds.

A dictionary of sequences, keyed by the unique seeds: composition count distinct@ measures the iterations needed to converge; max finds the slowest sequences.

Reverse lookup on a dictionary finds their unique seeds; indexing the sequence and seed dictionaries with them finds the slowest sequences and all the seeds that produce them.

:fontawesome-solid-check-circle: Seven code lines, no loops, no counters, no control structures.

Find for Sum & Say sequences all the seed values up to a million that take the most iterations to converge.

See Rosetta Code for the task details.1

Note that in this context a series has converged when a value in the series is repeated.

Start with the simple Look and Say sequence.

q)ls:{raze(string 1_ deltas d,count x),'x d:where differ x}  / look & say
q)ls string 0
q)10 ls\string 0

The Look and Say sequence (or “Morris sequence”) grows indefinitely. The Summarize and Say variant converges.

q)sumsay:ls desc@                                            / summarize & say
q)15 sumsay\string 0

Unique seeds

Because sumsay sorts its argument digits, variations in order produce the same sequence.

q)sumsay each string`123`321

So it is not necessary to construct a sequence for every one of a million seeds.

q)seeds:group desc each string til 1000000

A dictionary. Its key is the unique seeds; its value their permutations.

q)seeds string`21`9721`66633
12 21
1279 1297 1729 1792 1927 1972 2179 2197 2719 2791 2917 2971 7129 7192 7219 72..
33666 36366 36636 36663 63366 63636 63663 66336 66363 66633


Construct a 30-term sequence for each seed.

seq:(key seeds)!30 sumsay\'key seeds

A dictionary of seeds and their sequences.

Iterator syntax

An iterator is a unary operator with postfix syntax. It takes a single argument, on its left.

Above, sumsay is the argument of \ (the Scan form of) the Do iterator. Applying Do to sumsay derives the function sumsay\.

We earlier saw sumsay\ applied as a binary, with infix syntax and 15 as a left argument. Here the left argument is 30: thirty successive applications of sumsay. With no right argument, 30 sumsay\ is a projection of sumsay\ on its left argument 30, forming a unary that will apply sumsay to its argument 30 times.

That projection 30 sumsay\ is the left argument of the Each operator '.

The unary derived function 30 saysum\' applies 30 saysum\ to each item of its argument.

How to use iterators

Bonus points

The repetition in (key seeds)!30 sumsay\'key seeds can be removed by another iterator.

The expression n f/x applies f successively n times to x. Its Scan form n f\x does the same, but returns the result of each application. The result has n+1 items, corresponding to til n+1 applications. The first item of the result corresponds to 0 applications; i.e. the original argument.

0 f/ and 0 f\ are identity functions for any f. Which means… 1 f\x <=> (x;f x).

q)1 reverse\1011001b

The Apply operator applies a function to a list of its arguments. So for some f we can define

q)(~) . 1 reverse\ "madamimadam"

In our case f is {30 sumsay\'x}.

seq:(!) . 1{30 sumsay\'x}\key seeds


Count the iterations to convergence: how many does the slowest take?

q)max its:(count distinct@)each seq

Above, (count distinct@) is a composition, equivalent to {count distinct x}

Selecting seeds

What unique seeds had sequences that took 21 steps to converge? its was defined by applying (count distinct@)each to seq, so its is a dictionary with the same keys, and

q)where its=max its

is a dictionary reverse lookup, returning the keys with values of 21.

Just that one, then. But that unique seed corresponds to several seeds:

q)raze seeds where its=max its
9009 9090 9900

Above, the reverse dictionary lookup, then the results looked up in dictionary seeds.


Put it all together.

ls:{raze(string 1_ deltas d,count x),'x d:where differ x}  / look & say
sumsay:ls desc ::                                          / summarize & say

seeds:group desc each string til 1000000                   / seeds: 1M integers
seq:(key seeds)!30 sumsay\'key seeds                       / for unique seeds
top:max its:(count distinct@)each seq                      / count iterations

/ report results
rpt:{1 x,": ",y,"\n\n";}
rpt["Seeds"]" "sv string raze seeds where its=top          / all top seed forms
rpt["Iterations"]string top
rpt["Sequence"]"\n\n","\n"sv raze seq where its=top


Seeds: 9009 9090 9900

Iterations: 21




Defined function ls for the Look & Say sequence; sumsay for the Summarize & Say sequence is just he composition ls desc::.

Took a million integers as strings and used group to hash them into an 8002-entry dictionary keyed by their digits sorted in descending order.

Made a dictionary of 30-item sequences for the unique seeds, using the Do and Each iterators.

Used composition count distinct@ and each to count the number of iterations required in each sequence before it converged, and aggregator max to measure the longest.

Used reverse lookup on the dictionary of iterations to find the unique seeds for the slowest-converging sequences, then mapped them to the corresponding original seeds and to the sequences themselves. (Turns out there is just the one.)

  1. An earlier version of this article appeared on