Split into two different posts
This commit is contained in:
parent
44f662be42
commit
5be4981b16
2 changed files with 249 additions and 248 deletions
|
@ -1,554 +0,0 @@
|
|||
---
|
||||
title: "Dyalog APL Problem Solving Competition 2020"
|
||||
subtitle: "Annotated Solutions"
|
||||
date: 2020-07-31
|
||||
toc: true
|
||||
---
|
||||
|
||||
* Phase I
|
||||
|
||||
#+begin_src default
|
||||
:Namespace Phase1
|
||||
#+end_src
|
||||
|
||||
** 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
|
||||
|
||||
*Solution:* ~(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
|
||||
nested array, in the last train.
|
||||
|
||||
** 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
|
||||
|
||||
*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
|
||||
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
|
||||
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
|
||||
|
||||
*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
|
||||
~(⎕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
|
||||
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
|
||||
|
||||
*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),
|
||||
- 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
|
||||
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
|
||||
|
||||
*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
|
||||
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
|
||||
~-/⍵~. Finally, we just have to start the sequence at the first
|
||||
element of ~⍵~.
|
||||
|
||||
** 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
|
||||
|
||||
*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
|
||||
[[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
|
||||
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
|
||||
|
||||
*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
|
||||
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
|
||||
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
|
||||
|
||||
*Solution:* ~∧/2=∘|2-/∘×2-/(10∘⊥⍣¯1)~
|
||||
|
||||
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
|
||||
|
||||
#+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
|
||||
|
||||
*Solution:* ~{∧/(⍳∘≢≡⍋)¨(⊂((⊢⍳⌈/)↑⊢),⍵),⊂⌽((⊢⍳⌈/)↓⊢),⍵}~
|
||||
|
||||
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
|
||||
|
||||
#+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
|
||||
|
||||
*Solution:* ~{↑⊃,/↓¨⍕¨⍵}~
|
||||
|
||||
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')
|
||||
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]][fn::Split is the
|
||||
dual of Mix.] (~↓~) 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
|
||||
#+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
|
Loading…
Add table
Add a link
Reference in a new issue