--- title: "Dyalog APL Problem Solving Competition 2020 — Phase II" subtitle: "Annotated Solutions" date: 2020-08-02 toc: true --- * Introduction After [[./dyalog-apl-competition-2020-phase-1.html][Phase I]], here are my solutions to Phase II problems. The full code is included in the post, but everything is also available [[https://github.com/dlozeve/apl-competition-2020][on GitHub]]. A PDF of the problems descriptions is available on [[https://www.dyalogaplcompetition.com/][the competition website]], or directly from [[https://github.com/dlozeve/apl-competition-2020/blob/master/Contest2020/2020%20APL%20Problem%20Solving%20Competition%20Phase%20II%20Problems.pdf][my GitHub repo]]. The submission guidelines gave a template where everything is defined in a ~Contest2020.Problems~ Namespace. I kept the default values for ~⎕IO~ and ~⎕ML~ because the problems were not particularly easier with ~⎕IO←0~. #+begin_src default :Namespace Contest2020 :Namespace Problems (⎕IO ⎕ML ⎕WX)←1 1 3 #+end_src #+begin_quote This post is still a work in progress! I will try to write explanations for every problem below. #+end_quote * 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