blog/posts/dyalog-apl-competition-2020.org

311 lines
8 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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←subarraysdna
⍝ 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|-⍵+.×113 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←⍉(72)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⊥⍣¯12*⍵}
⍝ Keep only the subsets whose sum is exactly
⍝ (+/nums)÷2.
partitions←nums{((2÷⍨+/)=+.×⍵)/⍵}subsetsnums
: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