From 56d878eb0c734ba4a2c04c1be6af5dc19d753c20 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 14 Jul 2020 16:06:35 +0200 Subject: [PATCH 01/12] 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 From ea20c7bd3912dfa8e556cd9e1c8e21a88ec991a7 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 14 Jul 2020 16:21:34 +0200 Subject: [PATCH 02/12] Add Phase I problems descriptions --- posts/dyalog-apl-competition-2020.org | 122 ++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org index 1292f3e..5f7bd90 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020.org @@ -12,60 +12,182 @@ date: 2020-07-31 ** 1. Let's Split! +#+begin_quote +Write a function that, given a right argument ~Y~ which is a scalar or +a non-empty vector and a left argument ~X~ which is a single non-zero +integer so that its absolute value is less or equal to ~≢Y~, splits +~Y~ into a vector of two vectors according to ~X~, as follows: +- If ~X>0~, the first vector contains the first ~X~ elements of ~Y~ + and the second vector contains the remaining elements. +- If ~X<0~, the second vector contains the last ~|X~ elements of ~Y~ + and the first vector contains the remaining elements. +#+end_quote + #+begin_src default split←(0>⊣)⌽((⊂↑),(⊂↓)) #+end_src ** 2. Character Building +#+begin_quote +UTF-8 encodes Unicode characters using 1-4 integers for each +character. Dyalog APL includes a system function, ~⎕UCS~, that can +convert characters into integers and integers into characters. The +expression ~'UTF-8'∘⎕UCS~ converts between characters and UTF-8. + +Consider the following: + + #+begin_src default + 'UTF-8'∘⎕UCS 'D¥⍺⌊○9' +68 194 165 226 141 186 226 140 138 226 151 139 57 + 'UTF-8'∘⎕UCS 68 194 165 226 141 186 226 140 138 226 151 139 57 +D¥⍺⌊○9 + #+end_src + +How many integers does each character use? + + #+begin_src default + 'UTF-8'∘⎕UCS¨ 'D¥⍺⌊○9' ⍝ using ]Boxing on +┌──┬───────┬───────────┬───────────┬───────────┬──┐ +│68│194 165│226 141 186│226 140 138│226 151 139│57│ +└──┴───────┴───────────┴───────────┴───────────┴──┘ + #+end_src + +The rule is that an integer in the range 128 to 191 (inclusive) +continues the character of the previous integer (which may itself be a +continuation). With that in mind, write a function that, given a right +argument which is a simple integer vector representing valid UTF-8 +text, encloses each sequence of integers that represent a single +character, like the result of ~'UTF-8'∘⎕UCS¨'UTF-8'∘⎕UCS~ but does not +use any system functions (names beginning with ~⎕~) +#+end_quote + #+begin_src default characters←{(~⍵∊127+⍳64)⊂⍵} #+end_src ** 3. Excel-lent Columns +#+begin_quote +A Microsoft Excel spreadsheet numbers its rows counting up +from 1. However Excel's columns are labelled alphabetically — +beginning with A–Z, then AA–AZ, BA–BZ, up to ZA–ZZ, then AAA–AAZ and +so on. + +Write a function that, given a right argument which is a character +scalar or non-empty vector representing a valid character Excel column +identifier between A and XFD, returns the corresponding column number +#+end_quote + #+begin_src default columns←26⊥⎕A∘⍳ #+end_src ** 4. Take a Leap +#+begin_quote +Write a function that, given a right argument which is an integer +array of year numbers greater than or equal to 1752 and less than +4000, returns a result of the same shape as the right argument where 1 +indicates that the corresponding year is a leap year (0 otherwise). + +A leap year algorithm can be found [[https://en.wikipedia.org/wiki/Leap_year#Algorithm][here]]. +#+end_quote + #+begin_src default leap←1 3∊⍨(0+.=400 100 4∘.|⊢) #+end_src ** 5. Stepping in the Proper Direction +#+begin_quote +Write a function that, given a right argument of 2 integers, returns a +vector of the integers from the first element of the right argument to +the second, inclusively. +#+end_quote + #+begin_src default stepping←{(⊃⍵)+(-×-/⍵)×0,⍳|-/⍵} #+end_src ** 6. Please Move to the Front +#+begin_quote +Write a function that, given a right argument which is an integer +vector and a left argument which is an integer scalar, reorders the +right argument so any elements equal to the left argument come first +while all other elements keep their order. +#+end_quote + #+begin_src default movefront←{⍵[⍋⍺≠⍵]} #+end_src ** 7. See You in a Bit +#+begin_quote +A common technique for encoding a set of on/off states is to use a +value of $2^n$ for the state in position $n$ (origin 0), 1 if the +state is "on" or 0 for "off" and then add the values. Dyalog APL's +[[https://help.dyalog.com/17.1/#Language/APL%20Component%20Files/Component%20Files.htm#File_Access_Control][component file permission codes]] are an example of this. For example, +if you wanted to grant permissions for read (access code 1), append +(access code 8) and rename (access code 128) then the resulting code +would be 137 because that's 1 + 8 + 128. + +Write a function that, given a non-negative right argument which is an +integer scalar representing the encoded state and a left argument +which is an integer scalar representing the encoded state settings +that you want to query, returns 1 if all of the codes in the left +argument are found in the right argument (0 otherwise). +#+end_quote + #+begin_src default bits←{f←⍸∘⌽(2∘⊥⍣¯1)⋄∧/(f⍺)∊f⍵} #+end_src ** 8. Zigzag Numbers +#+begin_quote +A zigzag number is an integer in which the difference in magnitude of +each pair of consecutive digits alternates from positive to negative +or negative to positive. + +Write a function that takes a single integer greater than or equal to +100 and less than 10^{15} as its right argument and returns a 1 if the +integer is a zigzag number, 0 otherwise. +#+end_quote + #+begin_src default zigzag←∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1) #+end_src ** 9. Rise and Fall +#+begin_quote +Write a function that, given a right argument which is an integer +scalar or vector, returns a 1 if the values of the right argument +conform to the following pattern (0 otherwise): + +- The elements increase or stay the same until the "apex" (highest + value) is reached +- After the apex, any remaining values decrease or remain the same +#+end_quote + #+begin_src default risefall←{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵} #+end_src ** 10. Stacking It Up +#+begin_quote +Write a function that takes as its right argument a vector of simple +arrays of rank 2 or less (scalar, vector, or matrix). Each simple +array will consist of either non-negative integers or printable ASCII +characters. The function must return a simple character array that +displays identically to what ~{⎕←⍵}¨~ displays when applied to the +right argument. +#+end_quote + #+begin_src default stacking←{↑⊃,/↓¨⍕¨⍵} #+end_src From 7edc20f556d0b085a970338ac5abf39243901a43 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Wed, 15 Jul 2020 16:05:57 +0200 Subject: [PATCH 03/12] Add explanations for some Phase I problems --- posts/dyalog-apl-competition-2020.org | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org index 5f7bd90..7b8cfda 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020.org @@ -27,6 +27,17 @@ integer so that its absolute value is less or equal to ~≢Y~, splits split←(0>⊣)⌽((⊂↑),(⊂↓)) #+end_src +There are three nested trains here. The first one, ~((⊂↑),(⊂↓))~, uses +the two functions [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Take.htm][Take]] (~↑~) and [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Drop.htm][Drop]] (~↓~) to build a nested array +consisting of the two outputs we need. (Take and Drop already have the +behaviour needed regarding negative arguments.) However, if the left +argument is positive, the two arrays will not be in the correct +order. So we need a way to reverse them if ~X<0~. + +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 #+begin_quote @@ -66,6 +77,11 @@ use any system functions (names beginning with ~⎕~) characters←{(~⍵∊127+⍳64)⊂⍵} #+end_src +First, we build a binary array from the string, encoding each +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 #+begin_quote @@ -83,6 +99,12 @@ identifier between A and XFD, returns the corresponding column number columns←26⊥⎕A∘⍳ #+end_src +We use the alphabet ~⎕A~ and [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Index%20Of.htm][Index Of]] (~⍳~) to compute the index in +the alphabet of every character. As a train, this can be done by +~(⎕A∘⍳)~. We then obtain an array of numbers, each representing a +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 #+begin_quote @@ -98,6 +120,17 @@ A leap year algorithm can be found [[https://en.wikipedia.org/wiki/Leap_year#Alg leap←1 3∊⍨(0+.=400 100 4∘.|⊢) #+end_src +According to the algorithm, a year is a leap year in two situations: +- if it is divisible by 4, but not 100 (and therefore not 400), +- if it is divisible by 400 (and therefore 4 and 100 as well). + +The train ~(400 100 4∘.|⊢)~ will test if each year in the right +argument is divisible by 400, 100, and 4, using an [[https://help.dyalog.com/latest/#Language/Primitive%20Operators/Outer%20Product.htm][Outer Product]]. We +then use an [[https://help.dyalog.com/latest/#Language/Primitive%20Operators/Inner%20Product.htm][Inner Product]] to count how many times each year is +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 #+begin_quote @@ -110,6 +143,15 @@ the second, inclusively. stepping←{(⊃⍵)+(-×-/⍵)×0,⍳|-/⍵} #+end_src +First, we have to compute the range of the output, which is the +absolute value of the difference between the two integers ~|-/⍵~. From +this, we compute the actual sequence, including zero: ~0,⍳|-/⍵~. + +This sequence will always be nondecreasing, but we have to make it +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 #+begin_quote @@ -123,6 +165,10 @@ while all other elements keep their order. movefront←{⍵[⍋⍺≠⍵]} #+end_src +~⍺≠⍵~ will return a binary vector marking as 0 all elements equal to +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 #+begin_quote @@ -145,6 +191,18 @@ argument are found in the right argument (0 otherwise). bits←{f←⍸∘⌽(2∘⊥⍣¯1)⋄∧/(f⍺)∊f⍵} #+end_src +The difficult part is to find the set of states for an integer. We +need a function that will return ~1 8 128~ (or an equivalent +representation) for an input of ~137~. To do this, we need the base-2 +representations of $137 = 1 + 8 + 128 = 2^0 + 2^3 + 2^7 = +10010001_2$. The function ~(2∘⊥⍣¯1)~ will return the base-2 +representation of its argument, and by [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Reverse.htm][reversing]] and finding [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Where.htm][where]] the +non-zero elements are, we find the correct exponents (~1 3 7~ in this +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 #+begin_quote @@ -161,6 +219,8 @@ integer is a zigzag number, 0 otherwise. zigzag←∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1) #+end_src +TODO + ** 9. Rise and Fall #+begin_quote @@ -177,6 +237,8 @@ conform to the following pattern (0 otherwise): risefall←{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵} #+end_src +TODO + ** 10. Stacking It Up #+begin_quote @@ -192,6 +254,8 @@ right argument. stacking←{↑⊃,/↓¨⍕¨⍵} #+end_src +TODO + #+begin_src default :EndNamespace #+end_src From 02a1a96ed676df2bf003b9e08e2d77e7a5d6519e Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Fri, 17 Jul 2020 16:49:35 +0200 Subject: [PATCH 04/12] Better display of problems and solutions --- css/default.css | 8 ++++++ posts/dyalog-apl-competition-2020.org | 40 +++++++-------------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/css/default.css b/css/default.css index 6fc94b7..6ea8f3f 100644 --- a/css/default.css +++ b/css/default.css @@ -42,6 +42,14 @@ article .header { /* background: #eee; */ /* } */ +blockquote { + display: block; + border-left: 2px solid #808080; + padding-left: 1rem; + margin-top: 1rem; + margin-bottom: 1rem; +} + .definition, .proposition, .theorem { display: block; border-left: 2px solid #808080; diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org index 7b8cfda..b9a5ecb 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020.org @@ -23,9 +23,7 @@ integer so that its absolute value is less or equal to ~≢Y~, splits and the first vector contains the remaining elements. #+end_quote -#+begin_src default - split←(0>⊣)⌽((⊂↑),(⊂↓)) -#+end_src +*Solution:* ~(0>⊣)⌽((⊂↑),(⊂↓))~ There are three nested trains here. The first one, ~((⊂↑),(⊂↓))~, uses the two functions [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Take.htm][Take]] (~↑~) and [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Drop.htm][Drop]] (~↓~) to build a nested array @@ -73,9 +71,7 @@ character, like the result of ~'UTF-8'∘⎕UCS¨'UTF-8'∘⎕UCS~ but does not use any system functions (names beginning with ~⎕~) #+end_quote -#+begin_src default - characters←{(~⍵∊127+⍳64)⊂⍵} -#+end_src +*Solution:* ~{(~⍵∊127+⍳64)⊂⍵}~ First, we build a binary array from the string, encoding each continuation character as 0, and all the others as 1. Next, we can use @@ -95,9 +91,7 @@ scalar or non-empty vector representing a valid character Excel column identifier between A and XFD, returns the corresponding column number #+end_quote -#+begin_src default - columns←26⊥⎕A∘⍳ -#+end_src +*Solution:* ~26⊥⎕A∘⍳~ We use the alphabet ~⎕A~ and [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Index%20Of.htm][Index Of]] (~⍳~) to compute the index in the alphabet of every character. As a train, this can be done by @@ -116,9 +110,7 @@ indicates that the corresponding year is a leap year (0 otherwise). A leap year algorithm can be found [[https://en.wikipedia.org/wiki/Leap_year#Algorithm][here]]. #+end_quote -#+begin_src default - leap←1 3∊⍨(0+.=400 100 4∘.|⊢) -#+end_src +*Solution:* ~1 3∊⍨(0+.=400 100 4∘.|⊢)~ According to the algorithm, a year is a leap year in two situations: - if it is divisible by 4, but not 100 (and therefore not 400), @@ -139,9 +131,7 @@ vector of the integers from the first element of the right argument to the second, inclusively. #+end_quote -#+begin_src default - stepping←{(⊃⍵)+(-×-/⍵)×0,⍳|-/⍵} -#+end_src +*Solution:* ~{(⊃⍵)+(-×-/⍵)×0,⍳|-/⍵}~ First, we have to compute the range of the output, which is the absolute value of the difference between the two integers ~|-/⍵~. From @@ -161,9 +151,7 @@ right argument so any elements equal to the left argument come first while all other elements keep their order. #+end_quote -#+begin_src default - movefront←{⍵[⍋⍺≠⍵]} -#+end_src +*Solution:* ~{⍵[⍋⍺≠⍵]}~ ~⍺≠⍵~ will return a binary vector marking as 0 all elements equal to the left argument. Using this index to sort in the usual way with @@ -187,9 +175,7 @@ that you want to query, returns 1 if all of the codes in the left argument are found in the right argument (0 otherwise). #+end_quote -#+begin_src default - bits←{f←⍸∘⌽(2∘⊥⍣¯1)⋄∧/(f⍺)∊f⍵} -#+end_src +*Solution:* ~{f←⍸∘⌽(2∘⊥⍣¯1)⋄∧/(f⍺)∊f⍵}~ The difficult part is to find the set of states for an integer. We need a function that will return ~1 8 128~ (or an equivalent @@ -215,9 +201,7 @@ Write a function that takes a single integer greater than or equal to integer is a zigzag number, 0 otherwise. #+end_quote -#+begin_src default - zigzag←∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1) -#+end_src +*Solution:* ~∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1)~ TODO @@ -233,9 +217,7 @@ conform to the following pattern (0 otherwise): - After the apex, any remaining values decrease or remain the same #+end_quote -#+begin_src default - risefall←{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵} -#+end_src +*Solution:* ~{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵}~ TODO @@ -250,9 +232,7 @@ displays identically to what ~{⎕←⍵}¨~ displays when applied to the right argument. #+end_quote -#+begin_src default - stacking←{↑⊃,/↓¨⍕¨⍵} -#+end_src +*Solution:* ~{↑⊃,/↓¨⍕¨⍵}~ TODO From c5bbfe81513a0022bf414f36fd5e7897272a30b8 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Mon, 20 Jul 2020 10:40:08 +0200 Subject: [PATCH 05/12] Add table of contents --- posts/dyalog-apl-competition-2020.org | 1 + 1 file changed, 1 insertion(+) diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org index b9a5ecb..b70eec3 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020.org @@ -2,6 +2,7 @@ title: "Dyalog APL Problem Solving Competition 2020" subtitle: "Annotated Solutions" date: 2020-07-31 +toc: true --- * Phase I From 1314b2c26d6230d5bfbfd2356adb03ae9715bcc8 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 21 Jul 2020 17:00:57 +0200 Subject: [PATCH 06/12] Finish explanations of Phase I problems --- posts/dyalog-apl-competition-2020.org | 69 +++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org index b70eec3..7782623 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020.org @@ -204,7 +204,10 @@ integer is a zigzag number, 0 otherwise. *Solution:* ~∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1)~ -TODO +First, we decompose a number into an array of digits, using +~(10∘⊥⍣¯1)~ ([[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Decode.htm][Decode]] (~⊥~) in base 10). Then, we [[https://help.dyalog.com/latest/#Language/Primitive%20Operators/Reduce%20N%20Wise.htm][Reduce N Wise]] to +compute the difference between each pair of digits, take the sign, and +ensure that the signs are indeed alternating. ** 9. Rise and Fall @@ -220,7 +223,30 @@ conform to the following pattern (0 otherwise): *Solution:* ~{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵}~ -TODO +How do we approach this? First we have to split the vector at the +"apex". The train ~(⊢⍳⌈/)~ will return the [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Index%20Of.htm][index of]] (~⍳~) the maximum +element. + +#+begin_src default + (⊢⍳⌈/)1 3 3 4 5 2 1 +5 +#+end_src + +Combined with [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Take.htm][Take]] (~↑~) and [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Drop.htm][Drop]] (~↓~), we build a two-element vector +containing both parts, in ascending order (we [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Reverse.htm][Reverse]] (~⌽~) one of +them). Note that we have to [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Ravel.htm][Ravel]] (~,~) the argument to avoid rank +errors in Index Of. + +#+begin_src default + {(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵}1 3 3 4 5 2 1 +┌─────────┬───┐ +│1 3 3 4 5│1 2│ +└─────────┴───┘ +#+end_src + +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 @@ -235,7 +261,44 @@ right argument. *Solution:* ~{↑⊃,/↓¨⍕¨⍵}~ -TODO +So this is a little bit by trial-and-error. The first step is to +[[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Format%20Monadic.htm][Format]] (~⍕~) everything to get strings. The next step would be to +"stack everything vertically", so we will need [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Mix.htm][Mix]] (~↑~) at some +point. However, if we do it immediately we don't get the correct +result: + +#+begin_src default + {↑⍕¨⍵}(3 3⍴⍳9)(↑'Adam' 'Michael') +1 2 3 +4 5 6 +7 8 9 + +Adam +Michael +#+end_src + +Mix is padding with spaces both horizontally (necessary as we want the +output to be a simple array of characters) and vertically (not what we +want). We will have to decompose everything line by line, and then mix +all the lines together. This is exactly what [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Split.htm][Split]] (~↓~) does: + +#+begin_src default + {↓¨⍕¨⍵}(3 3⍴⍳9)(↑'Adam' 'Michael')(⍳10) '*'(5 5⍴⍳25) +┌───────────────────┬─────────────────┬──────────────────────┬─┬─────────────── +│┌─────┬─────┬─────┐│┌───────┬───────┐│┌────────────────────┐│*│┌────────────── +││1 2 3│4 5 6│7 8 9│││Adam │Michael│││1 2 3 4 5 6 7 8 9 10││ ││ 1 2 3 4 5 +│└─────┴─────┴─────┘│└───────┴───────┘│└────────────────────┘│ │└────────────── +└───────────────────┴─────────────────┴──────────────────────┴─┴─────────────── + + ─────────────────────────────────────────────────────────────┐ + ┬──────────────┬──────────────┬──────────────┬──────────────┐│ + │ 6 7 8 9 10│11 12 13 14 15│16 17 18 19 20│21 22 23 24 25││ + ┴──────────────┴──────────────┴──────────────┴──────────────┘│ + ─────────────────────────────────────────────────────────────┘ +#+end_src + +Next, we clean this up with Ravel (~,~) and we can Mix to obtain the +final result. #+begin_src default :EndNamespace From 44f662be42fd0e188bbc53a44f6b69ddc372a075 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 21 Jul 2020 17:33:54 +0200 Subject: [PATCH 07/12] Add some additional remarks --- posts/dyalog-apl-competition-2020.org | 47 +++++++++++++++++---------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/posts/dyalog-apl-competition-2020.org b/posts/dyalog-apl-competition-2020.org index 7782623..150254b 100644 --- a/posts/dyalog-apl-competition-2020.org +++ b/posts/dyalog-apl-competition-2020.org @@ -18,20 +18,26 @@ Write a function that, given a right argument ~Y~ which is a scalar or a non-empty vector and a left argument ~X~ which is a single non-zero integer so that its absolute value is less or equal to ~≢Y~, splits ~Y~ into a vector of two vectors according to ~X~, as follows: -- If ~X>0~, the first vector contains the first ~X~ elements of ~Y~ - and the second vector contains the remaining elements. -- If ~X<0~, the second vector contains the last ~|X~ elements of ~Y~ - and the first vector contains the remaining elements. + +If ~X>0~, the first vector contains the first ~X~ elements of ~Y~ and +the second vector contains the remaining elements. + +If ~X<0~, the second vector contains the last ~|X~ elements of ~Y~ and +the first vector contains the remaining elements. #+end_quote *Solution:* ~(0>⊣)⌽((⊂↑),(⊂↓))~ -There are three nested trains here. The first one, ~((⊂↑),(⊂↓))~, uses -the two functions [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Take.htm][Take]] (~↑~) and [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Drop.htm][Drop]] (~↓~) to build a nested array -consisting of the two outputs we need. (Take and Drop already have the -behaviour needed regarding negative arguments.) However, if the left -argument is positive, the two arrays will not be in the correct -order. So we need a way to reverse them if ~X<0~. +There are three nested trains here[fn:trains]. The first one, +~((⊂↑),(⊂↓))~, uses the two functions [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Take.htm][Take]] (~↑~) and [[https://help.dyalog.com/18.0/index.htm#Language/Primitive%20Functions/Drop.htm][Drop]] (~↓~) to +build a nested array consisting of the two outputs we need. (Take and +Drop already have the behaviour needed regarding negative arguments.) +However, if the left argument is positive, the two arrays will not be +in the correct order. So we need a way to reverse them if ~X<0~. + +[fn:trains] Trains are nice to read (even if they are easy to abuse), +and generally make for shorter dfns, which is better for Phase I. + 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 @@ -136,7 +142,9 @@ the second, inclusively. First, we have to compute the range of the output, which is the absolute value of the difference between the two integers ~|-/⍵~. From -this, we compute the actual sequence, including zero: ~0,⍳|-/⍵~. +this, we compute the actual sequence, including zero[fn::If we had +~⎕IO←0~, we could have written ~⍳|1+-/⍵~, but this is the same number +of characters.]: ~0,⍳|-/⍵~. This sequence will always be nondecreasing, but we have to make it decreasing if needed, so we multiply it by the opposite of the sign of @@ -261,11 +269,15 @@ right argument. *Solution:* ~{↑⊃,/↓¨⍕¨⍵}~ -So this is a little bit by trial-and-error. The first step is to -[[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Format%20Monadic.htm][Format]] (~⍕~) everything to get strings. The next step would be to -"stack everything vertically", so we will need [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Mix.htm][Mix]] (~↑~) at some -point. However, if we do it immediately we don't get the correct -result: +The first step is to [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Format%20Monadic.htm][Format]] (~⍕~) everything to get +strings.[fn:trial-error] The next step would be to "stack everything +vertically", so we will need [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Mix.htm][Mix]] (~↑~) at some point. However, if we +do it immediately we don't get the correct result: + +[fn:trial-error] {-} A lot of trial-and-error is always necessary when +dealing with nested arrays, and this being about formatting +exacerbates the problem. + #+begin_src default {↑⍕¨⍵}(3 3⍴⍳9)(↑'Adam' 'Michael') @@ -280,7 +292,8 @@ Michael Mix is padding with spaces both horizontally (necessary as we want the output to be a simple array of characters) and vertically (not what we want). We will have to decompose everything line by line, and then mix -all the lines together. This is exactly what [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Split.htm][Split]] (~↓~) does: +all the lines together. This is exactly what [[https://help.dyalog.com/latest/#Language/Primitive%20Functions/Split.htm][Split]][fn::Split is the +dual of Mix.] (~↓~) does: #+begin_src default {↓¨⍕¨⍵}(3 3⍴⍳9)(↑'Adam' 'Michael')(⍳10) '*'(5 5⍴⍳25) From 5be4981b162ddfdced686bad701af91b8c385383 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 21 Jul 2020 17:38:18 +0200 Subject: [PATCH 08/12] Split into two different posts --- ...> dyalog-apl-competition-2020-phase-1.org} | 259 +----------------- posts/dyalog-apl-competition-2020-phase-2.org | 238 ++++++++++++++++ 2 files changed, 249 insertions(+), 248 deletions(-) rename posts/{dyalog-apl-competition-2020.org => dyalog-apl-competition-2020-phase-1.org} (67%) create mode 100644 posts/dyalog-apl-competition-2020-phase-2.org 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 From 94512e0297e006358f262a02d5ef5e8902978a36 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Tue, 21 Jul 2020 17:51:17 +0200 Subject: [PATCH 09/12] Add background to blockquotes --- css/default.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/css/default.css b/css/default.css index 6ea8f3f..d3df289 100644 --- a/css/default.css +++ b/css/default.css @@ -48,6 +48,8 @@ blockquote { padding-left: 1rem; margin-top: 1rem; margin-bottom: 1rem; + color: #333; + background: #eeeeee; } .definition, .proposition, .theorem { From 28e381d7bd98e9d7035f7fa7b0a925f507b7ea93 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Sun, 2 Aug 2020 12:56:23 +0200 Subject: [PATCH 10/12] Finish post for Phase I --- posts/dyalog-apl-competition-2020-phase-1.org | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/posts/dyalog-apl-competition-2020-phase-1.org b/posts/dyalog-apl-competition-2020-phase-1.org index ae11d37..61ac03f 100644 --- a/posts/dyalog-apl-competition-2020-phase-1.org +++ b/posts/dyalog-apl-competition-2020-phase-1.org @@ -1,13 +1,45 @@ --- title: "Dyalog APL Problem Solving Competition 2020 — Phase I" subtitle: "Annotated Solutions" -date: 2020-07-31 +date: 2020-08-02 toc: true --- -#+begin_src default - :Namespace Phase1 -#+end_src +* Introduction + +I've always been quite fond of [[https://en.wikipedia.org/wiki/APL_(programming_language)][APL]] and its "array-oriented" approach +of programming[fn:previous-post]. Every year, [[https://www.dyalog.com/][Dyalog]] (the company +behind probably the most popular APL implementation) organises a +competition with various challenges in APL. + +[fn:previous-post] See my [[./ising-apl.html][previous post]] on simulating the Ising model +with APL. It also contains more background on APL. + + +The [[https://www.dyalogaplcompetition.com/][Dyalog APL Problem Solving Competition]] consists of two phases: +- Phase I consists of 10 short puzzles (similar to what one can find + on [[https://projecteuler.net/][Project Euler]] or similar), that can be solved by a one-line APL + function. +- Phase II is a collection of larger problems, that may require longer + solutions and a larger context (e.g. reading and writing to files), + often in a more applied setting. Problems are often inspired by + existing domains, such as AI, bioinformatics, and so on. + +In 2018, I participated in the competition, entering only Phase +I[fn:2018-competition] (my solutions are on [[https://github.com/dlozeve/apl-competition-2018][GitHub]]). This year, I +entered in both phases. I explain my solutions to Phase I in this +post. Another post will contain annotated solutions for Phase II +problems. + +[fn:2018-competition] Since I was a student at the time, I was +eligible for a prize, and [[https://www.dyalog.com/nnews/128/456/Winners-Announced-for-the-2018-APL-Programming-Contest.htm][I won $100]] for a 10-line submission, which +is quite good! + + +The full code for my submission is on GitHub at +[[https://github.com/dlozeve/apl-competition-2020][dlozeve/apl-competition-2020]], but everything is reproduced in this +post. + * 1. Let's Split! @@ -87,7 +119,7 @@ output. #+begin_quote A Microsoft Excel spreadsheet numbers its rows counting up -from 1. However Excel's columns are labelled alphabetically — +from 1. However, Excel's columns are labelled alphabetically — beginning with A–Z, then AA–AZ, BA–BZ, up to ZA–ZZ, then AAA–AAZ and so on. @@ -222,7 +254,7 @@ Write a function that, given a right argument which is an integer scalar or vector, returns a 1 if the values of the right argument conform to the following pattern (0 otherwise): -- The elements increase or stay the same until the "apex" (highest +- The elements increase or stay the same until the "apex" (the highest value) is reached - After the apex, any remaining values decrease or remain the same #+end_quote @@ -310,8 +342,3 @@ dual of Mix.] (~↓~) does: Next, we clean this up with Ravel (~,~) and we can Mix to obtain the final result. - -#+begin_src default - :EndNamespace -#+end_src - From 8b870f1dc59b914c901949e6db6534003753a525 Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Sun, 2 Aug 2020 13:07:47 +0200 Subject: [PATCH 11/12] Update intro for Phase II post --- posts/dyalog-apl-competition-2020-phase-2.org | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/posts/dyalog-apl-competition-2020-phase-2.org b/posts/dyalog-apl-competition-2020-phase-2.org index e0c8576..4860f89 100644 --- a/posts/dyalog-apl-competition-2020-phase-2.org +++ b/posts/dyalog-apl-competition-2020-phase-2.org @@ -1,10 +1,24 @@ --- title: "Dyalog APL Problem Solving Competition 2020 — Phase II" subtitle: "Annotated Solutions" -date: 2020-07-31 +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 @@ -12,6 +26,11 @@ toc: true (⎕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 @@ -46,7 +65,6 @@ toc: true ∇ #+end_src - * Problem 3 -- Past Tasks Blast #+begin_src default From e43eafdae4c7ed148a61a1ce2bbe7f98cca149dc Mon Sep 17 00:00:00 2001 From: Dimitri Lozeve Date: Sun, 2 Aug 2020 13:08:29 +0200 Subject: [PATCH 12/12] Generate pages --- _site/archive.html | 8 + _site/atom.xml | 651 ++++++++++-------- _site/css/default.css | 2 +- _site/index.html | 16 +- .../dyalog-apl-competition-2020-phase-1.html | 219 ++++++ .../dyalog-apl-competition-2020-phase-2.html | 256 +++++++ _site/rss.xml | 651 ++++++++++-------- 7 files changed, 1240 insertions(+), 563 deletions(-) create mode 100644 _site/posts/dyalog-apl-competition-2020-phase-1.html create mode 100644 _site/posts/dyalog-apl-competition-2020-phase-2.html diff --git a/_site/archive.html b/_site/archive.html index aa07705..2dbc709 100644 --- a/_site/archive.html +++ b/_site/archive.html @@ -50,6 +50,14 @@ Here you can find all my previous posts: