Sunday Times Teaser 3096 – That Strikes a Chord
by Colin Vout
Published Sunday January 23 2022 (link)
Skaredahora used a scale with seven notes, labelled J to P, for an idiosyncratic composition. It had a sequence of chords, each comprising exactly three different notes (the order being immaterial). The sequence started and ended with JNP. Each change from one chord to the next involved two notes increasing by one step in the repeating scale, and the other decreasing by two steps (eg, JNP changing to KLJ).
Every chord (eventually) reachable from JNP was used at least once; every allowable change from one of these chords to another was used exactly once. The number of chord changes between the first pair of JNPs was more than that between the last such pair, but, given that, was the smallest it could be.
With notes in alphabetical order, what was the seventh chord in the sequence?
-
Brian Gladman permalink12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364from itertools import combinationsfrom collections import defaultdict# the notes J..P will be represented by the integers 0..6# the starting/ending chordjnp = (0, 4, 6)# map chords to the chords that can follow themch2chs = defaultdict(set)for ch in combinations(range(7), 3):# consider the three possible next chordsfor p in range(3):# create the next chordnch = set(((x - 2) if i == p else (x + 1)) % 7for i, x in enumerate(ch))# check that it has three different notesif len(nch) == 3:nch = tuple(sorted(nch))if nch != ch:ch2chs[ch].add(nch)# generate chord sequences starting and ending with chord <sc>def chord_seq1(sc, ch_seq=()):if ch_seq and ch_seq[-1] == ch_seq[0]:yield ch_seqelse:if not ch_seq:ch_seq = (sc,)for ch in ch2chs[ch_seq[-1]]:if ch_seq.count(ch) < 2:yield from chord_seq1(ch, ch_seq + (ch,))# collect all chord changes in sequences starting with jnpcc = set()for s in chord_seq1(jnp):for i, c in enumerate(s[1:]):cc.add((s[i], c))# generate chord sequences starting with chord <sc># that use all chord changes in <cc>def chord_seq2(sc, cc, ch_seq=()):if not cc:yield ch_seqelse:for nc in ch2chs[sc]:t = (sc, nc)if t in cc:yield from chord_seq2(nc, cc.difference([t]), ch_seq + (nc,))min_diff, min_seq = None, None# consider all chord sequences that use all chord changesfor s in chord_seq2(jnp, cc):# find the positions of jnp chordsps = tuple(i for i, c in enumerate(s) if c == jnp)# the first gap between jnp chords is smaller than the lastif len(ps) > 1 and ps[0] > ps[-1] - ps[-2]:if min_diff is None or ps[0] < min_diff:min_diff, min_seq = ps[0], ss = [''.join('JKLMNOP'[n] for n in x) for x in min_seq]print(f"{s[6]} ({' '.join(s)})")