337 lines
7.6 KiB
ReStructuredText
337 lines
7.6 KiB
ReStructuredText
.. code:: ipython3
|
||
|
||
from notebook_preamble import D, DefinitionWrapper, J, V, define
|
||
|
||
On "Two Exercises Found in a Book on Algorithmics"
|
||
==================================================
|
||
|
||
Bird & Meertens
|
||
|
||
`PDF paper available
|
||
here <https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.694.2614>`__
|
||
|
||
Define ``scan`` in terms of a reduction.
|
||
----------------------------------------
|
||
|
||
Problem I. The reduction operator ``/`` of APL takes some binary
|
||
operator ``⨁`` on its left and a vector ``x`` of values on its
|
||
right. The meaning of ``⨁/x`` for ``x = [a b ... z]`` is the value
|
||
``a⨁b⨁...⨁z``. For this to be well-defined in the absence of
|
||
brackets, the operation ``⨁`` has to be associative. Now there is
|
||
another operator ``\`` of APL called ``scan``. Its effect is closely
|
||
related to reduction in that we have:
|
||
|
||
::
|
||
|
||
⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]
|
||
|
||
The problem is to find some definition of ``scan`` as a reduction.
|
||
In other words, we have to find some function ``f`` and an operator
|
||
``⨂`` so that
|
||
|
||
::
|
||
|
||
⨁\x = f(a)⨂f(b)⨂...⨂f(z)
|
||
|
||
Designing the Recursive Function
|
||
--------------------------------
|
||
|
||
Ignoring the exact requirements (finding ``f`` and ``⨂``) can we
|
||
implement ``scan`` as a hylomorphism in Joy?
|
||
|
||
Looking at the forms of hylomorphism, ``H3`` is the one to use:
|
||
|
||
``H3``
|
||
~~~~~~
|
||
|
||
If the combiner and the generator both need to work on the current value
|
||
then ``dup`` must be used, and the generator must produce one item
|
||
instead of two (the b is instead the duplicate of a.)
|
||
|
||
::
|
||
|
||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
||
|
||
... a [G] dupdip [H3] dip F
|
||
... a G a [H3] dip F
|
||
... a′ a [H3] dip F
|
||
... a′ H3 a F
|
||
... a′ [G] dupdip [H3] dip F a F
|
||
... a′ G a′ [H3] dip F a F
|
||
... a″ a′ [H3] dip F a F
|
||
... a″ H3 a′ F a F
|
||
... a″ [G] dupdip [H3] dip F a′ F a F
|
||
... a″ G a″ [H3] dip F a′ F a F
|
||
... a‴ a″ [H3] dip F a′ F a F
|
||
... a‴ H3 a″ F a′ F a F
|
||
... a‴ pop c a″ F a′ F a F
|
||
... c a″ F a′ F a F
|
||
... d a′ F a F
|
||
... d′ a F
|
||
... d″
|
||
|
||
Initial Definition
|
||
~~~~~~~~~~~~~~~~~~
|
||
|
||
We're building a list of values so this is an "anamorphism". (An
|
||
anamorphism uses ``[]`` for ``c`` and ``swons`` for ``F``.)
|
||
|
||
::
|
||
|
||
scan == [P] [pop []] [[G] dupdip] [dip swons] genrec
|
||
|
||
Convert to ``ifte``:
|
||
|
||
::
|
||
|
||
scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte
|
||
|
||
On the recursive branch ``[G] dupdip`` doesn't cut it:
|
||
|
||
::
|
||
|
||
[1 2 3] [G] dupdip [scan] dip swons
|
||
[1 2 3] G [1 2 3] [scan] dip swons
|
||
|
||
Use ``first``
|
||
~~~~~~~~~~~~~
|
||
|
||
At this point, we want the copy of ``[1 2 3]`` to just be ``1``, so we
|
||
use ``first``.
|
||
|
||
::
|
||
|
||
scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec
|
||
|
||
[1 2 3] [G] dupdip first [scan] dip swons
|
||
[1 2 3] G [1 2 3] first [scan] dip swons
|
||
[1 2 3] G 1 [scan] dip swons
|
||
|
||
``G`` applies ``⨁``
|
||
~~~~~~~~~~~~~~~~~~~
|
||
|
||
Now what does ``G`` have to do? Just apply ``⨁`` to the first two terms
|
||
in the list.
|
||
|
||
::
|
||
|
||
[1 2 3] G
|
||
[1 2 3] [⨁] infra
|
||
[1 2 3] [+] infra
|
||
[3 3]
|
||
|
||
Predicate ``P``
|
||
~~~~~~~~~~~~~~~
|
||
|
||
Which tells us that the predicate ``[P]`` must guard against lists with
|
||
less that two items in them:
|
||
|
||
::
|
||
|
||
P == size 1 <=
|
||
|
||
Let's see what we've got so far:
|
||
|
||
::
|
||
|
||
scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec
|
||
scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec
|
||
|
||
Handling the Last Term
|
||
~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
This works to a point, but it throws away the last term:
|
||
|
||
.. code:: ipython3
|
||
|
||
J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[1 3]
|
||
|
||
|
||
Hmm... Let's take out the ``pop`` for a sec...
|
||
|
||
.. code:: ipython3
|
||
|
||
J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[6] [1 3]
|
||
|
||
|
||
That leaves the last item in our list, then it puts an empty list on the
|
||
stack and ``swons``'s the new terms onto that. If we leave out that
|
||
empty list, they will be ``swons``'d onto that list that already has the
|
||
last item.
|
||
|
||
.. code:: ipython3
|
||
|
||
J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[1 3 6]
|
||
|
||
|
||
Parameterize ``⨁``
|
||
~~~~~~~~~~~~~~~~~~
|
||
|
||
So we have:
|
||
|
||
::
|
||
|
||
[⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
|
||
|
||
Trivially:
|
||
|
||
::
|
||
|
||
== [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
|
||
== [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec
|
||
== [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
||
== [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
||
|
||
And so:
|
||
|
||
::
|
||
|
||
scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
|
||
|
||
.. code:: ipython3
|
||
|
||
define('scan [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')
|
||
|
||
.. code:: ipython3
|
||
|
||
J('[1 2 3 4] [+] scan')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[1 3 6 10]
|
||
|
||
|
||
.. code:: ipython3
|
||
|
||
J('[1 2 3 4] [*] scan')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[1 2 6 24]
|
||
|
||
|
||
.. code:: ipython3
|
||
|
||
J('[1 2 3 4 5 6 7] [neg +] scan')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
[1 1 2 2 3 3 4]
|
||
|
||
|
||
Problem 2.
|
||
----------
|
||
|
||
Define a line to be a sequence of characters not containing the
|
||
newline character. It is easy to define a function ``Unlines`` that
|
||
converts a non-empty sequence of lines into a sequence of characters
|
||
by inserting newline characters between every two lines.
|
||
|
||
Since ``Unlines`` is injective, the function ``Lines``, which
|
||
converts a sequence of characters into a sequence of lines by
|
||
splitting on newline characters, can be specified as the inverse of
|
||
``Unlines``.
|
||
|
||
The problem, just as in Problem 1. is to find a definition by
|
||
reduction of the function ``Lines``.
|
||
|
||
::
|
||
|
||
Unlines = uncons ['\n' swap + +] step
|
||
|
||
.. code:: ipython3
|
||
|
||
J('["hello" "world"] uncons ["\n" swap + +] step')
|
||
|
||
|
||
.. parsed-literal::
|
||
|
||
'hello\nworld'
|
||
|
||
|
||
Again ignoring the actual task let's just derive ``Lines``:
|
||
|
||
::
|
||
|
||
"abc\nefg\nhij" Lines
|
||
---------------------------
|
||
["abc" "efg" "hij"]
|
||
|
||
Instead of ``P == [size 1 <=]`` we want ``["\n" in]``, and for the
|
||
base-case of a string with no newlines in it we want to use ``unit``:
|
||
|
||
::
|
||
|
||
Lines == ["\n" in] [unit] [R0] [dip swons] genrec
|
||
Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte
|
||
|
||
Derive ``R0``:
|
||
|
||
::
|
||
|
||
"a \n b" R0 [Lines] dip swons
|
||
"a \n b" split-at-newline swap [Lines] dip swons
|
||
"a " " b" swap [Lines] dip swons
|
||
" b" "a " [Lines] dip swons
|
||
" b" Lines "a " swons
|
||
[" b"] "a " swons
|
||
["a " " b"]
|
||
|
||
So:
|
||
|
||
::
|
||
|
||
R0 == split-at-newline swap
|
||
|
||
Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec
|
||
|
||
Missing the Point?
|
||
------------------
|
||
|
||
This is all good and well, but in the paper many interesting laws and
|
||
properties are discussed. Am I missing the point?
|
||
|
||
::
|
||
|
||
0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat
|
||
|
||
For associative function ``F`` and a "unit" element for that function,
|
||
here represented by ``0``.
|
||
|
||
For functions that don't have a "unit" we can fake it (the example is
|
||
given of infinity for the ``min(a, b)`` function.) We can also use:
|
||
|
||
::
|
||
|
||
safe_step == [size 1 <=] [] [uncons [F] step] ifte
|
||
|
||
Or:
|
||
|
||
::
|
||
|
||
safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte
|
||
|
||
[a b c] [F] safe_step
|
||
---------------------------
|
||
a [b c] [F] step
|
||
|
||
To limit ``F`` to working on pairs of terms from its domain.
|