Add the post for the second homework assignment.
This commit is contained in:
parent
382102f071
commit
4e918db5cb
11
code/cs325-langs/sols/hw2.lang
Normal file
11
code/cs325-langs/sols/hw2.lang
Normal file
|
@ -0,0 +1,11 @@
|
|||
state 0;
|
||||
|
||||
effect {
|
||||
if(SOURCE == R) {
|
||||
STATE = STATE + |LEFT|;
|
||||
}
|
||||
}
|
||||
|
||||
combine {
|
||||
STATE = STATE + LSTATE + RSTATE;
|
||||
}
|
153
content/blog/01_cs325_languages_hw2.md
Normal file
153
content/blog/01_cs325_languages_hw2.md
Normal file
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: A Language for an Assignment - Homework 2
|
||||
date: 2019-12-30T20:05:10-08:00
|
||||
tags: ["Haskell", "Python", "Algorithms"]
|
||||
---
|
||||
|
||||
After the madness of the
|
||||
[language for homework 1]({{< relref "00_cs325_languages_hw1.md" >}}),
|
||||
the solution to the second homework offers a moment of respite.
|
||||
Let's get right into the problems, shall we?
|
||||
|
||||
### Homework 2
|
||||
Besides some free-response questions, the homework contains
|
||||
two problems. The first:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw2.txt" 29 34 >}}
|
||||
|
||||
And the second:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw2.txt" 36 44 >}}
|
||||
|
||||
At first glance, it's not obvious why these problems are good for
|
||||
us. However, there's one key observation: __`num_inversions` can be implemented
|
||||
using a slightly-modified `mergesort`__. The trick is to maintain a counter
|
||||
of inversions in every recursive call to `mergesort`, updating
|
||||
it every time we take an element from the
|
||||
{{< sidenote "right" "right-note" "right list" >}}
|
||||
If this nomeclature is not clear to you, recall that
|
||||
mergesort divides a list into two smaller lists. The
|
||||
"right list" refers to the second of the two, because
|
||||
if you visualize the original list as a rectangle, and cut
|
||||
it in half (vertically, down the middle), then the second list
|
||||
(from the left) is on the right.
|
||||
{{< /sidenote >}} while there are still elements in the
|
||||
{{< sidenote "left" "left-note" "left list" >}}
|
||||
Why this is the case is left as an exercise to the reader.
|
||||
{{< /sidenote >}}.
|
||||
When we return from the call,
|
||||
we add up the number of inversions from running `num_inversions`
|
||||
on the smaller lists, and the number of inversions that we counted
|
||||
as I described. We then return both the total number
|
||||
of inversions and the sorted list.
|
||||
|
||||
So, we either perform the standard mergesort, or we perform mergesort
|
||||
with additional steps added on. The additional steps can be divided into
|
||||
three general categories:
|
||||
|
||||
1. __Initialization__: We create / set some initial state. This state
|
||||
doesn't depend on the lists or anything else.
|
||||
2. __Effect__: Each time that an element is moved from one of the two smaller
|
||||
lists into the output list, we may change the state in some way (create
|
||||
an effect).
|
||||
3. __Combination__: The final state, and the results of the two
|
||||
sub-problem states, are combined into the output of the function.
|
||||
|
||||
This is all very abstract. In the concrete case of inversions,
|
||||
these steps are as follows:
|
||||
|
||||
1. __Initializtion__: The initial state, which is just the counter, is set to 0.
|
||||
2. __Effect__: Each time an element is moved, if it comes from the right list,
|
||||
the number of inversions is updated.
|
||||
3. __Combination__: We update the state, simply adding the left and right
|
||||
inversion counts.
|
||||
|
||||
We can make a language out of this!
|
||||
|
||||
### A Language
|
||||
Again, let's start by visualizing what the solution will look like. How about this:
|
||||
|
||||
{{< rawblock "cs325-langs/sols/hw2.lang" >}}
|
||||
|
||||
We divide the code into the same three steps that we described above. The first
|
||||
section is the initial state. Since it doesn't depend on anything, we expect
|
||||
it to be some kind of literal, like an integer. Next, we have the effect section,
|
||||
which has access to variables such as "STATE" (to access the current state)
|
||||
and "LEFT" (to access the left list), or "L" to access the "name" of the left list.
|
||||
We use an `if`-statement to check if the origin of the element that was popped
|
||||
(held in the "SOURCE" variable) is the right list (denoted by "R"). If it is,
|
||||
we increment the counter (state) by the proper amount. In the combine step, we simply increment
|
||||
the state by the counters from the left and right solutions, stored in "LSTATE" and "RSTATE".
|
||||
That's it!
|
||||
|
||||
#### Implementation
|
||||
The implementation is not tricky at all. We don't need to use monads like we did last
|
||||
time, and nor do we have to perform any fancy Python nested function declarations.
|
||||
|
||||
To keep with the Python convention of lowercase variables, we'll translate the
|
||||
uppercase "global" variables to lowercase. We'll do it like so:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageTwo.hs" 211 220 >}}
|
||||
|
||||
Note that we translated "L" and "R" to integer literals. We'll indicate the source of
|
||||
each element with an integer, since there's no real point to representing it with
|
||||
a string or a variable. We'll need to be aware of this when we implement the actual, generic
|
||||
mergesort code. Let's do that now:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageTwo.hs" 145 205 >}}
|
||||
|
||||
This is probably the ugliest part of this assignment: we handwrote a Python
|
||||
AST in Haskell that implements mergesort with our augmentations. Note that
|
||||
this is a function, which takes a `Py.PyExpr` (the initial state expression),
|
||||
and two lists of `Py.PyStmt`, which are the "effect" and "combination" code,
|
||||
respectively. We simply splice them into our regular mergesort function.
|
||||
The translation is otherwise pretty trivial, so there's no real reason
|
||||
to show it here.
|
||||
|
||||
### The Output
|
||||
What's the output of our solution to `num_inversions`? Take a look for yourself:
|
||||
|
||||
```Python
|
||||
def prog(xs):
|
||||
if len(xs)<2:
|
||||
return (0, xs)
|
||||
leng = len(xs)//2
|
||||
left = xs[:(leng)]
|
||||
right = xs[(leng):]
|
||||
(ls,left) = prog(left)
|
||||
(rs,right) = prog(right)
|
||||
left.reverse()
|
||||
right.reverse()
|
||||
state = 0
|
||||
source = 0
|
||||
total = []
|
||||
while (left!=[])and(right!=[]):
|
||||
if left[-1]<=right[-1]:
|
||||
total.append(left.pop())
|
||||
source = 1
|
||||
else:
|
||||
total.append(right.pop())
|
||||
source = 2
|
||||
if source==2:
|
||||
state = state+len(left)
|
||||
state = state+ls+rs
|
||||
left.reverse()
|
||||
right.reverse()
|
||||
return (state, total+left+right)
|
||||
```
|
||||
|
||||
Honestly, that's pretty clean. As clean as `left.reverse()` to allow for \\(O(1)\\) pop is.
|
||||
What's really clean, however, is the implementation of mergesort in our language.
|
||||
It goes as follows:
|
||||
|
||||
```
|
||||
state 0;
|
||||
effect {}
|
||||
combine {}
|
||||
```
|
||||
|
||||
To implement mergesort in our language, which describes mergesort variants, all
|
||||
we have to do is not specify any additional behavior. Cool, huh?
|
||||
|
||||
That's the end of this post. If you liked this one (and the previous one!),
|
||||
keep an eye out for more!
|
Loading…
Reference in New Issue
Block a user