diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020-phase-1.org similarity index 67% rename from posts/dyalog-apl-competition-2020.org rename to posts/dyalog-apl-competition-2020-phase-1.org index 150254b..ae11d37 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020-phase-1.org @@ -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←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 diff --git a/posts/dyalog-apl-competition-2020-phase-2.org b/posts/dyalog-apl-competition-2020-phase-2.org new file mode 100644 index 0000000..e0c8576 --- /dev/null +++ b/posts/dyalog-apl-competition-2020-phase-2.org @@ -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←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