From 56d878eb0c734ba4a2c04c1be6af5dc19d753c20 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 14 Jul 2020 16:06:35 +0200 Subject: [PATCH] Add post on the Dyalog APL Competition --- posts/dyalog-apl-competition-2020.org | 311 ++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 posts/dyalog-apl-competition-2020.org diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org new file mode 100644 index 0000000..1292f3e --- /dev/null +++ b/posts/dyalog-apl-competition-2020.org @@ -0,0 +1,311 @@ +--- +title: "Dyalog APL Problem Solving Competition 2020" +subtitle: "Annotated Solutions" +date: 2020-07-31 +--- + +* Phase I + +#+begin_src default + :Namespace Phase1 +#+end_src + +** 1. Let's Split! + +#+begin_src default + split←(0>⊣)⌽((⊂↑),(⊂↓)) +#+end_src + +** 2. Character Building + +#+begin_src default + characters←{(~⍵∊127+⍳64)⊂⍵} +#+end_src + +** 3. Excel-lent Columns + +#+begin_src default + columns←26⊥⎕A∘⍳ +#+end_src + +** 4. Take a Leap + +#+begin_src default + leap←1 3∊⍨(0+.=400 100 4∘.|⊢) +#+end_src + +** 5. Stepping in the Proper Direction + +#+begin_src default + stepping←{(⊃⍵)+(-×-/⍵)×0,⍳|-/⍵} +#+end_src + +** 6. Please Move to the Front + +#+begin_src default + movefront←{⍵[⍋⍺≠⍵]} +#+end_src + +** 7. See You in a Bit + +#+begin_src default + bits←{f←⍸∘⌽(2∘⊥⍣¯1)⋄∧/(f⍺)∊f⍵} +#+end_src + +** 8. Zigzag Numbers + +#+begin_src default + zigzag←∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1) +#+end_src + +** 9. Rise and Fall + +#+begin_src default + risefall←{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵} +#+end_src + +** 10. Stacking It Up + +#+begin_src default + stacking←{↑⊃,/↓¨⍕¨⍵} +#+end_src + +#+begin_src default + :EndNamespace +#+end_src + +* Phase II + +#+begin_src default + :Namespace Contest2020 + + :Namespace Problems + (⎕IO ⎕ML ⎕WX)←1 1 3 +#+end_src + +** Problem 1 -- Take a Dive + +#+begin_src default + ∇ score←dd DiveScore scores + :If 7=≢scores + scores←scores[¯2↓2↓⍋scores] + :ElseIf 5=≢scores + scores←scores[¯1↓1↓⍋scores] + :Else + scores←scores + :EndIf + score←2(⍎⍕)dd×+/scores + ∇ +#+end_src + +** Problem 2 -- Another Step in the Proper Direction + +#+begin_src default + ∇ 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 p<0 ⍝ -⌊p is the number of equally-sized steps to take + segments←(-⌊p){0,⍵×⍺÷⍨⍳⍺}width + :ElseIf p>0 ⍝ 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 + ∇ +#+end_src + + +** Problem 3 -- Past Tasks Blast + +#+begin_src default + ∇ urls←PastTasks url;r;paths + r←HttpCommand.Get url + paths←('[a-zA-Z0-9_/]+\.pdf'⎕S'&')r.Data + urls←('https://www.dyalog.com/'∘,)¨paths + ∇ +#+end_src + +** Problem 4 -- Bioinformatics + +#+begin_src default + ⍝ Test if a DNA string is a reverse palindrome. + isrevp←{⍵≡⌽'TAGC'['ATCG'⍳⍵]} + + ⍝ Generate all subarrays (position, length) pairs, for + ⍝ 4 ≤ length ≤ 12. + subarrays←{⊃,/(⍳⍵),¨¨3↓¨⍳¨12⌊1+⍵-⍳⍵} + + ∇ r←revp dna;positions + positions←subarrays⍴dna + ⍝ Filter subarrays which are reverse palindromes. + r←↑({isrevp dna[¯1+⍵[1]+⍳⍵[2]]}¨positions)/positions + ∇ +#+end_src + +#+begin_src default + sset←{((1E6|2∘×)⍣⍵)1} +#+end_src + +** Problem 5 -- Future and Present Value + +#+begin_src default + ⍝ First solution: ((1+⊢)⊥⊣) computes the total return + ⍝ for a vector of amounts ⍺ and a vector of rates + ⍝ ⍵. It is applied to every prefix subarray of amounts + ⍝ and rates to get all intermediate values. However, + ⍝ this has quadratic complexity. + ⍝ rr←(,\⊣)((1+⊢)⊥⊣)¨(,\⊢) + + ⍝ Second solution: We want to be able to use the + ⍝ recurrence relation (recur) and scan through the + ⍝ vectors of amounts and rates, accumulating the total + ⍝ value at every time step. However, APL evaluation is + ⍝ right-associative, so a simple Scan + ⍝ (recur\amounts,¨values) would not give the correct + ⍝ result, since recur is not associative and we need + ⍝ to evaluate it left-to-right. (In any case, in this + ⍝ case, Scan would have quadratic complexity, so would + ⍝ not bring any benefit over the previous solution.) + ⍝ What we need is something akin to Haskell's scanl + ⍝ function, which would evaluate left to right in O(n) + ⍝ time. This is what we do here, accumulating values + ⍝ from left to right. (This is inspired from + ⍝ dfns.ascan, although heavily simplified.) + rr←{recur←{⍵[1]+⍺×1+⍵[2]} ⋄ 1↓⌽⊃{(⊂(⊃⍵)recur⍺),⍵}/⌽⍺,¨⍵} +#+end_src + +#+begin_src default + ⍝ Simply apply the formula for cashflow calculations. + pv←{+/⍺÷×\1+⍵} +#+end_src + +** Problem 6 -- Merge + +#+begin_src default + ∇ val←ns getval var + :If ''≡var ⍝ literal '@' + val←'@' + :ElseIf (⊂var)∊ns.⎕NL ¯2 + val←⍕ns⍎var + :Else + val←'???' + :EndIf + ∇ +#+end_src + +#+begin_src default + ∇ text←templateFile Merge jsonFile;template;ns + template←⊃⎕NGET templateFile 1 + ns←⎕JSON⊃⎕NGET jsonFile + ⍝ We use a simple regex search and replace on the + ⍝ template. + text←↑('@[a-zA-Z]*@'⎕R{ns getval ¯1↓1↓⍵.Match})template + ∇ +#+end_src + +** Problem 7 -- UPC + +#+begin_src default + CheckDigit←{10|-⍵+.×11⍴3 1} +#+end_src + +#+begin_src default + ⍝ Left and right representations of digits. Decoding + ⍝ the binary representation from decimal is more + ⍝ compact than writing everything explicitly. + lrepr←⍉(7⍴2)⊤13 25 19 61 35 49 47 59 55 11 + rrepr←~¨lrepr +#+end_src + +#+begin_src default + ∇ bits←WriteUPC digits;left;right + :If (11=≢digits)∧∧/digits∊0,⍳9 + left←,lrepr[1+6↑digits;] + right←,rrepr[1+6↓digits,CheckDigit digits;] + bits←1 0 1,left,0 1 0 1 0,right,1 0 1 + :Else + bits←¯1 + :EndIf + ∇ +#+end_src + +#+begin_src default + ∇ digits←ReadUPC bits + :If 95≠⍴bits ⍝ incorrect number of bits + digits←¯1 + :Else + ⍝ Test if the barcode was scanned right-to-left. + :If 0=2|+/bits[3+⍳7] + bits←⌽bits + :EndIf + digits←({¯1+lrepr⍳⍵}¨(7/⍳6)⊆42↑3↓bits),{¯1+rrepr⍳⍵}¨(7/⍳6)⊆¯42↑¯3↓bits + :If ~∧/digits∊0,⍳9 ⍝ incorrect parity + digits←¯1 + :ElseIf (⊃⌽digits)≠CheckDigit ¯1↓digits ⍝ incorrect check digit + digits←¯1 + :EndIf + :EndIf + ∇ +#+end_src + +** Problem 8 -- Balancing the Scales + +#+begin_src default + ∇ parts←Balance nums;subsets;partitions + ⍝ This is a brute force solution, running in + ⍝ exponential time. We generate all the possible + ⍝ partitions, filter out those which are not + ⍝ balanced, and return the first matching one. There + ⍝ are more advanced approach running in + ⍝ pseudo-polynomial time (based on dynamic + ⍝ programming, see the "Partition problem" Wikipedia + ⍝ page), but they are not warranted here, as the + ⍝ input size remains fairly small. + + ⍝ Generate all partitions of a vector of a given + ⍝ size, as binary mask vectors. + subsets←{1↓2⊥⍣¯1⍳2*⍵} + ⍝ Keep only the subsets whose sum is exactly + ⍝ (+/nums)÷2. + partitions←nums{((2÷⍨+/⍺)=⍺+.×⍵)/⍵}subsets⍴nums + :If 0=≢,partitions + ⍝ If no partition satisfy the above + ⍝ criterion, we return ⍬. + parts←⍬ + :Else + ⍝ Otherwise, we return the first possible + ⍝ partition. + parts←nums{((⊂,(⊂~))⊃↓⍉⍵)/¨2⍴⊂⍺}partitions + :EndIf + ∇ +#+end_src + +** Problem 9 -- Upwardly Mobile + +#+begin_src default + ∇ weights←Weights filename;mobile;branches;mat + ⍝ Put your code and comments below here + + ⍝ Parse the mobile input file. + mobile←↑⊃⎕NGET filename 1 + branches←⍸mobile∊'┌┴┐' + ⍝ TODO: Build the matrix of coefficients mat. + + ⍝ Solve the system of equations (arbitrarily setting + ⍝ the first variable at 1 because the system is + ⍝ overdetermined), then multiply the coefficients by + ⍝ their least common multiple to get the smallest + ⍝ integer weights. + weights←((1∘,)×(∧/÷))mat[;1]⌹1↓[2]mat + ∇ +#+end_src + +#+begin_src default + :EndNamespace + :EndNamespace +#+end_src + +* General Remarks