Split into two different posts

This commit is contained in:
Dimitri Lozeve 2020-07-21 17:38:18 +02:00
parent 44f662be42
commit 5be4981b16
2 changed files with 249 additions and 248 deletions

View file

@ -1,17 +1,15 @@
---
title: "Dyalog APL Problem Solving Competition 2020"
title: "Dyalog APL Problem Solving Competition 2020 — Phase I"
subtitle: "Annotated Solutions"
date: 2020-07-31
toc: true
---
* Phase I
#+begin_src default
:Namespace Phase1
#+end_src
** 1. Let's Split!
* 1. Let's Split!
#+begin_quote
Write a function that, given a right argument ~Y~ which is a scalar or
@ -43,7 +41,7 @@ The second train ~(0>⊣)~ will return 1 if its left argument is
positive. From this, we can use [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Rotate.htm][Rotate]] (~⌽~) to correctly order the
nested array, in the last train.
** 2. Character Building
* 2. Character Building
#+begin_quote
UTF-8 encodes Unicode characters using 1-4 integers for each
@ -85,7 +83,7 @@ continuation character as 0, and all the others as 1. Next, we can use
this binary array with [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Partitioned%20Enclose.htm][Partitioned Enclose]] (~⊂~) to return the correct
output.
** 3. Excel-lent Columns
* 3. Excel-lent Columns
#+begin_quote
A Microsoft Excel spreadsheet numbers its rows counting up
@ -106,7 +104,7 @@ the alphabet of every character. As a train, this can be done by
letter from 1 to 26. The [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Decode.htm][Decode]] (~⊥~) function can then turn this
base-26 number into the expected result.
** 4. Take a Leap
* 4. Take a Leap
#+begin_quote
Write a function that, given a right argument which is an integer
@ -130,7 +128,7 @@ divisible by one of these numbers. If the count is 1 or 3, it is a
leap year. Note that we use [[https://help.dyalog.com/latest/#Language/Primitive%20Operators/Commute.htm][Commute]] (~⍨~) to keep the dfn as a train,
and to preserve the natural right-to-left reading of the algorithm.
** 5. Stepping in the Proper Direction
* 5. Stepping in the Proper Direction
#+begin_quote
Write a function that, given a right argument of 2 integers, returns a
@ -151,7 +149,7 @@ decreasing if needed, so we multiply it by the opposite of the sign of
~-/⍵~. Finally, we just have to start the sequence at the first
element of ~⍵~.
** 6. Please Move to the Front
* 6. Please Move to the Front
#+begin_quote
Write a function that, given a right argument which is an integer
@ -166,7 +164,7 @@ while all other elements keep their order.
the left argument. Using this index to sort in the usual way with
[[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Grade%20Up%20Monadic.htm][Grade Up]] will return the expected result.
** 7. See You in a Bit
* 7. See You in a Bit
#+begin_quote
A common technique for encoding a set of on/off states is to use a
@ -198,7 +196,7 @@ case). That is what the function ~f~ does.
Next, we just need to check that all elements of ~f~ are also in
~f⍵~.
** 8. Zigzag Numbers
* 8. Zigzag Numbers
#+begin_quote
A zigzag number is an integer in which the difference in magnitude of
@ -217,7 +215,7 @@ First, we decompose a number into an array of digits, using
compute the difference between each pair of digits, take the sign, and
ensure that the signs are indeed alternating.
** 9. Rise and Fall
* 9. Rise and Fall
#+begin_quote
Write a function that, given a right argument which is an integer
@ -256,7 +254,7 @@ Next, ~(⍳∘≢≡⍋)~ on each of the two vectors will test if they are
non-decreasing (i.e. if the ranks of all the elements correspond to a
simple range from 1 to the size of the vector).
** 10. Stacking It Up
* 10. Stacking It Up
#+begin_quote
Write a function that takes as its right argument a vector of simple
@ -317,238 +315,3 @@ final result.
: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

View file

@ -0,0 +1,238 @@
---
title: "Dyalog APL Problem Solving Competition 2020 — Phase II"
subtitle: "Annotated Solutions"
date: 2020-07-31
toc: true
---
#+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