With Dyalogβs APL Problem Solving Competition 2021 in full swing, itβs time to highlight some of the excellent solutions that were submitted to last yearβs edition.
Stefan Kruger works for IBM making databases. While he tries to learn at least one new programming language a year, he got hooked on APL and participated in the competition. This is his perspective on some solutions that the judges picked out – call it the βJudgesβ Pickβ, if you like; smart, novel, or otherwise noteworthy solutions that can serve as an inspiration.
This blog post is also available as an interactive Jupyter Notebook document.
By Stefan Kruger
Iβll show a cool solution or two to each Phase II problem and dive into the details of a couple. If you need to refresh your memory with what the problems looked like, there’s a PDF of the Phase II problems.
Oh, and note that at the time of writing there is still plenty time to take part in the current edition of the competition (and really, who knew bowling was so complicated?) – there are some juicy cash prices to be won.
Problem 1: Take a Dive (1 task)
Level of Difficulty: Low
So let’s kick off with problem 1. The task was to calculate the score of an Olympic dive, consisting of a technical difficulty rating and a vector containing either 3, 5 or 7 judges’ scores. Only the central three ordered judges’ scores should be considered, which should be summed and multiplied by the technical difficulty rating.
Here is a cunning trick that wasn’t at all obvious:
β scoreβdd DiveScore scores;sorted;cenzored;rotator
β 2020 APL Problem Solving Competition Phase II
β Problem 1, Task 1 - DiveScore
sortedβ{β΅[ββ΅]}scores
β 0 1 2 rotates score indexes to 123, 23451 or 3456712
β So three center values always goes first
β 51 = (0 1 2β§.= 3 5 7 β.|β³100) β³ 1
rotatorβ51
cenzoredβ3βrotatorβ½sorted
scoreββ2βdd+.Γcenzored
β
2.9 2.6 2.7 DiveScore¨(7 7.5 6.5 8 8 7.5 7)(9.5 8 8.5)(7.5 7 7 8.5 8)
63.8 67.6 60.75
This contestant figured out that if a vector of length 3, 5 or 7 is rotated 51 steps, then the original central three items will always end up at the beginning. No, really. It turns out that 51 is the first number X
such that 0 1 2β‘3 5 7|X
. They tabulated the options and picked the first solution, guessing that it’d be less than 100:
βΈ0 1 2β§.=3 5 7β.|β³100
51
But there is another way – this is one of those situations where the Chinese Remainder Theorem comes in handy, especially since it’s available on APLcart:
3 5 7 {m|β΅+.ΓβΊ(β£Γβ’|ββ{0=β΅:1 0 β (β΅ββ΅|βΊ)+.Γ0 1,βͺ1,-ββΊΓ·β΅})Β¨β¨βΊΓ·β¨mβΓ/βΊ} 0 1 2 β https://aplcart.info?q=chinese
51
If you figured that out, award yourself a well-deserved pat on the back. For us mortals, we probably all did something rather more pedestrian:
DiveScore β {
d β 2-2Γ·β¨7-β’β΅ β How many items should we drop each side?
βΊ+.Γ(-d)βdββ΅[ββ΅]
}
Problem 2 β Another Step in the Proper Direction (1 task)
Level of Difficulty: Medium
Problem 2 builds upon Problem 5 from Phase I. In short, we are asked to write a function Steps
that takes a two-element vector to the right, defining a start and end value, and an optional left integer argument that tweaks how we generate values from start
to end
. The complexity here comes from the many combinations of behaviours from what exactly is given as the left argument: integer or float? positive or negative? Also, the range must be inclusive, even if a floating-point step size means that the end point is overshot. I took this on thinking it would be trivial – it wasn’t.
Here’s a great solution that manages to combine this functionality with a call to a single dfn:
β stepsβ{p}Steps fromTo;segments;width
width β |-/fromTo
:If 0=βNC'p' β No left argument: same as Problem 5 of Phase I
segments β 0,β³width
:ElseIf p0 β p is the step size
segments β p {β΅ββΊΓ0,β³ββ΅Γ·βΊ} width
:ElseIf p=0 β As if we took zero step
segments β 0
:EndIf
β Take into account the start point and the direction.
steps β fromTo {(ββΊ)+(-Γ-/βΊ)Γβ΅} segments
β
I ended up with something more convoluted, with a few ugly special cases, and shamelessly borrowing from dfns.iotag:
Steps β {
range β {
r β βΊ-sΓβIO-β³β1-(βΊ-ββ΅)Γ·sβΓ/1ββ΅,(βΊ>ββ΅)/Β―1 β "inspired" by dfns.iotag
(ββ΅)β ββr: r,ββ΅ β r β Ensure endpoint is included – yeuch :(
}
βΊ β β¬
(b e) β β΅
βΊβ‘β¬: b range e β No βΊ
βΊ=0: b β Zero step; return start point
βΊ>0: b range e βΊ β Positive βΊ
len β (e-b)Γ·countββ-βΊ β Negative βΊ
len=0: b/β¨1+count
b range e len
}
Problem 3 β Past Tasks Blast (1 task)
Level of Difficulty: Medium
The task here was to scrape the Dyalog APL Problem Solving Competition webpage to extract all links to PDF files. We get the suggestion to use either Dyalog’s HttpCommand
or shell out to a system mechanism for fetching a web page.
To use HttpCommand
, we first need to load it:
]load HttpCommand
#.HttpCommand
Here’s a slightly tweaked competition submission, showing great flair in how to process XML:
PastTasks β {
url β β΅
r β (HttpCommand.Get url).Data β get page contents
(d n c a t) β βββXML r β depth; name; content; attributes; type
(k v) β ββ ββͺ/ ((,'a')ββ‘Β¨n)/a β extract key-value pairs of <a> elements
urls β ('href'ββ‘Β¨k)/v β get URLs
pdfs β ('.pdf'β⑨¯4βΒ¨urls)/urls β filter .pdfs
base β ββ½β('base'ββ‘Β¨n)/a β base URL
baseβ,Β¨pdfs
}
The problem statement suggests that a regex-based solution might be tolerable. Here’s a stab at that approach:
PastTasks β {
body β (HttpCommand.Get β΅).Data
pdfs β '<a href="(.+?\.pdf)"'βS'\1'β’body
base β '<base href="(.+?)"'βS'\1'β’body
base,Β¨pdfs
}
So which is the “better” solution? Well, the first approach has a number of advantages: firstly, is much more robust (provided that the web page is valid XHTML, which we are told is a given), meaning that we can abdicate responsibility for dealing with markup quirks (single vs double quotes, whitespace etc) to the built-in βXML
system function, and secondly, there is that (in)famous quote from Jamie Zawinski:
Some people, when confronted with a problem, think βI know, I’ll use regular expressions.β Now they have two problems. – jwz
Mixing in a liberal helping of regular expressions in with APL is perhaps not helping APL’s unfair reputation for being write-only.
However, when dealing with patterns in textual data, as we unquestionably are here, regular expressions – even in a powerful language like APL – are sharp tools that are hard to beat, and any programmer worth their salt owes it to themselves to master them. In the case above, had the data not neatly been parseable as XML, it would have been more awkward to solve a problem like this relying only on APL primitives.
Problem 4 β Bioinformatics (2 tasks)
Level of Difficulty: Medium
The two tasks making up Problem 4 are borrowed from Project Rosalind, which is a Bioinformatics problem collection that often has great APL affinity:
and a hint that one benefits from understanding modular multiplication, as this isn’t built into Dyalog APL.
Here is a great example:
revp β { β r β revp dna
dnaNum β 'ACGT'β³β΅ β Convert to 1..4 so that A+T = C+G = 5
FindRevp β { β Given chunk size, extract positions and build the output format
chunks β β΅,/dnaNum
isRevp β (β’β‘5-β½)Β¨chunks
β΅,β¨βͺβΈisRevp
}
ββͺ/FindRevpΒ¨4 6 8 10 12 β Test against all chunk sizes and collect results
}
sset β { β rβsset n
bin β 2β₯β£Β―1β’β΅ β Binary digits
arr β β½2*bin β Repeated squaring: Starting from MSB and 1, square β΅, multiply βΊ, modulo m
mod β 1000000
{mod|βΊΓβ΅*2}/arr,1
}
This contestant also saw fit to include their test suite; a nice touch! Roger Hui’s version of assert has become the de facto standard, and the contestant puts it to good use:
Assert β {βΊβ'assertion failure' β 0ββ΅:βΊ βSIGNAL 8 β shyβ0} β Roger Hui's Assert
RevpTest β {
s β 'TCAATGCATGCGGGTCTATATGCAT'
ans β revp s
Assert 8 2β‘β΄ans:
Assert 5 4 7 4 17 4 18 4 21 4 4 6 6 6 20 6β‘βans:
header β 'Contest2020/Data/' β Change as needed
data1 β β1βββNGET (header,'rosalind_revp_1_dataset.txt') 1
ans1 β ββΒ¨ββNGET (header,'rosalind_revp_1_output.txt') 1
data2 β β1βββNGET (header,'rosalind_revp_2_dataset.txt') 1
ans2 β ββΒ¨ββNGET (header,'rosalind_revp_2_output.txt') 1
Assert ans1 β‘ revp data1:
Assert ans2 β‘ revp data2:
'Test passed'
}
SsetTest β {
Assert 8 = sset 3:
Assert 551872 = sset 857:
Assert 935424 = sset 870:
'Test passed'
}
Problem 5 β Future and Present Value (2 tasks)
Level of Difficulty: Medium
Problem 5 is some hedge fund maths, or something where my eyes glazed over before I fully understood the ask. What is this, Kβ½
This solution is impressively compact – I removed the comments to highlight the APL artistry on display: no less than three scans, count ’em!
rr β {ARΓ+\βΊΓ·ARβΓ\1+β΅}
pv β {+/βΊΓ·Γ\1+β΅}
Here’s how the competitor outlined how their solution works:
This can be calculated elegantly with the following operations:
- Find the accumulated interest rate (
AR
) for each term (ARβΓ\1+β΅
).
- Deprecate the cashflow amounts by dividing them by
AR
. This finds the present value of all the amounts.
- Accumulate all the present values of the amounts to find the total present value at each term.
- Multiply by
AR
to find future values at each term.
This way the money that was invested or withdrawn in a term is not changed for that term, but the money that came from the previous terms is multiplied by the current interest rate for each term arriving to the correct recurrent relation:
Step 2) |
amounts[i]/AR[i] β β‘ PV[i] |
Step 3) |
amounts[i]/AR[i] + APV[i-1] |
Step 4) |
amounts[i] + APV[i-1]ΓAR[i]
amounts[i] + APV[i-1]ΓAR[i-1]Γ(1+rate[i])
amounts[i] + r[i-1]Γ(1+rate[i]) β β‘ r[i] |
Problem 6 β Merge (1 task)
Level of Difficulty: Medium
Mail merge – gotta love it. Your spam folder is full of bad examples of this: “Dear $FIRSTNAME, do you want to purchase a bridge?” We’re given a template file with patterns such as @firstname@
which are to be replaced with values stored in a JSON file. Here’s a smart approach from a competitor who knows their way around the @
operator:
Merge β {
templateFile β βΊ
jsonFile β β΅
template β ββNGET templateFile
ns β βJSONββNGET jsonFile
getValue β {
0=β΄β΅:,'@' β '@@' β ,'@'
6::'???' β ~β΅βns.βNL Β―2 β '???'
βnsββ΅ β β΅βns.βNL Β―2 β βns.β΅
}
βgetValueΒ¨@(β΄β΄1 0β¨)'@'(1βΒ¨=ββ’)template
}
The key insight here is that since each template starts and ends with the same marker, we can partition the data on sections beginning with @
and then we’ll have a vector where every other element is a template to be substituted. Here’s an example of this:
β('@'(1βΒ¨=ββ’) '@title@ @firstname@ @lastname@, would you be interested in the Brooklyn Bridge?') (1 0 1 0 1 0)
βββββββ¬ββ¬ββββββββββ¬ββ¬βββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββ
βtitleβ βfirstnameβ βlastnameβ, would you be interested in the Brooklyn Bridge?β
βββββββΌββΌββββββββββΌββΌβββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββ€
β1 β0β1 β0β1 β0 β
βββββββ΄ββ΄ββββββββββ΄ββ΄βββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββ
I added the second row for clarity to show the alternating templates. Cool, huh? However, this only works correctly if the data leads with a template. Consider:
'@'(1βΒ¨=ββ’) 'Dear @firstname@ @lastname@, or maybe the Golden Gate?'
βββββββββββ¬ββ¬βββββββββ¬ββββββββββββββββββββββββββββ
βfirstnameβ βlastnameβ, or maybe the Golden Gate?β
βββββββββββ΄ββ΄βββββββββ΄ββββββββββββββββββββββββββββ
We still have the alternating templates, but the prefix (Dear
) is lost. We can tweak the Merge
function a bit to cater for this if we need to:
Merge β {
templateFile β βΊ
jsonFile β β΅
template β ββNGET templateFile
ns β βJSONββNGET jsonFile
first β templβ³'@'
first>β’templ: templ β No templates at all
prefix β firstβtempl β Anything preceding the first '@'?
getValue β {
0=β΄β΅:,'@' β '@@' β ,'@'
6::'???' β ~β΅βns.βNL Β―2 β '???'
βnsββ΅ β β΅βns.βNL Β―2 β βns.β΅
}
βprefix,getValueΒ¨@(β΄β΄1 0β¨)'@'(1βΒ¨=ββ’)template
}
Now, the competition is pitched such that “proper array solutions” are preferred – and for good reasons, most of the time. However, it’s hard to overlook some industrial regex action in this case. Strictly for Perl-fans:
Merge β {
mrg β βJSONββNGET β΅
keys β mrg.βNLΒ―2
vals β mrg.βΒ¨keys
('@',Β¨(keys,'' '[^@]+'),Β¨'@')βR((βΒ¨vals),'@' '???')ββNGET βΊ
}
Problem 7 β UPC (3 tasks)
Level of Difficulty: Medium
Problem 7 had us learning more about bar codes than we ever thought necessary. Read them, write them, verify them, scan them – forwards and backwards no less. Good scope for stretching your array muscles on this one. The eagle-eyed amongst you may have spotted that the verification aspect is a simplified version of Luhn’s algorithm, which a certain Morten Kromberg used to illustrate APL’s array capabilities at JIO a while back.
Here’s a good solution:
CheckDigit β (10|β-+.Γβ(11β΄3 1)) β Computes the check digit for a UPC-A barcode.
UPCRD β 114 102 108 66 92 78 80 68 72 116 β Right digits of a UPC-A barcode, base 10.
bUPCRD β β2ββ₯β£Β―1β’UPCRD β Bit matrix with one right digit per row.
WriteUPC β {
β Writes the bits of a UPC-A barcode.
~((11β=β’)β§(β§/0ββ€β§β€β9))β΅: Β―1 β Check for simple errors
b β bUPCRD[β΅,CheckDigit β΅;]
1 0 1, (,~6βb), 0 1 0 1 0, (,6βb), 1 0 1
}
ReadUPC β {
β Reads a UPC-A barcode into its digits.
~(β§/0ββ€β§β€β1)β΅: Β―1 β Input isn't a bit vector
95β β’β΅: Β―1 β Number of bits must be 95
(b l m r e) β β΅ ββ¨ (βΒ―1ββ,β½) (3β1)(42β1)(5β1)
b β¨β₯(β’β1 0 1) e: Β―1 β Wrong patterns for the guards
mβ’0 1 0 1 0: Β―1
bits β β12 7β΄ l,r
C β (βbUPCRD)ββ³ ~@(β³6) β Convert bits to digits
tf β ~β§/10 > nums β C bits β Should we try flipping the bits?
nums β (numsΓ1-tf) + tfΓCβ½ββ½βbits
β¨/10=nums: Β―1 β Bits simply aren't right
(Β―1βnums)β CheckDigit 11βnums: Β―1 β Bad check digit
nums
}
Problem 8 β Balancing the Scales (1 task)
Level of Difficulty: Hard
Our task is to partition a set of numbers into two groups of equal sum if this is possible, or return β¬
if not. This is a well-known NP-complete problem called The Partition Problem and, as such, has no polynomial time exact solutions. The problem statement indicates that we only need to consider a set of 20 numbers or fewer, which is a bit of a hint on what kind of solution is expected.
This problem, in common with many other NP problems, also has a plethora of interesting heuristic solutions: polynomial algorithms that whilst not guaranteed to always find the optimal solution will either get close, or be correct for a significant subset of the problem domain in a fraction of the time the exact algorithms would take.
However, it’s clear that Dyalog expects us to give an exact solution, and has given us an upper bound on the input data length. Finally, we’re offered the cryptic advice that
Understanding the nuances of the problem is the key to developing a good algorithm.
Yes, thank you, master Yoda.
Here’s a great, efficient solution:
Balanceβ{
sumβ1β₯β΅
2|sum: β¬ β Lists with an odd sum cannot be split into equal parts.
halfsumβsumΓ·2
β A partitioning method based on the algorithm by Horowitz and Sahni.
β The basic idea of the algorithm is to split the input into two parts,
β and then generate all subset sums for these parts. Then the problem
β becomes finding a sum of two subset sums from different parts
β equal to the desired value. Instead of sorting the sums and comparing
β them like in the original algorithm, standard APL searching primitives
β β and β³ are used. Another key idea is to generate the subset sums
β in a specific order, so that the nth subset sum in the vectors a and b
β is the sum of the elements chosen by the binary representation of n.
β This means that we can get the elements of the solution sum
β without having to generate anything but the sums.
horowitzsahniβ{
sββ΅(β{βΊβ΅}β)β¨β2Γ·β¨β’β΅ β Split the input.
a bββΒ¨(β’,+)/Β¨s,Β¨0 β Generate the subset sums.
indexesβa {(β’,β΅β³βΊβ·β¨(β’βΊ)ββ’)1β³β¨βΊββ΅} halfsum-b β Search for solution indexes.
indexes[2]>β’b: β¬
β΅ {(βΊ/β¨~β΅)(β΅/βΊ)} β(2β΄Β¨β¨β’Β¨s)β€Β¨indexes-1 β Get the solution from the indexes.
}
β A simple exhaustive search. It uses the same binary representation
β idea as the horowitzsahni function.
exhaustiveβ{
iβhalfsumβ³β¨β(β’,+)/β΅,0
i>2*β’β΅: β¬
β΅ {(βΊ/β¨~β΅)(β΅/βΊ)} (2β΄β¨β’β΅)β€i-1
}
β The exhaustive method performs better than the Horowitz-Sahni method
β for small input sizes. 14 seems to be a reasonable cutoff point.
14>β’β΅: exhaustive β΅
horowitzsahni β΅
}
There are a number of clever touches here – there are actually two different solutions, an exhaustive search and an implementation of the algorithm due to Horowitz and Sahni, which, although still exponential, is known to be one of the fastest for certain subsets and input sizes. A switch based on input size checks for the crossover point and chooses the fastest option. And this is fast – five times faster than that of the Grand Prize winner, and four orders of magnitude faster than the slowest solution.
Such a performance spread is intriguing, so there are clearly lessons to be learned here. When I tried this problem, I ended up with a pretty straight-forward (a.k.a. naive) brute force search:
Balance β {βIOβ0
total β +/β΅
2|total: β¬ β Sum must be divisible by 2
psum β totalΓ·2 β Our target partition sum
bitp β β2ββ₯β£Β―1β³2*β’β΅ β All possible bit patterns up to β’β΅
idx β βΈ<\psum=bitp+.Γβ΅ β First index of partition sum = target
β¬β‘idx: β¬ β If we have no 1s, there is no solution
part β idxβ·bitp β Partition corresponding to solution index
(part/β΅)(β΅/β¨~part) β Compress input by solution pattern and inverse
}
If you come to APL from a scalar language, that approach must seem incredibly wasteful: make all bit patterns. Try all sums. Search for the right one, if it exists. But as it turns out, this is APL home turf advantage. Let’s try to demonstrate this point. If you did this “loop and branch”, you’d iterate over the bit patterns and stop once you find the first solution – in fact, for the test data in the problem specification, the first solution appears at around the 1500th bit pattern if you generate them as I do above. The vector version would need to consider the whole space of around
Β―1+2*20
1048575
a million or so, so quite a difference. Surely, in this case the scalar approach should be way faster? Only one way to find out. We can make a scalar version in several ways – here’s the “Scheme” version:
BalanceScalar β {βIOβ0 β Warning: this is not the APL Way, as we shall see.
total β +/β΅
2|total: β¬ β Sum must be divisible by 2
psum β totalΓ·2 β Our target partition sum
data β β΅
bitp β ββ2ββ₯β£Β―1β³2*β’β΅ β Pre-compute the bit patterns
{ β Try one sum after the other, halt on first solution
0=β΅: β¬
patt β β΅βbitp
psum=patt+.Γdata: (patt/data)(data/β¨~patt) β Exit on first solution found
βΒ―1+β΅
} Β―1+β’bitp
}
Dyalog’s got game when it comes to tail call optimisation, right? OK, let’s race:
'cmpx'βCY'dfns'
d β 10 81 98 27 28 5 1 46 63 99 25 39 84 87 76 85 78 64 41 93
cmpx 'Balance d' 'BalanceScalar d'
Balance d β 2.7EΒ―2 | 0% ββββββββββββββββββββββββββββ
* BalanceScalar d β 3.9EΒ―2 | +43% ββββββββββββββββββββββββββββββββββββββββ
Vectorisation, Boolean vectors and primitive functions wins the day. We didn’t go completely scalar, to be fair, as we still pre-computed all the binary patterns.
But back to the task at hand – let’s pit ourselves against the intellectual might of Horowitz and Sahni:
horowitzsahniβ{
sumβ1β₯β΅
2|sum: β¬ β Lists with an odd sum cannot be split into equal parts.
halfsumβsumΓ·2
sββ΅(β{βΊβ΅}β)β¨β2Γ·β¨β’β΅ β Split the input.
a bββΒ¨(β’,+)/Β¨s,Β¨0 β Generate the subset sums.
indexesβa {(β’,β΅β³βΊβ·β¨(β’βΊ)ββ’)1β³β¨βΊββ΅} halfsum-b β Search for solution indexes.
indexes[2]>β’b: β¬
β΅ {(βΊ/β¨~β΅)(β΅/βΊ)} β(2β΄Β¨β¨β’Β¨s)β€Β¨indexes-1 β Get the solution from the indexes.
}
cmpx 'horowitzsahni d' 'Balance d' 'BalanceScalar d'
horowitzsahni d β 4.7EΒ―5 | 0%
* Balance d β 2.8EΒ―2 | +59266% ββββββββββββββββββββββββββββ
BalanceScalar d β 4.0EΒ―2 | +84466% ββββββββββββββββββββββββββββββββββββββββ
Ouch! Well, told you my exhaustive search was naive. An impressive performance from the competitor – but also an impressive performance from Dyalog APL – even my knocked up exhaustive search runs in a pretty decent 25–30ms or so, about half the time of my shoddy Python attempt (although out-speeding Python is a low bar). I’m keeping the above implementation of Horowitz/Sahni handy for next edition of Advent of Code, where this problem always seems to crop up in some shape or form.
Problem 9 β Upwardly Mobile (1 task)
Level of Difficulty: Hard
And so for the final question. We were offered strong hints that a neat array-oriented solution might not be possible, but that the judges were prepared to be proven wrong.
Here’s a nicely compact, recursive solution:
β weights β Weights filename;diag;FindWeights;start
diag β β(β β(βUCS 10)ββ’)ββNGET filename
FindWeights β {
'βββ'ββ¨ββ΅: β1ββ΅ β if on any of these, go down
βAββ¨ββ΅: βA=ββ΅ β if on a letter, give weights
r_disp β 'β'β³β¨0β·β΅ β otherwise, (i.e. on 'β΄'), find the displacement of right branch,
l_disp β -1+'β'β³β¨β½0β·β΅ β ...and the left branch
wts β β(βr_dispβ½β΅)(βl_dispβ½β΅) β recurse,
+βΏwtsΓ[0]β½(+/wts)Γr_disp (-l_disp) β ...and calculate new weights
}
start β diagβ½β¨βΈ'β΄β'ββ¨0β·diag β starting position attained by β½'ing to 'β΄' or 'β'
weights β (~β0Γ·β¨/)FindWeights start β remove 0s and get lowest weights
β
Finally, someone took the suggestion that an array-based solution might not be possible as a personal challenge and produced the following:
Weights β {
m β β(βUCS 10)(β ββ’)ββNGET β΅ β no empty lines midway through so this is fine
fm β m='β΄' β fulcrum mask
ER β {+\1-β΅\Β―2-βΏ0βͺβΈβ΅} β distance to closest 1 to the left
wa β +/,mββA β weight amount
wi β (β³wa)@{β΅} mββA β weight indexes
fa β +/,fm β fulcrum amount
firβ wa + β³fa β fulcrum indexes (reduced)
fi β fir@{β΅} fm β fulcrum indexes
ai β fi+wi β all indexes
ai+β β(mβ'ββ') {βΊ\β΅/β¨β΅β 0}β€1β₯β 0@1β’ai β extend indexes upwards to the ββs that need them (exclude top β΄ as it isn't matched)
ld β ERβ€1β’ m='β' β distance to left
rd β β½ERβ€1β½ m='β' β distance to right
xp β (β΄m)β΄β³2ββ΄m β x position
fmlβ βfm β fulcrum mask & its lines
ailβ βai β all index lines
GETβ {β,/ailβ·β¨ββΒ¨fml/Β¨β΅} β get an item of ai for each fulcrum at x position β΅
lirβ GET βxp-ld β left indexes (reduced)
rirβ GET βxp+rd β right indexes (reduced)
ldrβ fm /β₯, ld β left distance (reduced)
rdrβ fm /β₯, rd β right distance (reduced)
in β ββ{(+/β΅[βΊ])@(ββΊ)β’ β΅}/ (βββfir lir rir) , ββ(β³fa+wa)β.=β³wa β included weights for each index
cf β (ldr Γβ€Β―1β’ in[lir;]) - rdr Γβ€Β―1β’ in[rir;] β coefficients
ws β (1,(β’cf)β΄0) βΉ ((2ββ΄cf)β1)βͺcf β unscaled weights
(β’Γ·β¨/) ws β scale weights to integers
}
I take my hat off in admiration of the audacity: “An array solution might not be possible, eh? Hold my beer.”
So there we have it, a smΓΆrgΓ₯sbord of clever solutions to serve as an inspiration for us all. The 2020 edition of the competition sported a slightly simplified format where you were expected to tackle every problem instead of the approach in previous years where you had to make a subset selection from themed groups – this new approach remains for the current (2021) edition.
You are taking part, aren’t you?