Compare commits
21 Commits
sidenotes
...
a026e67a3b
| Author | SHA1 | Date | |
|---|---|---|---|
| a026e67a3b | |||
| d9544398b9 | |||
| 1c4bb29fdd | |||
| 765d497724 | |||
| 80410c9200 | |||
| 4e918db5cb | |||
| 382102f071 | |||
| 6e88780f8b | |||
| e3035b9d66 | |||
| 8765626898 | |||
| c38247df9e | |||
| baf44f8627 | |||
| 19aa126025 | |||
| a406fb0846 | |||
| 75664e90bb | |||
| f74209c970 | |||
| c7ce8a3107 | |||
| b3b906dd90 | |||
| b8e0e0b4ce | |||
| eb02e1e6b0 | |||
| b2fc6ea5a8 |
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.gmachine-instruction-name {
|
||||
padding: 10px;
|
||||
padding: .8rem;
|
||||
border-right: $standard-border;
|
||||
flex-grow: 1;
|
||||
flex-basis: 20%;
|
||||
@@ -28,12 +28,12 @@
|
||||
}
|
||||
|
||||
.gmachine-inner-label {
|
||||
padding: 10px;
|
||||
padding: .8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gmachine-inner-text {
|
||||
padding: 10px;
|
||||
padding: .8rem;
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
119
code/cs325-langs/hws/hw1.txt
Normal file
119
code/cs325-langs/hws/hw1.txt
Normal file
@@ -0,0 +1,119 @@
|
||||
CS 325-001, Analysis of Algorithms, Fall 2019
|
||||
HW1 - Python 3, qsort, BST, and qselect
|
||||
Due electronically on flip on Monday 9/30 at 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Need to submit on flip: report.txt, qsort.py, and qselect.py.
|
||||
qselect.py will be automatically graded for correctness (1%).
|
||||
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw1 qselect.py qsort.py report.txt
|
||||
|
||||
Note:
|
||||
|
||||
1. You can ssh to flip machines from your own machine by:
|
||||
$ ssh access.engr.oregonstate.edu
|
||||
|
||||
2. You can add /nfs/farm/classes/eecs/fall2019/cs325-001/ to your $PATH:
|
||||
$ export PATH=$PATH:/nfs/farm/classes/eecs/fall2019/cs325-001/
|
||||
and add the above command to your ~/.bash_profile,
|
||||
so that you don't need to type it every time.
|
||||
|
||||
(alternatively, you can use symbolic links or aliases to avoid typing the long path)
|
||||
|
||||
3. You can choose to submit each file separately, or submit them together.
|
||||
|
||||
Textbooks for References:
|
||||
[1] CLRS Ch. 9.2 and Ch. 12
|
||||
|
||||
0. Q: What's the best-case, worst-case, and average-case time complexities of quicksort.
|
||||
Briefly explain each case.
|
||||
|
||||
1. [WILL BE GRADED]
|
||||
Quickselect with Randomized Pivot (CLRS Ch. 9.2).
|
||||
|
||||
>>> from qselect import *
|
||||
>>> qselect(2, [3, 10, 4, 7, 19])
|
||||
4
|
||||
>>> qselect(4, [11, 2, 8, 3])
|
||||
11
|
||||
|
||||
Q: What's the best-case, worst-case, and average-case time complexities? Briefly explain.
|
||||
|
||||
Filename: qselect.py
|
||||
|
||||
|
||||
2. Buggy Qsort Revisited
|
||||
|
||||
In the slides we showed a buggy version of qsort which is weird in an interesting way:
|
||||
it actually returns a binary search tree for the given array, rooted at the pivot:
|
||||
|
||||
>>> from qsort import *
|
||||
>>> tree = sort([4,2,6,3,5,7,1,9])
|
||||
>>> tree
|
||||
[[[[], 1, []], 2, [[], 3, []]], 4, [[[], 5, []], 6, [[], 7, [[], 9, []]]]]
|
||||
|
||||
which encodes a binary search tree:
|
||||
|
||||
4
|
||||
/ \
|
||||
2 6
|
||||
/ \ / \
|
||||
1 3 5 7
|
||||
\
|
||||
9
|
||||
|
||||
Now on top of that piece of code, add three functions:
|
||||
* sorted(t): returns the sorted order (infix traversal)
|
||||
* search(t, x): returns whether x is in t
|
||||
* insert(t, x): inserts x into t (in-place) if it is missing, otherwise does nothing.
|
||||
|
||||
>>> sorted(tree)
|
||||
[1, 2, 3, 4, 5, 6, 7, 9]
|
||||
>>> search(tree, 6)
|
||||
True
|
||||
>>> search(tree, 6.5)
|
||||
False
|
||||
>>> insert(tree, 6.5)
|
||||
>>> tree
|
||||
[[[[], 1, []], 2, [[], 3, []]], 4, [[[], 5, []], 6, [[[], 6.5, []], 7, [[], 9, []]]]]
|
||||
>>> insert(tree, 3)
|
||||
>>> tree
|
||||
[[[[], 1, []], 2, [[], 3, []]], 4, [[[], 5, []], 6, [[[], 6.5, []], 7, [[], 9, []]]]]
|
||||
|
||||
Hint: both search and insert should depend on a helper function _search(tree, x) which
|
||||
returns the subtree (a list) rooted at x when x is found, or the [] where x should
|
||||
be inserted.
|
||||
|
||||
e.g.,
|
||||
>>> tree = sort([4,2,6,3,5,7,1,9]) # starting from the initial tree
|
||||
>>> _search(tree, 3)
|
||||
[[], 3, []]
|
||||
>>> _search(tree, 0)
|
||||
[]
|
||||
>>> _search(tree, 6.5)
|
||||
[]
|
||||
>>> _search(tree, 0) is _search(tree, 6.5)
|
||||
False
|
||||
>>> _search(tree, 0) == _search(tree, 6.5)
|
||||
True
|
||||
|
||||
Note the last two []'s are different nodes (with different memory addresses):
|
||||
the first one is the left child of 1, while the second one is the left child of 7
|
||||
(so that insert is very easy).
|
||||
|
||||
Filename: qsort.py
|
||||
|
||||
Q: What are the time complexities for the operations implemented?
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
4. How deeply do you feel you understand the material it covers (0%–100%)?
|
||||
5. Any other comments?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
|
||||
170
code/cs325-langs/hws/hw10.txt
Normal file
170
code/cs325-langs/hws/hw10.txt
Normal file
@@ -0,0 +1,170 @@
|
||||
CS 325, Algorithms (MS/MEng-level), Fall 2019
|
||||
|
||||
HW10 - Challenge Problem - RNA Structure Prediction (6%)
|
||||
This problem combines dynamic programming and priority queues.
|
||||
|
||||
Due Wednesday 12/4, 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Include in your submission: report.txt, rna.py.
|
||||
Grading:
|
||||
* report.txt -- 1%
|
||||
* 1-best structure -- 2%
|
||||
* number of structures -- 1%
|
||||
* k-best structures -- 2%
|
||||
|
||||
Textbooks for References:
|
||||
[1] KT Ch. 6.5 (DP over intervals -- RNA structure)
|
||||
[2] KT slides: DP I (RNA section)
|
||||
http://www.cs.princeton.edu/~wayne/kleinberg-tardos/
|
||||
|
||||
***Please analyze time/space complexities for each problem in report.txt.
|
||||
|
||||
1. Given an RNA sequence, such as ACAGU, we can predict its secondary structure
|
||||
by tagging each nucleotide as (, ., or ). Each matching pair of () must be
|
||||
AU, GC, or GU (or their mirror symmetries: UA, CG, UG).
|
||||
We also assume pairs can _not_ cross each other.
|
||||
The following are valid structures for ACAGU:
|
||||
|
||||
ACAGU
|
||||
.....
|
||||
...()
|
||||
..(.)
|
||||
.(.).
|
||||
(...)
|
||||
((.))
|
||||
|
||||
We want to find the structure with the maximum number of matching pairs.
|
||||
In the above example, the last structure is optimal (2 pairs).
|
||||
|
||||
>>> best("ACAGU")
|
||||
(2, '((.))')
|
||||
|
||||
Tie-breaking: arbitrary. Don't worry as long as your structure
|
||||
is one of the correct best structures.
|
||||
|
||||
some other cases (more cases at the bottom):
|
||||
|
||||
GCACG
|
||||
(2, '().()')
|
||||
UUCAGGA
|
||||
(3, '(((.)))')
|
||||
GUUAGAGUCU
|
||||
(4, '(.()((.)))')
|
||||
AUAACCUUAUAGGGCUCUG
|
||||
(8, '.(((..)()()((()))))')
|
||||
AACCGCUGUGUCAAGCCCAUCCUGCCUUGUU
|
||||
(11, '(((.(..(.((.)((...().))()))))))')
|
||||
GAUGCCGUGUAGUCCAAAGACUUCACCGUUGG
|
||||
(14, '.()()(()(()())(((.((.)(.))()))))')
|
||||
CAUCGGGGUCUGAGAUGGCCAUGAAGGGCACGUACUGUUU
|
||||
(18, '(()())(((((.)))()(((())(.(.().()()))))))')
|
||||
ACGGCCAGUAAAGGUCAUAUACGCGGAAUGACAGGUCUAUCUAC
|
||||
(19, '.()(((.)(..))(((.()()(())))(((.)((())))))())')
|
||||
AGGCAUCAAACCCUGCAUGGGAGCACCGCCACUGGCGAUUUUGGUA
|
||||
(20, '.(()())...((((()()))((()(.()(((.)))()())))))()')
|
||||
|
||||
2. Total number of all possible structures
|
||||
|
||||
>>> total("ACAGU")
|
||||
6
|
||||
|
||||
3. k-best structures: output the 1-best, 2nd-best, ... kth-best structures.
|
||||
|
||||
>>> kbest("ACAGU", 3)
|
||||
[(2, '((.))'), (1, '(...)'), (1, '.(.).')]
|
||||
|
||||
The list must be sorted.
|
||||
Tie-breaking: arbitrary.
|
||||
|
||||
In case the input k is bigger than the number of possible structures, output all.
|
||||
|
||||
Sanity check: kbest(s, 1)[0][0] == best(s)[0] for each RNA sequence s.
|
||||
|
||||
All three functions should be in one file: rna.py.
|
||||
|
||||
See more testcases at the end.
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
5. Any other comments?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
|
||||
|
||||
TESTCASES:
|
||||
|
||||
for each sequence s, we list three lines:
|
||||
best(s)
|
||||
total(s)
|
||||
kbest(s, 10)
|
||||
|
||||
|
||||
|
||||
ACAGU
|
||||
(2, '((.))')
|
||||
6
|
||||
[(2, '((.))'), (1, '.(.).'), (1, '..(.)'), (1, '...()'), (1, '(...)'), (0, '.....')]
|
||||
------
|
||||
AC
|
||||
(0, '..')
|
||||
1
|
||||
[(0, '..')]
|
||||
------
|
||||
GUAC
|
||||
(2, '(())')
|
||||
5
|
||||
[(2, '(())'), (1, '()..'), (1, '.().'), (1, '(..)'), (0, '....')]
|
||||
------
|
||||
GCACG
|
||||
(2, '().()')
|
||||
6
|
||||
[(2, '().()'), (1, '(..).'), (1, '()...'), (1, '.(..)'), (1, '...()'), (0, '.....')]
|
||||
------
|
||||
CCGG
|
||||
(2, '(())')
|
||||
6
|
||||
[(2, '(())'), (1, '(.).'), (1, '.().'), (1, '.(.)'), (1, '(..)'), (0, '....')]
|
||||
------
|
||||
CCCGGG
|
||||
(3, '((()))')
|
||||
20
|
||||
[(3, '((()))'), (2, '((.)).'), (2, '(.()).'), (2, '.(()).'), (2, '.(().)'), (2, '.((.))'), (2, '((.).)'), (2, '(.(.))'), (2, '(.().)'), (2, '((..))')]
|
||||
------
|
||||
UUCAGGA
|
||||
(3, '(((.)))')
|
||||
24
|
||||
[(3, '(((.)))'), (2, '((.).).'), (2, '((..)).'), (2, '(.(.)).'), (2, '((.))..'), (2, '.((.)).'), (2, '.((.).)'), (2, '.((..))'), (2, '((..).)'), (2, '((.)..)')]
|
||||
------
|
||||
AUAACCUA
|
||||
(2, '.((...))')
|
||||
19
|
||||
[(2, '((.)..).'), (2, '(()...).'), (2, '()(...).'), (2, '().(..).'), (2, '()....()'), (2, '.()(..).'), (2, '.()...()'), (2, '.(.)..()'), (2, '.((...))'), (2, '.(.(..))')]
|
||||
------
|
||||
UUGGACUUG
|
||||
(4, '(()((.)))')
|
||||
129
|
||||
[(4, '(())(.)()'), (4, '(()((.)))'), (3, '(().)..()'), (3, '(().).(.)'), (3, '(().)(..)'), (3, '((.))..()'), (3, '((.)).(.)'), (3, '((.))(..)'), (3, '(())(..).'), (3, '(())(.)..')]
|
||||
------
|
||||
UUUGGCACUA
|
||||
(4, '(.()()(.))')
|
||||
179
|
||||
[(4, '((()).).()'), (4, '((.)()).()'), (4, '(.()()).()'), (4, '.(()()).()'), (4, '.(()()(.))'), (4, '((()).(.))'), (4, '((.)()(.))'), (4, '((()())..)'), (4, '(.()()(.))'), (3, '((()).)...')]
|
||||
------
|
||||
GAUGCCGUGUAGUCCAAAGACUUC
|
||||
(11, '(((()()((()(.))))((.))))')
|
||||
2977987
|
||||
[(11, '(()())(((()().))(((.))))'), (11, '(()())(((()()).)(((.))))'), (11, '(()())(((()(.)))(((.))))'), (11, '(()()()((()(.)))(((.))))'), (11, '(((()()((()().)))((.))))'), (11, '(((()()((()(.))))((.))))'), (11, '(()()()((()()).)(((.))))'), (11, '(()()()((()().))(((.))))'), (11, '(((()()((()()).))((.))))'), (10, '(()()()((()().).)((.))).')]
|
||||
------
|
||||
AGGCAUCAAACCCUGCAUGGGAGCG
|
||||
(10, '.(()())...((((()()))).())')
|
||||
560580
|
||||
[(10, '.(()())...((((())())).)()'), (10, '.(()())...((((()()))).)()'), (10, '.(()())...(((()(()))).)()'), (10, '.(()())...(((()(()))).())'), (10, '.(()())...((((())())).())'), (10, '.(()())...((((()()))).())'), (9, '((.).)(...(.((()()))).)()'), (9, '((.).)(...(((.)(()))).)()'), (9, '((.).)(...(.(()(()))).)()'), (9, '((.).)(...((.(()()))).)()')]
|
||||
------
|
||||
42
code/cs325-langs/hws/hw11.txt
Normal file
42
code/cs325-langs/hws/hw11.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
HW11 -- OPTIONAL (for your practice only -- solutions will be released on Tuesday)
|
||||
|
||||
Edit Distance (see updated final review solutions)
|
||||
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw11 edit.py
|
||||
|
||||
Implement two functions:
|
||||
* distance1(s, t): Viterbi-style (either top-down or bottom-up)
|
||||
* distance2(s, t): Dijkstra-style (best-first)
|
||||
|
||||
For Dijkstra, you can use either heapdict or heapq (see review problem 7).
|
||||
Given that this graph is extremely sparse (why?), heapq (ElogE) might be faster than heapdict (ElogV)
|
||||
because the latter has overhead for hash.
|
||||
|
||||
They should return the same result (just return the edit distance).
|
||||
|
||||
We have 10 testcases (listed below); the first 5 test distance1(),
|
||||
and the second 5 test distance2() on the same 5 string pairs.
|
||||
|
||||
My solutions (on flip2):
|
||||
Testing Case 1 (open)... 0.001 s, Correct
|
||||
Testing Case 2 (open)... 0.000 s, Correct
|
||||
Testing Case 3 (open)... 0.012 s, Correct
|
||||
Testing Case 4 (open)... 0.155 s, Correct
|
||||
Testing Case 5 (open)... 0.112 s, Correct
|
||||
Testing Case 6 (hidden)... 0.000 s, Correct
|
||||
Testing Case 7 (hidden)... 0.000 s, Correct
|
||||
Testing Case 8 (hidden)... 0.004 s, Correct
|
||||
Testing Case 9 (hidden)... 0.009 s, Correct
|
||||
Testing Case 10 (hidden)... 0.021 s, Correct
|
||||
Total Time: 0.316 s
|
||||
|
||||
distance1("abcdefh", "abbcdfg") == 3
|
||||
distance1("pretty", "prettier") == 3
|
||||
distance1("aaaaaaadaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxaaaaaaaaaaaaaaaaaaaaaa") == 5
|
||||
distance1('cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbxtwiqzvokqpkecyywrbvhlqgxzutdjfmvlhsezfbhfjbllmfhzlqlcwibubyyjupbwhztskyksfthkptxqlmhivfjbgclwsombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasomrhotoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwy', 'cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbtwiqzvokqpkecyywrbvhlqgxzutdjfmvlhsezfbhfjbllmfhzlqlcwibubyyjupbwhztskyksfthkptxqlmhivfbgclwsombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasonrhotoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwy') == 3
|
||||
distance1('cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbtwiqzvokqpasdfkecyywrbvhlqgxzutdjfmvlhsezfbhbllmfhzlqlcwibubyyjupbwhztsxyksfthkptxqlmhivfjbgclhombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasomrttoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwydmbihjkvziitusmkjljrsbafytsinql', 'cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbtwiqzvokqpkecyywrbvhlqgxzutdjfmvlhsezfbhfjbllmfhzlqlcwibubyyjupbwhztskyksfthkptxqlmhivfjbgclwsombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasomrhotoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwydmbihjkvziitusmkjljrsbafytsinql') == 11
|
||||
distance2("abcdefh", "abbcdfg") == 3
|
||||
distance2("pretty", "prettier") == 3
|
||||
distance2("aaaaaaadaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxaaaaaaaaaaaaaaaaaaaaaa") == 5
|
||||
distance2('cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbxtwiqzvokqpkecyywrbvhlqgxzutdjfmvlhsezfbhfjbllmfhzlqlcwibubyyjupbwhztskyksfthkptxqlmhivfjbgclwsombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasomrhotoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwy', 'cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbtwiqzvokqpkecyywrbvhlqgxzutdjfmvlhsezfbhfjbllmfhzlqlcwibubyyjupbwhztskyksfthkptxqlmhivfbgclwsombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasonrhotoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwy') == 3
|
||||
distance2('cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbtwiqzvokqpasdfkecyywrbvhlqgxzutdjfmvlhsezfbhbllmfhzlqlcwibubyyjupbwhztsxyksfthkptxqlmhivfjbgclhombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasomrttoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwydmbihjkvziitusmkjljrsbafytsinql', 'cpuyedzrwcbritzclzhwwabmlyresvewkdxwkamyzbtwiqzvokqpkecyywrbvhlqgxzutdjfmvlhsezfbhfjbllmfhzlqlcwibubyyjupbwhztskyksfthkptxqlmhivfjbgclwsombvytdztapwpzmdqfwwrhqsgztobeuiatcwmrzfbwhfnpzzasomrhotoqiwvexlgxsnafiagfewmopdzwanxswfsmbxsmsczbwsgnwydmbihjkvziitusmkjljrsbafytsinql') == 11
|
||||
80
code/cs325-langs/hws/hw2.txt
Normal file
80
code/cs325-langs/hws/hw2.txt
Normal file
@@ -0,0 +1,80 @@
|
||||
CS 325-001, Analysis of Algorithms, Fall 2019
|
||||
HW2 - Divide-n-conquer: mergesort, number of inversions, longest path
|
||||
|
||||
Due Monday Oct 7, 11:59pm (same submission instructions as HW1).
|
||||
No late submission will be accepted.
|
||||
|
||||
Need to submit: report.txt, msort.py, inversions.py, and longest.py.
|
||||
longest.py will be graded for correctness (1%).
|
||||
|
||||
To submit:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw2 report.txt {msort,inversions,longest}.py
|
||||
(You can submit each file separately, or submit them together.)
|
||||
|
||||
To see your best results so far:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/query hw2
|
||||
|
||||
|
||||
Textbooks for References:
|
||||
[1] CLRS Ch. 2
|
||||
|
||||
0. Which of the following sorting algorithms are (or can be made) stable?
|
||||
(a) mergesort
|
||||
(b) quicksort with the first element as pivot
|
||||
(c) quicksort with randomized pivot
|
||||
(d) selection sort
|
||||
(e) insertion sort
|
||||
(f) heap sort --- not covered yet (see CLRS Ch. 6)
|
||||
|
||||
1. Implement mergesort.
|
||||
|
||||
>>> mergesort([4, 2, 5, 1, 6, 3])
|
||||
[1, 2, 3, 4, 5, 6]
|
||||
|
||||
Filename: msort.py
|
||||
|
||||
2. Calculate the number of inversions in a list.
|
||||
|
||||
>>> num_inversions([4, 1, 3, 2])
|
||||
4
|
||||
>>> num_inversions([2, 4, 1, 3])
|
||||
3
|
||||
|
||||
Filename: inversions.py
|
||||
Must run in O(nlogn) time.
|
||||
|
||||
3. [WILL BE GRADED]
|
||||
|
||||
Length of the longest path in a binary tree (number of edges).
|
||||
|
||||
We will use the "buggy qsort" representation of binary trees from HW1:
|
||||
[left_subtree, root, right_subtree]
|
||||
|
||||
>>> longest([[], 1, []])
|
||||
0
|
||||
|
||||
>>> longest([[[], 1, []], 2, [[], 3, []]])
|
||||
2
|
||||
|
||||
>>> longest([[[[], 1, []], 2, [[], 3, []]], 4, [[[], 5, []], 6, [[], 7, [[], 9, []]]]])
|
||||
5
|
||||
|
||||
Note the answer is 5 because the longest path is 1-2-4-6-7-9.
|
||||
|
||||
Filename: longest.py
|
||||
Must run in O(n) time.
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
Note you are encouraged to discuss with your classmates,
|
||||
but each students should submit his/her own code.
|
||||
4. How deeply do you feel you understand the material it covers (0%–100%)?
|
||||
5. Any other comments?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
|
||||
83
code/cs325-langs/hws/hw3.txt
Normal file
83
code/cs325-langs/hws/hw3.txt
Normal file
@@ -0,0 +1,83 @@
|
||||
CS 325, Algorithms, Fall 2019
|
||||
HW3 - K closest numbers; Two Pointers
|
||||
|
||||
Due Monday Oct 14, 11:59pm. (same submission instructions as HW1-2).
|
||||
No late submission will be accepted.
|
||||
|
||||
Need to submit: report.txt, closest_unsorted.py, closest_sorted.py, xyz.py.
|
||||
closest_sorted.py will be graded for correctness (1%).
|
||||
|
||||
To submit:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw3 report.txt {closest*,xyz}.py
|
||||
(You can submit each file separately, or submit them together.)
|
||||
|
||||
To see your best results so far:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/query hw3
|
||||
|
||||
|
||||
1. Given an array A of n numbers, a query x, and a number k,
|
||||
find the k numbers in A that are closest (in value) to x.
|
||||
For example:
|
||||
|
||||
find([4,1,3,2,7,4], 5.2, 2) returns [4,4]
|
||||
find([4,1,3,2,7,4], 6.5, 3) returns [4,7,4]
|
||||
find([5,3,4,1,6,3], 3.5, 2) returns [3,4]
|
||||
|
||||
|
||||
Filename: closest_unsorted.py
|
||||
Must run in O(n) time.
|
||||
The elements in the returned list must be in the original order.
|
||||
In case two numbers are equally close to x, choose the earlier one.
|
||||
|
||||
|
||||
2. [WILL BE GRADED]
|
||||
Now what if the input array is sorted? Can you do it faster?
|
||||
|
||||
find([1,2,3,4,4,7], 5.2, 2) returns [4,4]
|
||||
find([1,2,3,4,4,7], 6.5, 3) returns [4,4,7]
|
||||
|
||||
Filename: closest_sorted.py
|
||||
Must run in O(logn + k) time.
|
||||
The elements in the returned list must be in the original order.
|
||||
|
||||
Note: in case two numbers are equally close to x, choose the smaller one:
|
||||
find([1,2,3,4,4,6,6], 5, 3) returns [4,4,6]
|
||||
find([1,2,3,4,4,5,6], 4, 5) returns [2,3,4,4,5]
|
||||
|
||||
Hint: you can use Python's bisect.bisect for binary search.
|
||||
|
||||
|
||||
3. For a given array A of n *distinct* numbers, find all triples (x,y,z)
|
||||
s.t. x + y = z. (x, y, z are distinct numbers)
|
||||
|
||||
e.g.,
|
||||
|
||||
find([1, 4, 2, 3, 5]) returns [(1,3,4), (1,2,3), (1,4,5), (2,3,5)]
|
||||
|
||||
Note that:
|
||||
1) no duplicates in the input array
|
||||
2) you can choose any arbitrary order for triples in the returned list.
|
||||
|
||||
Filename: xyz.py
|
||||
Must run in O(n^2) time.
|
||||
|
||||
Hint: you can use any built-in sort in Python.
|
||||
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
Note you are encouraged to discuss with your classmates,
|
||||
but each students should submit his/her own code.
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
|
||||
5. Which part(s) of the course you like the most so far?
|
||||
6. Which part(s) of the course you dislike the most so far?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
|
||||
114
code/cs325-langs/hws/hw4.txt
Normal file
114
code/cs325-langs/hws/hw4.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
CS 325-001, Algorithms, Fall 2019
|
||||
HW4 - Priority Queue and Heaps
|
||||
|
||||
Due via the submit program on Monday Oct 21, 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Need to submit: report.txt, nbest.py, kmergesort.py, datastream.py.
|
||||
datastream.py will be graded for correctness (1%).
|
||||
|
||||
To submit:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw4 report.txt {nbest,kmergesort,datastream}.py
|
||||
(You can submit each file separately, or submit them together.)
|
||||
|
||||
To see your best results so far:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/query hw4
|
||||
|
||||
|
||||
Textbooks for References:
|
||||
[1] CLRS Ch. 6
|
||||
[2] KT slides for binary heaps (only read the first 20 pages!):
|
||||
https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/BinomialHeaps.pdf
|
||||
[3] Python heapq module
|
||||
|
||||
0. There are two methods for building a heap from an unsorted array:
|
||||
(1) insert each element into the heap --- O(nlogn) -- heapq.heappush()
|
||||
(2) heapify (top-down) --- O(n) -- heapq.heapify()
|
||||
|
||||
(a) Derive these time complexities.
|
||||
(b) Use a long list of random numbers to show the difference in time. (Hint: random.shuffle or random.sample)
|
||||
(c) What about sorted or reversely-sorted numbers?
|
||||
|
||||
1. Given two lists A and B, each with n integers, return
|
||||
a sorted list C that contains the smallest n elements from AxB:
|
||||
|
||||
AxB = { (x, y) | x in A, y in B }
|
||||
|
||||
i.e., AxB is the Cartesian Product of A and B.
|
||||
|
||||
ordering: (x,y) < (x',y') iff. x+y < x'+y' or (x+y==x'+y' and y<y')
|
||||
|
||||
You need to implement three algorithms and compare:
|
||||
|
||||
(a) enumerate all n^2 pairs, sort, and take top n.
|
||||
(b) enumerate all n^2 pairs, but use qselect from hw1.
|
||||
(c) Dijkstra-style best-first, only enumerate O(n) (at most 2n) pairs.
|
||||
Hint: you can use Python's heapq module for priority queue.
|
||||
|
||||
Q: What are the time complexities of these algorithms?
|
||||
|
||||
>>> a, b = [4, 1, 5, 3], [2, 6, 3, 4]
|
||||
>>> nbesta(a, b) # algorithm (a), slowest
|
||||
[(1, 2), (1, 3), (3, 2), (1, 4)]
|
||||
>>> nbestb(a, b) # algorithm (b), slow
|
||||
[(1, 2), (1, 3), (3, 2), (1, 4)]
|
||||
>>> nbestc(a, b) # algorithm (c), fast
|
||||
[(1, 2), (1, 3), (3, 2), (1, 4)]
|
||||
|
||||
Filename: nbest.py
|
||||
|
||||
2. k-way mergesort (the classical mergesort is a special case where k=2).
|
||||
|
||||
>>> kmergesort([4,1,5,2,6,3,7,0], 3) # k=3
|
||||
[0,1,2,3,4,5,6,7]
|
||||
|
||||
Q: What is the complexity? Write down the detailed analysis in report.txt.
|
||||
|
||||
Filename: kmergesort.py
|
||||
|
||||
3. [WILL BE GRADED]
|
||||
|
||||
Find the k smallest numbers in a data stream of length n (k<<n),
|
||||
using only O(k) space (the stream itself might be too big to fit in memory).
|
||||
|
||||
>>> ksmallest(4, [10, 2, 9, 3, 7, 8, 11, 5, 7])
|
||||
[2, 3, 5, 7]
|
||||
>>> ksmallest(3, range(1000000, 0, -1))
|
||||
[1, 2, 3]
|
||||
|
||||
Note:
|
||||
a) it should work with both lists and lazy lists
|
||||
b) the output list should be sorted
|
||||
|
||||
Q: What is your complexity? Write down the detailed analysis in report.txt.
|
||||
|
||||
Filename: datastream.py
|
||||
|
||||
[UPDATE] The built-in function heapq.nsmallest() is _not_ allowed for this problem.
|
||||
The whole point is to implement it yourself. :)
|
||||
|
||||
|
||||
4. (optional) Summarize the time complexities of the basic operations (push, pop-min, peak, heapify) for these implementations of priority queue:
|
||||
|
||||
(a) unsorted array
|
||||
(b) sorted array (highest priority first)
|
||||
(c) reversly sorted array (lowest priority first)
|
||||
(d) linked list
|
||||
(e) binary heap
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
Note you are encouraged to discuss with your classmates,
|
||||
but each students should submit his/her own code.
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
5. Which part(s) of the course you like the most so far?
|
||||
6. Which part(s) of the course you dislike the most so far?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
|
||||
130
code/cs325-langs/hws/hw5.txt
Normal file
130
code/cs325-langs/hws/hw5.txt
Normal file
@@ -0,0 +1,130 @@
|
||||
CS 532-001, Algorithms, Fall 2019
|
||||
HW5 - DP (part 1: simple)
|
||||
|
||||
HWs 5-7 are all on DPs.
|
||||
|
||||
Due Monday Oct 28, 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Need to submit report.txt, mis.py, bsts.py, bitstrings.py.
|
||||
mis.py will be graded for correctness (1%).
|
||||
|
||||
To submit:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw5 report.txt {mis,bsts,bitstrings}.py
|
||||
(You can submit each file separately, or submit them together.)
|
||||
|
||||
To see your best results so far:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/query hw5
|
||||
|
||||
|
||||
Textbooks for References:
|
||||
[1] CLRS Ch. 15
|
||||
[2] KT Ch. 6
|
||||
or Ch. 5 in a previous version:
|
||||
http://cs.furman.edu/~chealy/cs361/kleinbergbook.pdf
|
||||
|
||||
Hint: Among the three coding questions, p3 is the easiest, and p1 is similar to p3.
|
||||
You'll realize that both are very similar to p0 (Fibonacci).
|
||||
p2 is slightly different from these, but still very easy.
|
||||
|
||||
0. (Optional) Is Fibonacci REALLY O(n)?
|
||||
Hint: the value of f(n) itself grows exponentially.
|
||||
|
||||
1. [WILL BE GRADED]
|
||||
Maximum Weighted Independent Set
|
||||
|
||||
[HINT] independent set is a set where no two numbers are neighbors in the original list.
|
||||
see also https://en.wikipedia.org/wiki/Independent_set_(graph_theory)
|
||||
|
||||
input: a list of numbers (could be negative)
|
||||
output: a pair of the max sum and the list of numbers chosen
|
||||
|
||||
>>> max_wis([7,8,5])
|
||||
(12, [7,5])
|
||||
|
||||
>>> max_wis([-1,8,10])
|
||||
(10, [10])
|
||||
|
||||
>>> max_wis([])
|
||||
(0, [])
|
||||
|
||||
[HINT] if all numbers are negative, the optimal solution is 0,
|
||||
since [] is an independent set according to the definition above.
|
||||
|
||||
>>> max_wis([-5, -1, -4])
|
||||
(0, [])
|
||||
|
||||
Q: What's the complexity?
|
||||
|
||||
Include both top-down (max_wis()) and bottom-up (max_wis2()) solutions,
|
||||
and make sure they produce exact same results.
|
||||
We'll only grade the top-down version.
|
||||
|
||||
Tie-breaking: any best solution is considered correct.
|
||||
|
||||
Filename: mis.py
|
||||
|
||||
[HINT] you can also use the naive O(2^n) exhaustive search method to verify your answer.
|
||||
|
||||
|
||||
2. Number of n-node BSTs
|
||||
|
||||
input: n
|
||||
output: number of n-node BSTs
|
||||
|
||||
>>> bsts(2)
|
||||
2
|
||||
>>> bsts(3)
|
||||
5
|
||||
>>> bsts(5)
|
||||
42
|
||||
|
||||
[HINT] There are two 2-node BSTs:
|
||||
2 1
|
||||
/ \
|
||||
1 2
|
||||
Note that all other 2-node BSTs are *isomorphic* to either one.
|
||||
|
||||
Qa: What's the complexity of this DP?
|
||||
|
||||
Qb: What's the name of this famous number series?
|
||||
|
||||
Feel free to use any implementation style.
|
||||
|
||||
Filename: bsts.py
|
||||
|
||||
3. Number of bit strings of length n that has
|
||||
|
||||
1) no two consecutive 0s.
|
||||
2) two consecutive 0s.
|
||||
|
||||
>>> num_no(3)
|
||||
5
|
||||
>>> num_yes(3)
|
||||
3
|
||||
|
||||
[HINT] There are three 3-bit 0/1-strings that have two consecutive 0s.
|
||||
001 100 000
|
||||
The other five 3-bit 0/1-strings have no two consecutive 0s:
|
||||
010 011 101 110 111
|
||||
|
||||
Feel free to choose any implementation style.
|
||||
|
||||
Filename: bitstrings.py
|
||||
|
||||
[HINT] Like problem 1, you can also use the O(2^n) exhaustive search method to verify your answer.
|
||||
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
5. Which part(s) of the course you like the most so far?
|
||||
6. Which part(s) of the course you dislike the most so far?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
114
code/cs325-langs/hws/hw6.txt
Normal file
114
code/cs325-langs/hws/hw6.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
CS 325-001, Algorithms, Fall 2019
|
||||
HW6 - DP (part 2)
|
||||
|
||||
Due on Monday Nov 4, 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Need to submit: report.txt, knapsack_unbounded.py, knapsack_bounded.py.
|
||||
knapsack_bounded.py will be graded for correctness (1%).
|
||||
|
||||
To submit:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw6 report.txt knapsack*.py
|
||||
(You can submit each file separately, or submit them together.)
|
||||
|
||||
To see your best results so far:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/query hw6
|
||||
|
||||
Textbooks for References:
|
||||
[1] KT Ch. 6.4
|
||||
or Ch. 5.3 in a previous version:
|
||||
http://cs.furman.edu/~chealy/cs361/kleinbergbook.pdf
|
||||
[2] KT slides for DP (pages 1-37):
|
||||
https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/06DynamicProgrammingI.pdf
|
||||
[3] Wikipedia: Knapsack (unbounded and 0/1)
|
||||
[4] CLRS Ch. 15
|
||||
|
||||
Please answer time/space complexities for each problem in report.txt.
|
||||
|
||||
0. For each of the coding problems below:
|
||||
(a) Describe a greedy solution.
|
||||
(b) Show a counterexample to the greedy solution.
|
||||
(c) Define the DP subproblem
|
||||
(d) Write the recurrence relations
|
||||
(e) Do not forget base cases
|
||||
(f) Analyze the space and time complexities
|
||||
|
||||
1. Unbounded Knapsack
|
||||
|
||||
You have n items, each with weight w_i and value v_i, and each has infinite copies.
|
||||
**All numbers are positive integers.**
|
||||
What's the best value for a bag of W?
|
||||
|
||||
>>> best(3, [(2, 4), (3, 5)])
|
||||
(5, [0, 1])
|
||||
|
||||
the input to the best() function is W and a list of pairs (w_i, v_i).
|
||||
this output means to take 0 copies of item 1 and 1 copy of item 2.
|
||||
|
||||
tie-breaking: *reverse* lexicographical: i.e., [1, 0] is better than [0, 1]:
|
||||
(i.e., take as many copies from the first item as possible, etc.)
|
||||
|
||||
>>> best(3, [(1, 5), (1, 5)])
|
||||
(15, [3, 0])
|
||||
|
||||
>>> best(3, [(1, 2), (1, 5)])
|
||||
(15, [0, 3])
|
||||
|
||||
>>> best(3, [(1, 2), (2, 5)])
|
||||
(7, [1, 1])
|
||||
|
||||
>>> best(58, [(5, 9), (9, 18), (6, 12)])
|
||||
(114, [2, 4, 2])
|
||||
|
||||
>>> best(92, [(8, 9), (9, 10), (10, 12), (5, 6)])
|
||||
(109, [1, 1, 7, 1])
|
||||
|
||||
Q: What are the time and space complexities?
|
||||
|
||||
filename: knapsack_unbounded.py
|
||||
|
||||
2. [WILL BE GRADED]
|
||||
Bounded Knapsack
|
||||
|
||||
You have n items, each with weight w_i and value v_i, and has c_i copies.
|
||||
**All numbers are positive integers.**
|
||||
What's the best value for a bag of W?
|
||||
|
||||
>>> best(3, [(2, 4, 2), (3, 5, 3)])
|
||||
(5, [0, 1])
|
||||
|
||||
the input to the best() function is W and a list of triples (w_i, v_i, c_i).
|
||||
|
||||
tie-breaking: same as in p1:
|
||||
|
||||
>>> best(3, [(1, 5, 2), (1, 5, 3)])
|
||||
(15, [2, 1])
|
||||
|
||||
>>> best(3, [(1, 5, 1), (1, 5, 3)])
|
||||
(15, [1, 2])
|
||||
|
||||
>>> best(20, [(1, 10, 6), (3, 15, 4), (2, 10, 3)])
|
||||
(130, [6, 4, 1])
|
||||
|
||||
>>> best(92, [(1, 6, 6), (6, 15, 7), (8, 9, 8), (2, 4, 7), (2, 20, 2)])
|
||||
(236, [6, 7, 3, 7, 2])
|
||||
|
||||
Q: What are the time and space complexities?
|
||||
|
||||
filename: knapsack_bounded.py
|
||||
|
||||
You are encouraged to come up with a few other testcases yourself to test your code!
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
5. Which part(s) of the course you like the most so far?
|
||||
6. Which part(s) of the course you dislike the most so far?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
147
code/cs325-langs/hws/hw8.txt
Normal file
147
code/cs325-langs/hws/hw8.txt
Normal file
@@ -0,0 +1,147 @@
|
||||
CS 325-001, Algorithms, Fall 2019
|
||||
HW8 - Graphs (part I); DP (part III)
|
||||
|
||||
Due on Monday November 18, 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Include in your submission: report.txt, topol.py, viterbi.py.
|
||||
viterbi.py will be graded for correctness (1%).
|
||||
|
||||
To submit:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/submit hw8 report.txt {topol,viterbi}.py
|
||||
(You can submit each file separately, or submit them together.)
|
||||
|
||||
To see your best results so far:
|
||||
flip $ /nfs/farm/classes/eecs/fall2019/cs325-001/query hw8
|
||||
|
||||
Textbooks for References:
|
||||
[1] CLRS Ch. 23 (Elementary Graph Algorithms)
|
||||
[2] KT Ch. 3 (graphs), or Ch. 2 in this earlier version:
|
||||
http://cs.furman.edu/~chealy/cs361/kleinbergbook.pdf
|
||||
[3] KT slides (highly recommend!):
|
||||
https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/03Graphs.pdf
|
||||
[4] Jeff Erickson: Ch. 5 (Basic Graph Algorithms):
|
||||
http://jeffe.cs.illinois.edu/teaching/algorithms/book/05-graphs.pdf
|
||||
[5] DPV Ch. 3, 4.2, 4.4, 4.7 (Dasgupta, Papadimitriou, Vazirani)
|
||||
https://www.cs.berkeley.edu/~vazirani/algorithms/chap3.pdf (decomposition of graphs)
|
||||
https://www.cs.berkeley.edu/~vazirani/algorithms/chap4.pdf (paths, shortest paths)
|
||||
[6] my advanced DP tutorial (up to page 16):
|
||||
http://web.engr.oregonstate.edu/~huanlian/slides/COLING-tutorial-anim.pdf
|
||||
|
||||
Please answer non-coding questions in report.txt.
|
||||
|
||||
0. For the following graphs, decide whether they are
|
||||
(1) directed or undirected, (2) dense or sparse, and (3) cyclic or acyclic:
|
||||
|
||||
(a) Facebook
|
||||
(b) Twitter
|
||||
(c) a family
|
||||
(d) V=airports, E=direct_flights
|
||||
(e) a mesh
|
||||
(f) V=courses, E=prerequisites
|
||||
(g) a tree
|
||||
(h) V=linux_software_packages, E=dependencies
|
||||
(i) DP subproblems for 0-1 knapsack
|
||||
|
||||
Can you name a very big dense graph?
|
||||
|
||||
1. Topological Sort
|
||||
|
||||
For a given directed graph, output a topological order if it exists.
|
||||
|
||||
Tie-breaking: ARBITRARY tie-breaking. This will make the code
|
||||
and time complexity analysis a lot easier.
|
||||
|
||||
e.g., for the following example:
|
||||
|
||||
0 --> 2 --> 3 --> 5 --> 6
|
||||
/ \ | / \
|
||||
/ \ v / \
|
||||
1 > 4 > 7
|
||||
|
||||
>>> order(8, [(0,2), (1,2), (2,3), (2,4), (3,4), (3,5), (4,5), (5,6), (5,7)])
|
||||
[0, 1, 2, 3, 4, 5, 6, 7]
|
||||
|
||||
Note that order() takes two arguments, n and list_of_edges,
|
||||
where n specifies that the nodes are named 0..(n-1).
|
||||
|
||||
If we flip the (3,4) edge:
|
||||
|
||||
>>> order(8, [(0,2), (1,2), (2,3), (2,4), (4,3), (3,5), (4,5), (5,6), (5,7)])
|
||||
[0, 1, 2, 4, 3, 5, 6, 7]
|
||||
|
||||
If there is a cycle, return None
|
||||
|
||||
>>> order(4, [(0,1), (1,2), (2,1), (2,3)])
|
||||
None
|
||||
|
||||
Other cases:
|
||||
|
||||
>>> order(5, [(0,1), (1,2), (2,3), (3,4)])
|
||||
[0, 1, 2, 3, 4]
|
||||
|
||||
>>> order(5, [])
|
||||
[0, 1, 2, 3, 4] # could be any order
|
||||
|
||||
>>> order(3, [(1,2), (2,1)])
|
||||
None
|
||||
|
||||
>>> order(1, [(0,0)]) # self-loop
|
||||
None
|
||||
|
||||
Tie-breaking: arbitrary (any valid topological order is fine).
|
||||
|
||||
filename: topol.py
|
||||
|
||||
questions:
|
||||
(a) did you realize that bottom-up implementations of DP use (implicit) topological orderings?
|
||||
e.g., what is the topological ordering in your (or my) bottom-up bounded knapsack code?
|
||||
(b) what about top-down implementations? what order do they use to traverse the graph?
|
||||
(c) does that suggest there is a top-down solution for topological sort as well?
|
||||
|
||||
2. [WILL BE GRADED]
|
||||
Viterbi Algorithm For Longest Path in DAG (see DPV 4.7, [2], CLRS problem 15-1)
|
||||
|
||||
Recall that the Viterbi algorithm has just two steps:
|
||||
a) get a topological order (use problem 1 above)
|
||||
b) follow that order, and do either forward or backward updates
|
||||
|
||||
This algorithm captures all DP problems on DAGs, for example,
|
||||
longest path, shortest path, number of paths, etc.
|
||||
|
||||
In this problem, given a DAG (guaranteed acyclic!), output a pair (l, p)
|
||||
where l is the length of the longest path (number of edges), and p is the path. (you can think of each edge being unit cost)
|
||||
|
||||
e.g., for the above example:
|
||||
|
||||
>>> longest(8, [(0,2), (1,2), (2,3), (2,4), (3,4), (3,5), (4,5), (5,6), (5,7)])
|
||||
(5, [0, 2, 3, 4, 5, 6])
|
||||
|
||||
>>> longest(8, [(0,2), (1,2), (2,3), (2,4), (4,3), (3,5), (4,5), (5,6), (5,7)])
|
||||
(5, [0, 2, 4, 3, 5, 6])
|
||||
|
||||
>>> longest(8, [(0,1), (0,2), (1,2), (2,3), (2,4), (4,3), (3,5), (4,5), (5,6), (5,7), (6,7)])
|
||||
(7, [0, 1, 2, 4, 3, 5, 6, 7]) # unique answer
|
||||
|
||||
Note that longest() takes two arguments, n and list_of_edges,
|
||||
where n specifies that the nodes are named 0..(n-1).
|
||||
|
||||
Tie-breaking: arbitrary. any longest path is fine.
|
||||
|
||||
Filename: viterbi.py
|
||||
|
||||
Note: you can use this program to solve MIS, knapsacks, coins, etc.
|
||||
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
5. Any other comments?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
166
code/cs325-langs/hws/hw9.txt
Normal file
166
code/cs325-langs/hws/hw9.txt
Normal file
@@ -0,0 +1,166 @@
|
||||
CS 325, Algorithms, Fall 2019
|
||||
HW9 - Graphs (part 2), DP (part 4)
|
||||
|
||||
Due Monday Nov 25, 11:59pm.
|
||||
No late submission will be accepted.
|
||||
|
||||
Include in your submission: report.txt, dijkstra.py, nbest.py.
|
||||
dijkstra.py will be graded for correctness (1%).
|
||||
|
||||
Textbooks for References:
|
||||
[1] CLRS Ch. 22 (graph)
|
||||
[2] my DP tutorial (up to page 16):
|
||||
http://web.engr.oregonstate.edu/~huanlian/slides/COLING-tutorial-anim.pdf
|
||||
[3] DPV Ch. 3, 4.2, 4.4, 4.7, 6 (Dasgupta, Papadimitriou, Vazirani)
|
||||
https://www.cs.berkeley.edu/~vazirani/algorithms/chap3.pdf
|
||||
https://www.cs.berkeley.edu/~vazirani/algorithms/chap4.pdf
|
||||
https://www.cs.berkeley.edu/~vazirani/algorithms/chap6.pdf
|
||||
[4] KT Ch. 6 (DP)
|
||||
http://www.aw-bc.com/info/kleinberg/assets/downloads/ch6.pdf
|
||||
[5] KT slides: Greedy II (Dijkstra)
|
||||
http://www.cs.princeton.edu/~wayne/kleinberg-tardos/
|
||||
|
||||
***Please answer time/space complexities for each problem in report.txt.
|
||||
|
||||
1. [WILL BE GRADED]
|
||||
Dijkstra (see CLRS 24.3 and DPV 4.4)
|
||||
|
||||
Given an undirected graph, find the shortest path from source (node 0)
|
||||
to target (node n-1).
|
||||
|
||||
Edge weights are guaranteed to be non-negative, since Dijkstra doesn't work
|
||||
with negative weights, e.g.
|
||||
|
||||
3
|
||||
0 ------ 1
|
||||
\ /
|
||||
2 \ / -2
|
||||
\/
|
||||
2
|
||||
|
||||
in this example, Dijkstra would return length 2 (path 0-2),
|
||||
but path 0-1-2 is better (length 1).
|
||||
|
||||
For example (return a pair of shortest-distance and shortest-path):
|
||||
|
||||
1
|
||||
0 ------ 1
|
||||
\ / \
|
||||
5 \ /1 \6
|
||||
\/ 2 \
|
||||
2 ------ 3
|
||||
|
||||
>>> shortest(4, [(0,1,1), (0,2,5), (1,2,1), (2,3,2), (1,3,6)])
|
||||
(4, [0,1,2,3])
|
||||
|
||||
If the target node (n-1) is unreachable from the source (0),
|
||||
return None:
|
||||
|
||||
>>> shortest(5, [(0,1,1), (0,2,5), (1,2,1), (2,3,2), (1,3,6)])
|
||||
None
|
||||
|
||||
Another example:
|
||||
|
||||
1 1
|
||||
0-----1 2-----3
|
||||
|
||||
>>> shortest(4, [(0,1,1), (2,3,1)])
|
||||
None
|
||||
|
||||
Tiebreaking: arbitrary. Any shortest path would do.
|
||||
|
||||
Filename: dijkstra.py
|
||||
|
||||
Hint: please use heapdict from here:
|
||||
https://raw.githubusercontent.com/DanielStutzbach/heapdict/master/heapdict.py
|
||||
|
||||
>>> from heapdict import heapdict
|
||||
>>> h = heapdict()
|
||||
>>> h['a'] = 3
|
||||
>>> h['b'] = 1
|
||||
>>> h.peekitem()
|
||||
('b', 1)
|
||||
>>> h['a'] = 0
|
||||
>>> h.peekitem()
|
||||
('a', 0)
|
||||
>>> h.popitem()
|
||||
('a', 0)
|
||||
>>> len(h)
|
||||
1
|
||||
>>> 'a' in h
|
||||
False
|
||||
>>> 'b' in h
|
||||
True
|
||||
|
||||
You don't need to submit heapdict.py; we have it in our grader.
|
||||
|
||||
|
||||
2. [Redo the nbest question from Midterm, preparing for HW10 part 3]
|
||||
|
||||
Given k pairs of lists A_i and B_i (0 <= i < k), each with n sorted numbers,
|
||||
find the n smallest pairs in all the (k n^2) pairs.
|
||||
We say (x,y) < (x', y') if and only if x+y < x'+y'.
|
||||
Tie-breaking: lexicographical (i.e., prefer smaller x).
|
||||
|
||||
You can base your code on the skeleton from the Midterm:
|
||||
|
||||
from heapq import heappush, heappop
|
||||
def nbest(ABs): # no need to pass in k or n
|
||||
k = len(ABs)
|
||||
n = len(ABs[0][0])
|
||||
def trypush(i, p, q): # push pair (A_i,p, B_i,q) if possible
|
||||
A, B = ABs[i] # A_i, B_i
|
||||
if p < n and q < n and ______________________________:
|
||||
heappush(h, (________________, i, p, q, (A[p],B[q])))
|
||||
used.add((i, p, q))
|
||||
h, used = ___________________ # initialize
|
||||
for i in range(k): # NEED TO OPTIMIZE
|
||||
trypush(______________)
|
||||
for _ in range(n):
|
||||
_, i, p, q, pair = ________________
|
||||
yield pair # return the next pair (in a lazy list)
|
||||
_______________________
|
||||
_______________________
|
||||
|
||||
|
||||
But recall we had two optimizations to speed up the first for-loop (queue initialization):
|
||||
|
||||
(1) using heapify instead of k initial pushes. You need to implement this (very easy).
|
||||
|
||||
(2) using qselect to choose top n out of the k bests. This one is OPTIONAL.
|
||||
|
||||
Analyze the time complexity for the version you implemented.
|
||||
|
||||
>>> list(nbest([([1,2,4], [2,3,5]), ([0,2,4], [3,4,5])]))
|
||||
|
||||
[(0, 3), (1, 2), (0, 4)]
|
||||
|
||||
>>> list(nbest([([-1,2],[1,4]), ([0,2],[3,4]), ([0,1],[4,6]), ([-1,2],[1,5])]))
|
||||
[(-1, 1), (-1, 1)]
|
||||
|
||||
>>> list(nbest([([5,6,10,14],[3,5,10,14]),([2,7,9,11],[3,8,12,16]),([1,3,8,10],[5,9,10,11]),([1,2,3,5],[3,4,9,10]),([4,5,9,10],[2,4,6,11]),([4,6,10,13],[2,3,5,9]),([3,7,10,12],[1,2,5,10]),([5,9,14,15],[4,8,13,14])]))
|
||||
|
||||
[(1, 3), (3, 1), (1, 4), (2, 3)]
|
||||
|
||||
>>> list(nbest([([1,6,8,13],[5,8,11,12]),([1,2,3,5],[5,9,11,13]),([3,5,7,10],[4,6,7,11]),([1,4,7,8],[4,9,11,15]),([4,8,10,13],[4,6,10,11]),([4,8,12,15],[5,10,11,13]),([2,3,4,8],[4,7,11,15]),([4,5,10,15],[5,6,7,8])]))
|
||||
|
||||
[(1, 4), (1, 5), (1, 5), (2, 4)]
|
||||
|
||||
This problem prepares you for the hardest question in HW10 (part 3).
|
||||
|
||||
Filename: nbest.py
|
||||
|
||||
|
||||
|
||||
Debriefing (required!): --------------------------
|
||||
|
||||
0. What's your name?
|
||||
1. Approximately how many hours did you spend on this assignment?
|
||||
2. Would you rate it as easy, moderate, or difficult?
|
||||
3. Did you work on it mostly alone, or mostly with other people?
|
||||
4. How deeply do you feel you understand the material it covers (0%-100%)?
|
||||
5. Any other comments?
|
||||
|
||||
This section is intended to help us calibrate the homework assignments.
|
||||
Your answers to this section will *not* affect your grade; however, skipping it
|
||||
will certainly do.
|
||||
19
code/cs325-langs/sols/hw1.lang
Normal file
19
code/cs325-langs/sols/hw1.lang
Normal file
@@ -0,0 +1,19 @@
|
||||
qselect(xs,k) =
|
||||
~xs -> {
|
||||
pivot <- xs[0]!
|
||||
left <- xs[#0 <= pivot]
|
||||
right <- xs[#0 > pivot]
|
||||
} ->
|
||||
if k > |left| + 1 then qselect(right, k - |left| - 1)
|
||||
else if k == |left| + 1 then [pivot]
|
||||
else qselect(left, k);
|
||||
|
||||
_search(xs, k) =
|
||||
if xs[1] == k then xs
|
||||
else if xs[1] > k then _search(xs[0], k)
|
||||
else _search(xs[2], k);
|
||||
|
||||
sorted(xs) = sorted(xs[0]) ++ [xs[1]] ++ sorted(xs[2]);
|
||||
search(xs, k) = |_search(xs, k)| != 0;
|
||||
insert(xs, k) = _insert(k, _search(xs, k));
|
||||
_insert(k, xs) = if |xs| == 0 then xs << [] << k << [] else xs
|
||||
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;
|
||||
}
|
||||
95
code/cs325-langs/sols/hw3.lang
Normal file
95
code/cs325-langs/sols/hw3.lang
Normal file
@@ -0,0 +1,95 @@
|
||||
function qselect(xs, k, c) {
|
||||
if xs == [] {
|
||||
return 0;
|
||||
}
|
||||
|
||||
traverser bisector(list: xs, span: (0,len(xs)));
|
||||
traverser pivot(list: xs, random: true);
|
||||
|
||||
let pivotE = pop!(pivot);
|
||||
let (leftList, rightList) = bisect!(bisector, (x) -> c(x) < c(pivotE));
|
||||
|
||||
if k > len(leftList) + 1 {
|
||||
return qselect(rightList, k - len(leftList) - 1, c);
|
||||
} elsif k == len(leftList) + 1 {
|
||||
return pivotE;
|
||||
} else {
|
||||
return qselect(leftList, k, c);
|
||||
}
|
||||
}
|
||||
|
||||
function closestUnsorted(xs, k, n) {
|
||||
let min = qselect(list(xs), k, (x) -> abs(x - n));
|
||||
let out = [];
|
||||
let countEqual = k;
|
||||
|
||||
traverser iter(list: xs, span: (0, len(xs)));
|
||||
while valid!(iter) {
|
||||
if abs(at!(iter)-n) < abs(min-n) {
|
||||
let countEqual = countEqual - 1;
|
||||
}
|
||||
step!(iter);
|
||||
}
|
||||
|
||||
traverser iter(list: xs, span: (0, len(xs)));
|
||||
while valid!(iter) {
|
||||
if abs(at!(iter)-n) == abs(min-n) and countEqual > 0 {
|
||||
let countEqual = countEqual - 1;
|
||||
let out = out + [at!(iter)];
|
||||
} elsif abs(at!(iter)-n) < abs(min-n) {
|
||||
let out = out + [at!(iter)];
|
||||
}
|
||||
step!(iter);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function closestSorted(xs, k, n) {
|
||||
let start = bisect(xs, n);
|
||||
let counter = 0;
|
||||
traverser left(list: xs, span: (0, start), reverse: true);
|
||||
traverser right(list: xs, span: (start, len(xs)));
|
||||
|
||||
while counter != k and canstep!(left) and valid!(right) {
|
||||
if abs(at!(left, 1) - n) < abs(at!(right) - n) {
|
||||
step!(left);
|
||||
} else {
|
||||
step!(right);
|
||||
}
|
||||
let counter = counter + 1;
|
||||
}
|
||||
|
||||
while counter != k and (canstep!(left) or valid!(right)) {
|
||||
if canstep!(left) { step!(left); }
|
||||
else { step!(right); }
|
||||
let counter = counter + 1;
|
||||
}
|
||||
|
||||
return subset!(left, right);
|
||||
}
|
||||
|
||||
sorted function xyz(xs, k) {
|
||||
traverser x(list: xs, span: (0,len(xs)));
|
||||
let dest = [];
|
||||
|
||||
while valid!(x) {
|
||||
traverser z(list: xs, span: (pos!(x)+2,len(xs)));
|
||||
traverser y(list: xs, span: (pos!(x)+1,pos!(z)));
|
||||
|
||||
while valid!(y) and valid!(z) {
|
||||
if at!(x) + at!(y) == at!(z) {
|
||||
let dest = dest + [(at!(x), at!(y), at!(z))];
|
||||
step!(z);
|
||||
} elsif at!(x) + at!(y) > at!(z) {
|
||||
step!(z);
|
||||
} else {
|
||||
step!(y);
|
||||
}
|
||||
}
|
||||
|
||||
step!(x);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
15
code/cs325-langs/src/Common.hs
Normal file
15
code/cs325-langs/src/Common.hs
Normal file
@@ -0,0 +1,15 @@
|
||||
module Common where
|
||||
import PythonAst
|
||||
import PythonGen
|
||||
import Text.Parsec
|
||||
|
||||
compile :: (String -> String -> Either ParseError p) -> (p -> [PyStmt]) -> String -> IO ()
|
||||
compile p t f = do
|
||||
let inputName = f ++ ".lang"
|
||||
let outputName = f ++ ".py"
|
||||
file <- readFile inputName
|
||||
let either = p inputName file
|
||||
case either of
|
||||
Right prog -> writeFile outputName (translate $ t prog)
|
||||
Left e -> print e
|
||||
|
||||
90
code/cs325-langs/src/CommonParsing.hs
Normal file
90
code/cs325-langs/src/CommonParsing.hs
Normal file
@@ -0,0 +1,90 @@
|
||||
module CommonParsing where
|
||||
import Data.Char
|
||||
import Data.Functor
|
||||
import Text.Parsec
|
||||
import Text.Parsec.Char
|
||||
import Text.Parsec.Combinator
|
||||
|
||||
type Parser a b = Parsec String a b
|
||||
|
||||
kw :: String -> Parser a ()
|
||||
kw s = try $ string s <* spaces $> ()
|
||||
|
||||
kwIf :: Parser a ()
|
||||
kwIf = kw "if"
|
||||
|
||||
kwThen :: Parser a ()
|
||||
kwThen = kw "then"
|
||||
|
||||
kwElse :: Parser a ()
|
||||
kwElse = kw "else"
|
||||
|
||||
kwElsif :: Parser a ()
|
||||
kwElsif = kw "elsif"
|
||||
|
||||
kwWhile :: Parser a ()
|
||||
kwWhile = kw "while"
|
||||
|
||||
kwState :: Parser a ()
|
||||
kwState = kw "state"
|
||||
|
||||
kwEffect :: Parser a ()
|
||||
kwEffect = kw "effect"
|
||||
|
||||
kwCombine :: Parser a ()
|
||||
kwCombine = kw "combine"
|
||||
|
||||
kwRand :: Parser a ()
|
||||
kwRand = kw "rand"
|
||||
|
||||
kwFunction :: Parser a ()
|
||||
kwFunction = kw "function"
|
||||
|
||||
kwSorted :: Parser a ()
|
||||
kwSorted = kw "sorted"
|
||||
|
||||
kwLet :: Parser a ()
|
||||
kwLet = kw "let"
|
||||
|
||||
kwTraverser :: Parser a ()
|
||||
kwTraverser = kw "traverser"
|
||||
|
||||
kwReturn :: Parser a ()
|
||||
kwReturn = kw "return"
|
||||
|
||||
op :: String -> op -> Parser a op
|
||||
op s o = string s $> o
|
||||
|
||||
int :: Parser a Int
|
||||
int = read <$> (many1 digit <* spaces)
|
||||
|
||||
var :: [String] -> Parser a String
|
||||
var reserved =
|
||||
do
|
||||
c <- satisfy $ \c -> isLetter c || c == '_'
|
||||
cs <- many (satisfy isLetter <|> digit) <* spaces
|
||||
let name = c:cs
|
||||
if name `elem` reserved
|
||||
then fail "Can't use reserved keyword as identifier"
|
||||
else return name
|
||||
|
||||
list :: Char -> Char -> Char -> Parser a b -> Parser a [b]
|
||||
list co cc cd pe = surround co cc $ sepBy pe (char cd >> spaces)
|
||||
|
||||
surround :: Char -> Char -> Parser a b -> Parser a b
|
||||
surround c1 c2 pe =
|
||||
do
|
||||
char c1 >> spaces
|
||||
e <- pe
|
||||
spaces >> char c2 >> spaces
|
||||
return e
|
||||
|
||||
level :: (o -> e -> e -> e) -> Parser a o -> Parser a e -> Parser a e
|
||||
level c po pe =
|
||||
do
|
||||
e <- pe <* spaces
|
||||
ops <- many $ try $ (flip . c <$> (po <* spaces) <*> pe) <* spaces
|
||||
return $ foldl (flip ($)) e ops
|
||||
|
||||
precedence :: (o -> e -> e -> e) -> Parser a e -> [ Parser a o ] -> Parser a e
|
||||
precedence = foldl . flip . level
|
||||
393
code/cs325-langs/src/LanguageOne.hs
Normal file
393
code/cs325-langs/src/LanguageOne.hs
Normal file
@@ -0,0 +1,393 @@
|
||||
module LanguageOne where
|
||||
import qualified PythonAst as Py
|
||||
import qualified CommonParsing as P
|
||||
import Data.Bifunctor
|
||||
import Data.Char
|
||||
import Data.Functor
|
||||
import qualified Data.Map as Map
|
||||
import Data.Maybe
|
||||
import qualified Data.Set as Set
|
||||
import Text.Parsec
|
||||
import Text.Parsec.Char
|
||||
import Text.Parsec.Combinator
|
||||
import Control.Monad.State
|
||||
|
||||
{- Data Types -}
|
||||
data PossibleType = List | Any deriving Eq
|
||||
|
||||
data SelectorMarker = None | Remove
|
||||
|
||||
data Op
|
||||
= Add
|
||||
| Subtract
|
||||
| Multiply
|
||||
| Divide
|
||||
| Insert
|
||||
| Concat
|
||||
| LessThan
|
||||
| LessThanEq
|
||||
| GreaterThan
|
||||
| GreaterThanEq
|
||||
| Equal
|
||||
| NotEqual
|
||||
| And
|
||||
| Or
|
||||
|
||||
data Selector = Selector String Expr
|
||||
|
||||
data Expr
|
||||
= Var String
|
||||
| IntLiteral Int
|
||||
| ListLiteral [Expr]
|
||||
| Split Expr [Selector] Expr
|
||||
| IfElse Expr Expr Expr
|
||||
| BinOp Op Expr Expr
|
||||
| FunctionCall Expr [Expr]
|
||||
| LengthOf Expr
|
||||
| Random
|
||||
| Access Expr Expr SelectorMarker
|
||||
| Parameter Int
|
||||
|
||||
data Function = Function String [String] Expr
|
||||
|
||||
data Prog = Prog [Function]
|
||||
|
||||
{- Parser -}
|
||||
type Parser = Parsec String (Maybe Int)
|
||||
|
||||
parseVar :: Parser String
|
||||
parseVar = P.var ["if", "then", "else", "var"]
|
||||
|
||||
parseThis :: Parser Expr
|
||||
parseThis =
|
||||
do
|
||||
char '&'
|
||||
contextNum <- getState
|
||||
spaces
|
||||
return (Var $ "context_" ++ show contextNum)
|
||||
|
||||
parseList :: Parser Expr
|
||||
parseList = ListLiteral <$>
|
||||
do
|
||||
char '[' >> spaces
|
||||
es <- sepBy parseExpr (char ',' >> spaces)
|
||||
spaces >> char ']' >> spaces
|
||||
return es
|
||||
|
||||
parseSplit :: Parser Expr
|
||||
parseSplit =
|
||||
do
|
||||
char '~' >> spaces
|
||||
e <- parseExpr
|
||||
spaces >> string "->"
|
||||
spaces >> char '{'
|
||||
contextNum <- getState
|
||||
putState $ return $ 1 + fromMaybe (-1) contextNum
|
||||
es <- many1 (spaces >> parseSelector)
|
||||
putState contextNum
|
||||
spaces >> char '}' >> spaces >> string "->" >> spaces
|
||||
e' <- parseExpr
|
||||
spaces
|
||||
return $ Split e es e'
|
||||
|
||||
parseSelectorMarker :: Parser SelectorMarker
|
||||
parseSelectorMarker = (char '!' >> return Remove) <|> return None
|
||||
|
||||
parseSelector :: Parser Selector
|
||||
parseSelector =
|
||||
do
|
||||
name <- parseVar
|
||||
spaces >> string "<-" >> spaces
|
||||
expr <- parseExpr
|
||||
spaces
|
||||
return $ Selector name expr
|
||||
|
||||
parseIfElse :: Parser Expr
|
||||
parseIfElse =
|
||||
do
|
||||
P.kwIf >> spaces
|
||||
ec <- parseExpr
|
||||
spaces >> P.kwThen >> spaces
|
||||
et <- parseExpr
|
||||
spaces >> P.kwElse >> spaces
|
||||
ee <- parseExpr
|
||||
spaces
|
||||
return $ IfElse ec et ee
|
||||
|
||||
parseLength :: Parser Expr
|
||||
parseLength =
|
||||
do
|
||||
char '|' >> spaces
|
||||
e <- parseExpr
|
||||
spaces >> char '|' >> spaces
|
||||
return $ LengthOf e
|
||||
|
||||
parseParameter :: Parser Expr
|
||||
parseParameter =
|
||||
do
|
||||
char '#'
|
||||
d <- digit
|
||||
spaces
|
||||
return $ Parameter $ read [d]
|
||||
|
||||
parseParenthesized :: Parser Expr
|
||||
parseParenthesized =
|
||||
do
|
||||
char '(' >> spaces
|
||||
e <- parseExpr
|
||||
spaces >> char ')' >> spaces
|
||||
return e
|
||||
|
||||
parseBasicExpr :: Parser Expr
|
||||
parseBasicExpr = choice
|
||||
[ IntLiteral <$> P.int
|
||||
, parseThis
|
||||
, parseList
|
||||
, parseSplit
|
||||
, parseLength
|
||||
, parseParameter
|
||||
, parseParenthesized
|
||||
, Var <$> try parseVar
|
||||
, P.kwRand $> Random
|
||||
, parseIfElse
|
||||
]
|
||||
|
||||
parsePostfix :: Parser (Expr -> Expr)
|
||||
parsePostfix = parsePostfixAccess <|> parsePostfixCall
|
||||
|
||||
parsePostfixAccess :: Parser (Expr -> Expr)
|
||||
parsePostfixAccess =
|
||||
do
|
||||
char '[' >> spaces
|
||||
e <- parseExpr
|
||||
spaces >> char ']' >> spaces
|
||||
marker <- parseSelectorMarker
|
||||
spaces
|
||||
return $ \e' -> Access e' e marker
|
||||
|
||||
parsePostfixCall :: Parser (Expr -> Expr)
|
||||
parsePostfixCall =
|
||||
do
|
||||
char '(' >> spaces
|
||||
es <- sepBy parseExpr (char ',' >> spaces)
|
||||
char ')' >> spaces
|
||||
return $ flip FunctionCall es
|
||||
|
||||
parsePostfixedExpr :: Parser Expr
|
||||
parsePostfixedExpr =
|
||||
do
|
||||
eb <- parseBasicExpr
|
||||
spaces
|
||||
ps <- many parsePostfix
|
||||
return $ foldl (flip ($)) eb ps
|
||||
|
||||
parseExpr :: Parser Expr
|
||||
parseExpr = P.precedence BinOp parsePostfixedExpr
|
||||
[ P.op "*" Multiply, P.op "/" Divide
|
||||
, P.op "+" Add, P.op "-" Subtract
|
||||
, P.op "<<" Insert
|
||||
, P.op "++" Concat
|
||||
, try (P.op "<=" LessThanEq) <|> try (P.op ">=" GreaterThanEq) <|>
|
||||
P.op "<" LessThan <|> P.op ">" GreaterThan <|>
|
||||
P.op "==" Equal <|> P.op "!=" NotEqual
|
||||
, P.op "&&" And <|> P.op "||" Or
|
||||
]
|
||||
|
||||
parseFunction :: Parser Function
|
||||
parseFunction =
|
||||
do
|
||||
name <- parseVar
|
||||
spaces >> char '(' >> spaces
|
||||
vs <- sepBy parseVar (char ',' >> spaces)
|
||||
spaces >> char ')' >> spaces >> char '=' >> spaces
|
||||
body <- parseExpr
|
||||
spaces
|
||||
return $ Function name vs body
|
||||
|
||||
parseProg :: Parser Prog
|
||||
parseProg = Prog <$> sepBy1 parseFunction (char ';' >> spaces)
|
||||
|
||||
parse :: SourceName -> String -> Either ParseError Prog
|
||||
parse = runParser parseProg Nothing
|
||||
|
||||
{- "Type" checker -}
|
||||
mergePossibleType :: PossibleType -> PossibleType -> PossibleType
|
||||
mergePossibleType List _ = List
|
||||
mergePossibleType _ List = List
|
||||
mergePossibleType _ _ = Any
|
||||
|
||||
getPossibleType :: String -> Expr -> PossibleType
|
||||
getPossibleType s (Var s') = if s == s' then List else Any
|
||||
getPossibleType _ (ListLiteral _) = List
|
||||
getPossibleType s (Split _ _ e) = getPossibleType s e
|
||||
getPossibleType s (IfElse i t e) =
|
||||
foldl1 mergePossibleType $ map (getPossibleType s) [i, t, e]
|
||||
getPossibleType _ (BinOp Insert _ _) = List
|
||||
getPossibleType _ (BinOp Concat _ _) = List
|
||||
getPossibleType _ _ = Any
|
||||
|
||||
{- Translator -}
|
||||
type Translator = Control.Monad.State.State (Map.Map String [String], Int)
|
||||
|
||||
currentTemp :: Translator String
|
||||
currentTemp = do
|
||||
t <- gets snd
|
||||
return $ "temp" ++ show t
|
||||
|
||||
incrementTemp :: Translator String
|
||||
incrementTemp = do
|
||||
modify (second (+1))
|
||||
currentTemp
|
||||
|
||||
hasLambda :: Expr -> Bool
|
||||
hasLambda (ListLiteral es) = any hasLambda es
|
||||
hasLambda (Split e ss r) =
|
||||
hasLambda e || any (\(Selector _ e') -> hasLambda e') ss || hasLambda r
|
||||
hasLambda (IfElse i t e) = hasLambda i || hasLambda t || hasLambda e
|
||||
hasLambda (BinOp o l r) = hasLambda l || hasLambda r
|
||||
hasLambda (FunctionCall e es) = any hasLambda $ e : es
|
||||
hasLambda (LengthOf e) = hasLambda e
|
||||
hasLambda (Access e _ _) = hasLambda e
|
||||
hasLambda Parameter{} = True
|
||||
hasLambda _ = False
|
||||
|
||||
translate :: Prog -> [Py.PyStmt]
|
||||
translate p = fst $ runState (translateProg p) (Map.empty, 0)
|
||||
|
||||
translateProg :: Prog -> Translator [Py.PyStmt]
|
||||
translateProg (Prog fs) = concat <$> traverse translateFunction fs
|
||||
|
||||
translateFunction :: Function -> Translator [Py.PyStmt]
|
||||
translateFunction (Function n ps ex) = do
|
||||
let createIf p = Py.BinOp Py.Equal (Py.Var p) (Py.ListLiteral [])
|
||||
let createReturn p = Py.IfElse (createIf p) [Py.Return (Py.Var p)] [] Nothing
|
||||
let fastReturn = [createReturn p | p <- take 1 ps, getPossibleType p ex == List]
|
||||
(ss, e) <- translateExpr ex
|
||||
return $ return $ Py.FunctionDef n ps $ fastReturn ++ ss ++ [Py.Return e]
|
||||
|
||||
translateSelector :: Selector -> Translator Py.PyStmt
|
||||
translateSelector (Selector n e) =
|
||||
let
|
||||
cacheCheck = Py.NotIn (Py.StrLiteral n) (Py.Var "cache")
|
||||
cacheAccess = Py.Access (Py.Var "cache") [Py.StrLiteral n]
|
||||
cacheSet = Py.Assign (Py.AccessPat (Py.Var "cache") [Py.StrLiteral n])
|
||||
body e' = [ Py.IfElse cacheCheck [cacheSet e'] [] Nothing, Py.Return cacheAccess]
|
||||
in
|
||||
do
|
||||
(ss, e') <- translateExpr e
|
||||
vs <- gets fst
|
||||
let callPrereq p = Py.Standalone $ Py.FunctionCall (Py.Var p) []
|
||||
let prereqs = maybe [] (map callPrereq) $ Map.lookup n vs
|
||||
return $ Py.FunctionDef n [] $ ss ++ prereqs ++ body e'
|
||||
|
||||
translateExpr :: Expr -> Translator ([Py.PyStmt], Py.PyExpr)
|
||||
translateExpr (Var s) = do
|
||||
vs <- gets fst
|
||||
let sVar = Py.Var s
|
||||
let expr = if Map.member s vs then Py.FunctionCall sVar [] else sVar
|
||||
return ([], expr)
|
||||
translateExpr (IntLiteral i) = return ([], Py.IntLiteral i)
|
||||
translateExpr (ListLiteral l) = do
|
||||
tl <- mapM translateExpr l
|
||||
return (concatMap fst tl, Py.ListLiteral $ map snd tl)
|
||||
translateExpr (Split e ss e') = do
|
||||
vs <- gets fst
|
||||
let cacheAssign = Py.Assign (Py.VarPat "cache") (Py.DictLiteral [])
|
||||
let cacheStmt = [ cacheAssign | Map.size vs == 0 ]
|
||||
let vnames = map (\(Selector n es) -> n) ss
|
||||
let prereqs = snd $ foldl (\(ds, m) (Selector n es) -> (n:ds, Map.insert n ds m)) ([], Map.empty) ss
|
||||
modify $ first $ Map.union prereqs
|
||||
fs <- mapM translateSelector ss
|
||||
(sts, te) <- translateExpr e'
|
||||
modify $ first $ const vs
|
||||
return (cacheStmt ++ fs ++ sts, te)
|
||||
translateExpr (IfElse i t e) = do
|
||||
temp <- incrementTemp
|
||||
let tempPat = Py.VarPat temp
|
||||
(ists, ie) <- translateExpr i
|
||||
(tsts, te) <- translateExpr t
|
||||
(ests, ee) <- translateExpr e
|
||||
let thenSts = tsts ++ [Py.Assign tempPat te]
|
||||
let elseSts = ests ++ [Py.Assign tempPat ee]
|
||||
let newIf = Py.IfElse ie thenSts [] $ Just elseSts
|
||||
return (ists ++ [newIf], Py.Var temp)
|
||||
translateExpr (BinOp o l r) = do
|
||||
(lsts, le) <- translateExpr l
|
||||
(rsts, re) <- translateExpr r
|
||||
(opsts, oe) <- translateOp o le re
|
||||
return (lsts ++ rsts ++ opsts, oe)
|
||||
translateExpr (FunctionCall f ps) = do
|
||||
(fsts, fe) <- translateExpr f
|
||||
tps <- mapM translateExpr ps
|
||||
return (fsts ++ concatMap fst tps, Py.FunctionCall fe $ map snd tps)
|
||||
translateExpr (LengthOf e) =
|
||||
second (Py.FunctionCall (Py.Var "len") . return) <$> translateExpr e
|
||||
translateExpr (Access e Random m) = do
|
||||
temp <- incrementTemp
|
||||
(sts, ce) <- translateExpr e
|
||||
let lenExpr = Py.FunctionCall (Py.Var "len") [Py.Var temp]
|
||||
let randExpr = Py.FunctionCall (Py.Var "randint") [ Py.IntLiteral 0, lenExpr ]
|
||||
return (sts, singleAccess ce randExpr m)
|
||||
translateExpr (Access c i m) = do
|
||||
(csts, ce) <- translateExpr c
|
||||
(ists, ie) <- translateExpr i
|
||||
temp <- incrementTemp
|
||||
if hasLambda i
|
||||
then return (csts ++ ists ++ [createFilterLambda temp ie m], Py.FunctionCall (Py.Var temp) [ce])
|
||||
else return (csts ++ ists, singleAccess ce ie m)
|
||||
translateExpr (Parameter i) = return $ ([], Py.Var $ "arg" ++ show i)
|
||||
translateExpr _ = fail "Invalid expression"
|
||||
|
||||
singleAccess :: Py.PyExpr -> Py.PyExpr -> SelectorMarker -> Py.PyExpr
|
||||
singleAccess c i None = Py.Access c [i]
|
||||
singleAccess c i Remove = Py.FunctionCall (Py.Member c "pop") [i]
|
||||
|
||||
createFilterLambda :: String -> Py.PyExpr -> SelectorMarker -> Py.PyStmt
|
||||
createFilterLambda s e None = Py.FunctionDef s ["arg"]
|
||||
[ Py.Assign (Py.VarPat "out") (Py.ListLiteral [])
|
||||
, Py.For (Py.VarPat "arg0") (Py.Var "arg")
|
||||
[ Py.IfElse e
|
||||
[ Py.Standalone $ Py.FunctionCall (Py.Member (Py.Var "out") "append")
|
||||
[ Py.Var "arg0" ]
|
||||
]
|
||||
[]
|
||||
Nothing
|
||||
]
|
||||
, Py.Return $ Py.Var "out"
|
||||
]
|
||||
createFilterLambda s e Remove = Py.FunctionDef s ["arg"]
|
||||
[ Py.Assign (Py.VarPat "i") $ Py.IntLiteral 0
|
||||
, Py.Assign (Py.VarPat "out") (Py.ListLiteral [])
|
||||
, Py.While (Py.BinOp Py.LessThan (Py.Var "i") $ Py.FunctionCall (Py.Var "len") [Py.Var "arg"])
|
||||
[ Py.IfElse e
|
||||
[ Py.Standalone $ Py.FunctionCall (Py.Member (Py.Var "out") "append")
|
||||
[ singleAccess (Py.Var "arg") (Py.Var "i") Remove
|
||||
]
|
||||
]
|
||||
[]
|
||||
Nothing
|
||||
, Py.Assign (Py.VarPat "i") (Py.BinOp Py.Add (Py.Var "i") (Py.IntLiteral 1))
|
||||
]
|
||||
, Py.Return $ Py.Var "out"
|
||||
]
|
||||
|
||||
translateOp :: Op -> Py.PyExpr -> Py.PyExpr -> Translator ([Py.PyStmt], Py.PyExpr)
|
||||
translateOp Add l r = return ([], Py.BinOp Py.Add l r)
|
||||
translateOp Subtract l r = return ([], Py.BinOp Py.Subtract l r)
|
||||
translateOp Multiply l r = return ([], Py.BinOp Py.Multiply l r)
|
||||
translateOp Divide l r = return ([], Py.BinOp Py.Divide l r)
|
||||
translateOp LessThan l r = return ([], Py.BinOp Py.LessThan l r)
|
||||
translateOp LessThanEq l r = return ([], Py.BinOp Py.LessThanEq l r)
|
||||
translateOp GreaterThan l r = return ([], Py.BinOp Py.GreaterThan l r)
|
||||
translateOp GreaterThanEq l r = return ([], Py.BinOp Py.GreaterThanEq l r)
|
||||
translateOp Equal l r = return ([], Py.BinOp Py.Equal l r)
|
||||
translateOp NotEqual l r = return ([], Py.BinOp Py.NotEqual l r)
|
||||
translateOp And l r = return ([], Py.BinOp Py.And l r)
|
||||
translateOp Or l r = return ([], Py.BinOp Py.Or l r)
|
||||
translateOp Concat l r = return ([], Py.BinOp Py.Add l r)
|
||||
translateOp Insert l r = do
|
||||
temp <- incrementTemp
|
||||
let assignStmt = Py.Assign (Py.VarPat temp) l
|
||||
let appendFunc = Py.Member (Py.Var temp) "append"
|
||||
let insertStmt = Py.Standalone $ Py.FunctionCall appendFunc [r]
|
||||
return ([assignStmt, insertStmt], Py.Var temp)
|
||||
461
code/cs325-langs/src/LanguageThree.hs
Normal file
461
code/cs325-langs/src/LanguageThree.hs
Normal file
@@ -0,0 +1,461 @@
|
||||
module LanguageThree where
|
||||
import qualified CommonParsing as P
|
||||
import qualified PythonAst as Py
|
||||
import Control.Monad.State
|
||||
import Data.Bifunctor
|
||||
import Data.Foldable
|
||||
import Data.Functor
|
||||
import qualified Data.Map as Map
|
||||
import Data.Maybe
|
||||
import Text.Parsec hiding (State)
|
||||
import Text.Parsec.Char
|
||||
import Text.Parsec.Combinator
|
||||
|
||||
{- Data Types -}
|
||||
data Op
|
||||
= Add
|
||||
| Subtract
|
||||
| Multiply
|
||||
| Divide
|
||||
| LessThan
|
||||
| LessThanEqual
|
||||
| GreaterThan
|
||||
| GreaterThanEqual
|
||||
| Equal
|
||||
| NotEqual
|
||||
| And
|
||||
| Or
|
||||
|
||||
data Expr
|
||||
= TraverserCall String [Expr]
|
||||
| FunctionCall String [Expr]
|
||||
| BinOp Op Expr Expr
|
||||
| Lambda [String] Expr
|
||||
| Var String
|
||||
| IntLiteral Int
|
||||
| BoolLiteral Bool
|
||||
| ListLiteral [Expr]
|
||||
| TupleLiteral [Expr]
|
||||
|
||||
type Branch = (Expr, [Stmt])
|
||||
|
||||
data Stmt
|
||||
= IfElse Branch [Branch] [Stmt]
|
||||
| While Branch
|
||||
| Traverser String [(String, Expr)]
|
||||
| Let Pat Expr
|
||||
| Return Expr
|
||||
| Standalone Expr
|
||||
|
||||
data Pat
|
||||
= VarPat String
|
||||
| TuplePat [Pat]
|
||||
|
||||
data SortedMarker = Sorted | Unsorted deriving Eq
|
||||
|
||||
data Function = Function SortedMarker String [String] [Stmt]
|
||||
|
||||
data Prog = Prog [Function]
|
||||
|
||||
{- Parser -}
|
||||
type Parser = Parsec String ()
|
||||
|
||||
parseVar :: Parser String
|
||||
parseVar = P.var
|
||||
[ "if", "elif", "else"
|
||||
, "while", "let", "traverser"
|
||||
, "function", "sort"
|
||||
, "true", "false"
|
||||
]
|
||||
|
||||
parseBool :: Parser Bool
|
||||
parseBool = (string "true" $> True) <|> (string "false" $> False)
|
||||
|
||||
parseList :: Parser Expr
|
||||
parseList = ListLiteral <$> P.list '[' ']' ',' parseExpr
|
||||
|
||||
parseTupleElems :: Parser [Expr]
|
||||
parseTupleElems = P.list '(' ')' ',' parseExpr
|
||||
|
||||
parseTuple :: Parser Expr
|
||||
parseTuple = do
|
||||
es <- parseTupleElems
|
||||
return $ case es of
|
||||
e:[] -> e
|
||||
_ -> TupleLiteral es
|
||||
|
||||
parseLambda :: Parser Expr
|
||||
parseLambda = try $ do
|
||||
vs <- P.list '(' ')' ',' parseVar
|
||||
string "->" >> spaces
|
||||
Lambda vs <$> parseExpr
|
||||
|
||||
parseCall :: Parser Expr
|
||||
parseCall = try $ do
|
||||
v <- parseVar
|
||||
choice
|
||||
[ TraverserCall v <$> (char '!' *> parseTupleElems)
|
||||
, FunctionCall v <$> parseTupleElems
|
||||
]
|
||||
|
||||
parseBasic :: Parser Expr
|
||||
parseBasic = choice
|
||||
[ IntLiteral <$> P.int
|
||||
, BoolLiteral <$> parseBool
|
||||
, try parseCall
|
||||
, Var <$> parseVar
|
||||
, parseList
|
||||
, parseLambda
|
||||
, parseTuple
|
||||
]
|
||||
|
||||
parseExpr :: Parser Expr
|
||||
parseExpr = P.precedence BinOp parseBasic
|
||||
[ P.op "*" Multiply <|> P.op "/" Divide
|
||||
, P.op "+" Add <|> P.op "-" Subtract
|
||||
, P.op "==" Equal <|> P.op "!=" NotEqual <|>
|
||||
try (P.op "<=" LessThanEqual) <|> P.op "<" LessThan <|>
|
||||
try (P.op ">=" GreaterThanEqual) <|> P.op ">" GreaterThan
|
||||
, P.op "and" And
|
||||
, P.op "or" Or
|
||||
]
|
||||
|
||||
parseBlock :: Parser [Stmt]
|
||||
parseBlock = char '{' >> spaces >> many parseStmt <* char '}' <* spaces
|
||||
|
||||
parseBranch :: Parser Branch
|
||||
parseBranch = (,) <$> (parseExpr <* spaces) <*> parseBlock
|
||||
|
||||
parseIf :: Parser Stmt
|
||||
parseIf = do
|
||||
i <- P.kwIf >> parseBranch
|
||||
els <- many (P.kwElsif >> parseBranch)
|
||||
e <- try (P.kwElse >> parseBlock) <|> return []
|
||||
return $ IfElse i els e
|
||||
|
||||
parseWhile :: Parser Stmt
|
||||
parseWhile = While <$> (P.kwWhile >> parseBranch)
|
||||
|
||||
parseTraverser :: Parser Stmt
|
||||
parseTraverser = Traverser
|
||||
<$> (P.kwTraverser *> parseVar)
|
||||
<*> (P.list '(' ')' ',' parseKey) <* char ';' <* spaces
|
||||
|
||||
parseKey :: Parser (String, Expr)
|
||||
parseKey = (,)
|
||||
<$> (parseVar <* spaces <* char ':' <* spaces)
|
||||
<*> parseExpr
|
||||
|
||||
parseLet :: Parser Stmt
|
||||
parseLet = Let
|
||||
<$> (P.kwLet >> parsePat <* char '=' <* spaces)
|
||||
<*> parseExpr <* char ';' <* spaces
|
||||
|
||||
parseReturn :: Parser Stmt
|
||||
parseReturn = Return <$> (P.kwReturn >> parseExpr <* char ';' <* spaces)
|
||||
|
||||
parsePat :: Parser Pat
|
||||
parsePat = (VarPat <$> parseVar) <|> (TuplePat <$> P.list '(' ')' ',' parsePat)
|
||||
|
||||
parseStmt :: Parser Stmt
|
||||
parseStmt = choice
|
||||
[ parseTraverser
|
||||
, parseLet
|
||||
, parseIf
|
||||
, parseWhile
|
||||
, parseReturn
|
||||
, Standalone <$> (parseExpr <* char ';' <* spaces)
|
||||
]
|
||||
|
||||
parseFunction :: Parser Function
|
||||
parseFunction = Function
|
||||
<$> (P.kwSorted $> Sorted <|> return Unsorted)
|
||||
<*> (P.kwFunction >> parseVar)
|
||||
<*> (P.list '(' ')' ',' parseVar)
|
||||
<*> parseBlock
|
||||
|
||||
parseProg :: Parser Prog
|
||||
parseProg = Prog <$> many parseFunction
|
||||
|
||||
parse :: String -> String -> Either ParseError Prog
|
||||
parse = runParser parseProg ()
|
||||
|
||||
{- Translation -}
|
||||
data TraverserBounds = Range Py.PyExpr Py.PyExpr | Random
|
||||
|
||||
data TraverserData = TraverserData
|
||||
{ list :: Maybe String
|
||||
, bounds :: Maybe TraverserBounds
|
||||
, rev :: Bool
|
||||
}
|
||||
|
||||
data ValidTraverserData = ValidTraverserData
|
||||
{ validList :: String
|
||||
, validBounds :: TraverserBounds
|
||||
, validRev :: Bool
|
||||
}
|
||||
|
||||
type Translator = State (Map.Map String ValidTraverserData, [Py.PyStmt], Int)
|
||||
|
||||
getScoped :: Translator (Map.Map String ValidTraverserData)
|
||||
getScoped = gets (\(m, _, _) -> m)
|
||||
|
||||
setScoped :: Map.Map String ValidTraverserData -> Translator ()
|
||||
setScoped m = modify (\(_, ss, i) -> (m, ss, i))
|
||||
|
||||
scope :: Translator a -> Translator a
|
||||
scope m = do
|
||||
s <- getScoped
|
||||
a <- m
|
||||
setScoped s
|
||||
return a
|
||||
|
||||
clearTraverser :: String -> Translator ()
|
||||
clearTraverser s = modify (\(m, ss, i) -> (Map.delete s m, ss, i))
|
||||
|
||||
putTraverser :: String -> ValidTraverserData -> Translator ()
|
||||
putTraverser s vtd = modify (\(m, ss, i) -> (Map.insert s vtd m, ss, i))
|
||||
|
||||
getTemp :: Translator String
|
||||
getTemp = gets $ \(_, _, i) -> "temp" ++ show i
|
||||
|
||||
freshTemp :: Translator String
|
||||
freshTemp = modify (second (+1)) >> getTemp
|
||||
|
||||
emitStatement :: Py.PyStmt -> Translator ()
|
||||
emitStatement = modify . first . (:)
|
||||
|
||||
collectStatements :: Translator a -> Translator ([Py.PyStmt], a)
|
||||
collectStatements t = do
|
||||
modify (first $ const [])
|
||||
a <- t
|
||||
ss <- gets $ \(_, ss, _) -> ss
|
||||
modify (first $ const [])
|
||||
return (ss, a)
|
||||
|
||||
withdrawStatements :: Translator (Py.PyStmt) -> Translator [Py.PyStmt]
|
||||
withdrawStatements ts =
|
||||
(\(ss, s) -> ss ++ [s]) <$> (collectStatements ts)
|
||||
|
||||
requireTraverser :: String -> Translator ValidTraverserData
|
||||
requireTraverser s = gets (\(m, _, _) -> Map.lookup s m) >>= handleMaybe
|
||||
where
|
||||
handleMaybe Nothing = fail "Invalid traverser"
|
||||
handleMaybe (Just vtd) = return vtd
|
||||
|
||||
traverserIncrement :: Bool -> Py.PyExpr -> Py.PyExpr -> Py.PyExpr
|
||||
traverserIncrement rev by e =
|
||||
Py.BinOp op e (Py.BinOp Py.Multiply by (Py.IntLiteral 1))
|
||||
where op = if rev then Py.Subtract else Py.Add
|
||||
|
||||
traverserValid :: Py.PyExpr -> ValidTraverserData -> Py.PyExpr
|
||||
traverserValid e vtd =
|
||||
case validBounds vtd of
|
||||
Range f t ->
|
||||
if validRev vtd
|
||||
then Py.BinOp Py.GreaterThanEq e f
|
||||
else Py.BinOp Py.LessThan e t
|
||||
Random -> Py.BoolLiteral True
|
||||
|
||||
traverserStep :: String -> ValidTraverserData -> Py.PyStmt
|
||||
traverserStep s vtd =
|
||||
case validBounds vtd of
|
||||
Range _ _ -> Py.Assign (Py.VarPat s) $ Py.BinOp op (Py.Var s) (Py.IntLiteral 1)
|
||||
where op = if validRev vtd then Py.Subtract else Py.Add
|
||||
Random -> traverserRandom s $ validList vtd
|
||||
|
||||
traverserRandom :: String -> String -> Py.PyStmt
|
||||
traverserRandom s l =
|
||||
Py.Assign (Py.VarPat s) $ Py.FunctionCall (Py.Var "random.randrange")
|
||||
[Py.FunctionCall (Py.Var "len") [Py.Var l]]
|
||||
|
||||
hasVar :: String -> Py.PyPat -> Bool
|
||||
hasVar s (Py.VarPat s') = s == s'
|
||||
hasVar s (Py.TuplePat ps) = any (hasVar s) ps
|
||||
hasVar s _ = False
|
||||
|
||||
substituteVariable :: String -> Py.PyExpr -> Py.PyExpr -> Py.PyExpr
|
||||
substituteVariable s e (Py.BinOp o l r) =
|
||||
Py.BinOp o (substituteVariable s e l) (substituteVariable s e r)
|
||||
substituteVariable s e (Py.ListLiteral es) =
|
||||
Py.ListLiteral $ map (substituteVariable s e) es
|
||||
substituteVariable s e (Py.DictLiteral es) =
|
||||
Py.DictLiteral $
|
||||
map (first (substituteVariable s e) . second (substituteVariable s e)) es
|
||||
substituteVariable s e (Py.Lambda ps e') =
|
||||
Py.Lambda ps $ if any (hasVar s) ps then substituteVariable s e e' else e'
|
||||
substituteVariable s e (Py.Var s')
|
||||
| s == s' = e
|
||||
| otherwise = Py.Var s'
|
||||
substituteVariable s e (Py.TupleLiteral es) =
|
||||
Py.TupleLiteral $ map (substituteVariable s e) es
|
||||
substituteVariable s e (Py.FunctionCall e' es) =
|
||||
Py.FunctionCall (substituteVariable s e e') $
|
||||
map (substituteVariable s e) es
|
||||
substituteVariable s e (Py.Access e' es) =
|
||||
Py.Access (substituteVariable s e e') $
|
||||
map (substituteVariable s e) es
|
||||
substituteVariable s e (Py.Ternary i t e') =
|
||||
Py.Ternary (substituteVariable s e i) (substituteVariable s e t)
|
||||
(substituteVariable s e e')
|
||||
substituteVariable s e (Py.Member e' m) =
|
||||
Py.Member (substituteVariable s e e') m
|
||||
substituteVariable s e (Py.In e1 e2) =
|
||||
Py.In (substituteVariable s e e1) (substituteVariable s e e2)
|
||||
substituteVariable s e (Py.NotIn e1 e2) =
|
||||
Py.NotIn (substituteVariable s e e1) (substituteVariable s e e2)
|
||||
substituteVariable s e (Py.Slice f t) =
|
||||
Py.Slice (substituteVariable s e <$> f) (substituteVariable s e <$> t)
|
||||
|
||||
translateExpr :: Expr -> Translator Py.PyExpr
|
||||
translateExpr (TraverserCall "pop" [Var s]) = do
|
||||
l <- validList <$> requireTraverser s
|
||||
return $ Py.FunctionCall (Py.Member (Py.Var l) "pop") [Py.Var s]
|
||||
translateExpr (TraverserCall "pos" [Var s]) = do
|
||||
requireTraverser s
|
||||
return $ Py.Var s
|
||||
translateExpr (TraverserCall "at" [Var s]) = do
|
||||
l <- validList <$> requireTraverser s
|
||||
return $ Py.Access (Py.Var l) [Py.Var s]
|
||||
translateExpr (TraverserCall "at" [Var s, IntLiteral i]) = do
|
||||
vtd <- requireTraverser s
|
||||
return $ Py.Access (Py.Var $ validList vtd)
|
||||
[traverserIncrement (validRev vtd) (Py.IntLiteral i) (Py.Var s)]
|
||||
translateExpr (TraverserCall "step" [Var s]) = do
|
||||
vtd <- requireTraverser s
|
||||
emitStatement $ traverserStep s vtd
|
||||
return $ Py.IntLiteral 0
|
||||
translateExpr (TraverserCall "canstep" [Var s]) = do
|
||||
vtd <- requireTraverser s
|
||||
return $
|
||||
traverserValid
|
||||
(traverserIncrement (validRev vtd) (Py.IntLiteral 1) (Py.Var s)) vtd
|
||||
translateExpr (TraverserCall "valid" [Var s]) = do
|
||||
vtd <- requireTraverser s
|
||||
return $ traverserValid (Py.Var s) vtd
|
||||
translateExpr (TraverserCall "subset" [Var s1, Var s2]) = do
|
||||
l1 <- validList <$> requireTraverser s1
|
||||
l2 <- validList <$> requireTraverser s2
|
||||
if l1 == l2
|
||||
then return $ Py.Access (Py.Var l1) [Py.Slice (Just $ Py.Var s1) (Just $ Py.Var s2)]
|
||||
else fail "Incompatible traversers!"
|
||||
translateExpr (TraverserCall "bisect" [Var s, Lambda [x] e]) = do
|
||||
vtd <- requireTraverser s
|
||||
newTemp <- freshTemp
|
||||
lambdaExpr <- translateExpr e
|
||||
let access = Py.Access (Py.Var $ validList vtd) [Py.Var s]
|
||||
let translated = substituteVariable x access lambdaExpr
|
||||
let append s = Py.FunctionCall (Py.Member (Py.Var s) "append") [ access ]
|
||||
let bisectStmt = Py.FunctionDef newTemp []
|
||||
[ Py.Nonlocal [s]
|
||||
, Py.Assign (Py.VarPat "l") (Py.ListLiteral [])
|
||||
, Py.Assign (Py.VarPat "r") (Py.ListLiteral [])
|
||||
, Py.While (traverserValid (Py.Var s) vtd)
|
||||
[ Py.IfElse translated
|
||||
[ Py.Standalone $ append "l" ]
|
||||
[]
|
||||
(Just [ Py.Standalone $ append "r" ])
|
||||
, traverserStep s vtd
|
||||
]
|
||||
, Py.Return $ Py.TupleLiteral [Py.Var "l", Py.Var "r"]
|
||||
]
|
||||
emitStatement bisectStmt
|
||||
return $ Py.FunctionCall (Py.Var newTemp) []
|
||||
translateExpr (TraverserCall _ _) = fail "Invalid traverser operation"
|
||||
translateExpr (FunctionCall f ps) = do
|
||||
pes <- mapM translateExpr ps
|
||||
return $ Py.FunctionCall (Py.Var f) pes
|
||||
translateExpr (BinOp o l r) =
|
||||
Py.BinOp (translateOp o) <$> translateExpr l <*> translateExpr r
|
||||
translateExpr (Lambda ps e) =
|
||||
Py.Lambda (map Py.VarPat ps) <$> translateExpr e
|
||||
translateExpr (Var s) = return $ Py.Var s
|
||||
translateExpr (IntLiteral i) = return $ Py.IntLiteral i
|
||||
translateExpr (BoolLiteral b) = return $ Py.BoolLiteral b
|
||||
translateExpr (ListLiteral es) = Py.ListLiteral <$> mapM translateExpr es
|
||||
translateExpr (TupleLiteral es) = Py.TupleLiteral <$> mapM translateExpr es
|
||||
|
||||
applyOption :: TraverserData -> (String, Py.PyExpr) -> Maybe TraverserData
|
||||
applyOption td ("list", Py.Var s) =
|
||||
return $ td { list = Just s }
|
||||
applyOption td ("span", Py.TupleLiteral [f, t]) =
|
||||
return $ td { bounds = Just $ Range f t }
|
||||
applyOption td ("random", Py.BoolLiteral True) =
|
||||
return $ td { bounds = Just Random }
|
||||
applyOption td ("reverse", Py.BoolLiteral b) =
|
||||
return $ td { rev = b }
|
||||
applyOption td _ = Nothing
|
||||
|
||||
translateOption :: (String, Expr) -> Translator (String, Py.PyExpr)
|
||||
translateOption (s, e) = (,) s <$> translateExpr e
|
||||
|
||||
defaultTraverser :: TraverserData
|
||||
defaultTraverser =
|
||||
TraverserData { list = Nothing, bounds = Nothing, rev = False }
|
||||
|
||||
translateBranch :: Branch -> Translator (Py.PyExpr, [Py.PyStmt])
|
||||
translateBranch (e, s) = (,) <$> translateExpr e <*>
|
||||
(concat <$> mapM (withdrawStatements . translateStmt) s)
|
||||
|
||||
translateStmt :: Stmt -> Translator Py.PyStmt
|
||||
translateStmt (IfElse i els e) = uncurry Py.IfElse
|
||||
<$> (translateBranch i) <*> (mapM translateBranch els) <*> convertElse e
|
||||
where
|
||||
convertElse [] = return Nothing
|
||||
convertElse es = Just . concat <$>
|
||||
mapM (withdrawStatements . translateStmt) es
|
||||
translateStmt (While b) = uncurry Py.While <$> translateBranch b
|
||||
translateStmt (Traverser s os) =
|
||||
foldlM applyOption defaultTraverser <$> mapM translateOption os >>= saveTraverser
|
||||
where
|
||||
saveTraverser :: Maybe TraverserData -> Translator Py.PyStmt
|
||||
saveTraverser (Just (td@TraverserData { list = Just l, bounds = Just bs})) =
|
||||
putTraverser s vtd $> translateInitialBounds s vtd
|
||||
where
|
||||
vtd = ValidTraverserData
|
||||
{ validList = l
|
||||
, validBounds = bs
|
||||
, validRev = rev td
|
||||
}
|
||||
saveTraverser Nothing = fail "Invalid traverser (!)"
|
||||
translateStmt (Let p e) = Py.Assign <$> translatePat p <*> translateExpr e
|
||||
translateStmt (Return e) = Py.Return <$> translateExpr e
|
||||
translateStmt (Standalone e) = Py.Standalone <$> translateExpr e
|
||||
|
||||
translateInitialBounds :: String -> ValidTraverserData -> Py.PyStmt
|
||||
translateInitialBounds s vtd =
|
||||
case (validBounds vtd, validRev vtd) of
|
||||
(Random, _) -> traverserRandom s $ validList vtd
|
||||
(Range l _, False) -> Py.Assign (Py.VarPat s) l
|
||||
(Range _ r, True) -> Py.Assign (Py.VarPat s) r
|
||||
|
||||
translatePat :: Pat -> Translator Py.PyPat
|
||||
translatePat (VarPat s) = clearTraverser s $> Py.VarPat s
|
||||
translatePat (TuplePat ts) = Py.TuplePat <$> mapM translatePat ts
|
||||
|
||||
translateOp :: Op -> Py.PyBinOp
|
||||
translateOp Add = Py.Add
|
||||
translateOp Subtract = Py.Subtract
|
||||
translateOp Multiply = Py.Multiply
|
||||
translateOp Divide = Py.Divide
|
||||
translateOp LessThan = Py.LessThan
|
||||
translateOp LessThanEqual = Py.LessThanEq
|
||||
translateOp GreaterThan = Py.GreaterThan
|
||||
translateOp GreaterThanEqual = Py.GreaterThanEq
|
||||
translateOp Equal = Py.Equal
|
||||
translateOp NotEqual = Py.NotEqual
|
||||
translateOp And = Py.And
|
||||
translateOp Or = Py.Or
|
||||
|
||||
translateFunction :: Function -> [Py.PyStmt]
|
||||
translateFunction (Function m s ps ss) = return $ Py.FunctionDef s ps $
|
||||
[ Py.Standalone $ Py.FunctionCall (Py.Member (Py.Var p) "sort") []
|
||||
| p <- take 1 ps, m == Sorted ] ++ stmts
|
||||
where
|
||||
stmts = concat $ evalState
|
||||
(mapM (withdrawStatements . translateStmt) ss) (Map.empty, [], 0)
|
||||
|
||||
translate :: Prog -> [Py.PyStmt]
|
||||
translate (Prog fs) =
|
||||
(Py.FromImport "bisect" ["bisect"]) :
|
||||
(Py.Import "random") : concatMap translateFunction fs
|
||||
198
code/cs325-langs/src/LanguageTwo.hs
Normal file
198
code/cs325-langs/src/LanguageTwo.hs
Normal file
@@ -0,0 +1,198 @@
|
||||
module LanguageTwo where
|
||||
import qualified PythonAst as Py
|
||||
import qualified CommonParsing as P
|
||||
import Data.Char
|
||||
import Data.Functor
|
||||
import Text.Parsec
|
||||
import Text.Parsec.Char
|
||||
import Text.Parsec.Combinator
|
||||
|
||||
{- Data Types -}
|
||||
data Op
|
||||
= Add
|
||||
| Subtract
|
||||
| Multiply
|
||||
| Divide
|
||||
| Equal
|
||||
| NotEqual
|
||||
| And
|
||||
| Or
|
||||
|
||||
data Expr
|
||||
= IntLiteral Int
|
||||
| BinOp Op Expr Expr
|
||||
| Var String
|
||||
| Length Expr
|
||||
|
||||
data Stmt
|
||||
= IfElse Expr Stmt (Maybe Stmt)
|
||||
| Assign String Expr
|
||||
| Block [Stmt]
|
||||
|
||||
data Prog = Prog Expr [Stmt] [Stmt]
|
||||
|
||||
{- Parser -}
|
||||
type Parser = Parsec String ()
|
||||
|
||||
parseVar :: Parser String
|
||||
parseVar = P.var [ "if", "else", "state", "effect", "combine" ]
|
||||
|
||||
parseLength :: Parser Expr
|
||||
parseLength = Length <$> P.surround '|' '|' parseExpr
|
||||
|
||||
parseParenthesized :: Parser Expr
|
||||
parseParenthesized = P.surround '(' ')' parseExpr
|
||||
|
||||
parseBasic :: Parser Expr
|
||||
parseBasic = choice
|
||||
[ IntLiteral <$> P.int
|
||||
, Var <$> parseVar
|
||||
, parseLength
|
||||
, parseParenthesized
|
||||
]
|
||||
|
||||
|
||||
parseExpr :: Parser Expr
|
||||
parseExpr = P.precedence BinOp parseBasic
|
||||
[ P.op "*" Multiply <|> P.op "/" Divide
|
||||
, P.op "+" Add <|> P.op "-" Subtract
|
||||
, P.op "==" Equal <|> P.op "!=" NotEqual
|
||||
, P.op "&&" And
|
||||
, try $ P.op "||" Or
|
||||
]
|
||||
|
||||
parseIf :: Parser Stmt
|
||||
parseIf = do
|
||||
P.kwIf >> spaces
|
||||
c <- parseParenthesized
|
||||
t <- parseStmt <* spaces
|
||||
e <- (Just <$> (P.kwElse >> spaces *> parseStmt)) <|> return Nothing
|
||||
return $ IfElse c t e
|
||||
|
||||
parseBlockStmts :: Parser [Stmt]
|
||||
parseBlockStmts = P.surround '{' '}' (many parseStmt)
|
||||
|
||||
parseBlock :: Parser Stmt
|
||||
parseBlock = Block <$> parseBlockStmts
|
||||
|
||||
parseAssign :: Parser Stmt
|
||||
parseAssign = Assign <$>
|
||||
(parseVar <* char '=' <* spaces) <*>
|
||||
parseExpr <* (char ';' >> spaces)
|
||||
|
||||
parseStmt :: Parser Stmt
|
||||
parseStmt = choice
|
||||
[ parseIf
|
||||
, parseAssign
|
||||
, parseBlock
|
||||
]
|
||||
|
||||
parseProgram :: Parser Prog
|
||||
parseProgram = do
|
||||
state <- P.kwState >> spaces *> parseExpr <* char ';' <* spaces
|
||||
effect <- P.kwEffect >> spaces *> parseBlockStmts <* spaces
|
||||
combined <- P.kwCombine >> spaces *> parseBlockStmts <* spaces
|
||||
return $ Prog state effect combined
|
||||
|
||||
parse :: String -> String -> Either ParseError Prog
|
||||
parse = runParser parseProgram ()
|
||||
|
||||
{- Translation -}
|
||||
baseFunction :: Py.PyExpr -> [Py.PyStmt] -> [Py.PyStmt] -> Py.PyStmt
|
||||
baseFunction s e c = Py.FunctionDef "prog" ["xs"] $
|
||||
[Py.IfElse
|
||||
(Py.BinOp Py.LessThan
|
||||
(Py.FunctionCall (Py.Var "len") [Py.Var "xs"])
|
||||
(Py.IntLiteral 2))
|
||||
[Py.Return $ Py.Tuple [s, Py.Var "xs"]]
|
||||
[]
|
||||
Nothing
|
||||
, Py.Assign (Py.VarPat "leng")
|
||||
(Py.BinOp Py.FloorDiv
|
||||
(Py.FunctionCall (Py.Var "len") [Py.Var "xs"])
|
||||
(Py.IntLiteral 2))
|
||||
, Py.Assign (Py.VarPat "left")
|
||||
(Py.Access
|
||||
(Py.Var "xs")
|
||||
[Py.Slice Nothing $ Just (Py.Var "leng")])
|
||||
, Py.Assign (Py.VarPat "right")
|
||||
(Py.Access
|
||||
(Py.Var "xs")
|
||||
[Py.Slice (Just (Py.Var "leng")) Nothing])
|
||||
, Py.Assign (Py.TuplePat [Py.VarPat "ls", Py.VarPat "left"])
|
||||
(Py.FunctionCall (Py.Var "prog") [Py.Var "left"])
|
||||
, Py.Assign (Py.TuplePat [Py.VarPat "rs", Py.VarPat "right"])
|
||||
(Py.FunctionCall (Py.Var "prog") [Py.Var "right"])
|
||||
, Py.Standalone $
|
||||
Py.FunctionCall (Py.Member (Py.Var "left") "reverse") []
|
||||
, Py.Standalone $
|
||||
Py.FunctionCall (Py.Member (Py.Var "right") "reverse") []
|
||||
, Py.Assign (Py.VarPat "state") s
|
||||
, Py.Assign (Py.VarPat "source") (Py.IntLiteral 0)
|
||||
, Py.Assign (Py.VarPat "total") (Py.ListLiteral [])
|
||||
, Py.While
|
||||
(Py.BinOp Py.And
|
||||
(Py.BinOp Py.NotEqual (Py.Var "left") (Py.ListLiteral []))
|
||||
(Py.BinOp Py.NotEqual (Py.Var "right") (Py.ListLiteral []))) $
|
||||
[ Py.IfElse
|
||||
(Py.BinOp Py.LessThanEq
|
||||
(Py.Access (Py.Var "left") [Py.IntLiteral $ -1])
|
||||
(Py.Access (Py.Var "right") [Py.IntLiteral $ -1]))
|
||||
[ Py.Standalone $
|
||||
Py.FunctionCall (Py.Member (Py.Var "total") "append")
|
||||
[Py.FunctionCall (Py.Member (Py.Var "left") "pop") []]
|
||||
, Py.Assign (Py.VarPat "source") (Py.IntLiteral 1)
|
||||
]
|
||||
[] $
|
||||
Just
|
||||
[ Py.Standalone $
|
||||
Py.FunctionCall (Py.Member (Py.Var "total") "append")
|
||||
[Py.FunctionCall (Py.Member (Py.Var "right") "pop") []]
|
||||
, Py.Assign (Py.VarPat "source") (Py.IntLiteral 2)
|
||||
]
|
||||
] ++ e
|
||||
] ++ c ++
|
||||
[ Py.Standalone $ Py.FunctionCall (Py.Member (Py.Var "left") "reverse") []
|
||||
, Py.Standalone $ Py.FunctionCall (Py.Member (Py.Var "right") "reverse") []
|
||||
, Py.Return $ Py.Tuple
|
||||
[ Py.Var "state"
|
||||
, foldl (Py.BinOp Py.Add) (Py.Var "total") [Py.Var "left", Py.Var "right"]
|
||||
]
|
||||
]
|
||||
|
||||
translateExpr :: Expr -> Py.PyExpr
|
||||
translateExpr (IntLiteral i) = Py.IntLiteral i
|
||||
translateExpr (BinOp op l r) =
|
||||
Py.BinOp (translateOp op) (translateExpr l) (translateExpr r)
|
||||
translateExpr (Var s)
|
||||
| s == "SOURCE" = Py.Var "source"
|
||||
| s == "LEFT" = Py.Var "left"
|
||||
| s == "RIGHT" = Py.Var "right"
|
||||
| s == "STATE" = Py.Var "state"
|
||||
| s == "LSTATE" = Py.Var "ls"
|
||||
| s == "RSTATE" = Py.Var "rs"
|
||||
| s == "L" = Py.IntLiteral 1
|
||||
| s == "R" = Py.IntLiteral 2
|
||||
| otherwise = Py.Var s
|
||||
translateExpr (Length e) = Py.FunctionCall (Py.Var "len") [translateExpr e]
|
||||
|
||||
translateOp :: Op -> Py.PyBinOp
|
||||
translateOp Add = Py.Add
|
||||
translateOp Subtract = Py.Subtract
|
||||
translateOp Multiply = Py.Multiply
|
||||
translateOp Divide = Py.Divide
|
||||
translateOp Equal = Py.Equal
|
||||
translateOp NotEqual = Py.NotEqual
|
||||
translateOp And = Py.And
|
||||
translateOp Or = Py.Or
|
||||
|
||||
translateStmt :: Stmt -> [Py.PyStmt]
|
||||
translateStmt (IfElse c t e) =
|
||||
[Py.IfElse (translateExpr c) (translateStmt t) [] (translateStmt <$> e)]
|
||||
translateStmt (Assign "STATE" e) = [Py.Assign (Py.VarPat "state") (translateExpr e)]
|
||||
translateStmt (Assign v e) = [Py.Assign (Py.VarPat v) (translateExpr e)]
|
||||
translateStmt (Block s) = concatMap translateStmt s
|
||||
|
||||
translate :: Prog -> [Py.PyStmt]
|
||||
translate (Prog s e c) =
|
||||
[baseFunction (translateExpr s) (concatMap translateStmt e) (concatMap translateStmt c)]
|
||||
52
code/cs325-langs/src/PythonAst.hs
Normal file
52
code/cs325-langs/src/PythonAst.hs
Normal file
@@ -0,0 +1,52 @@
|
||||
module PythonAst where
|
||||
|
||||
data PyBinOp
|
||||
= Add
|
||||
| Subtract
|
||||
| Multiply
|
||||
| Divide
|
||||
| FloorDiv
|
||||
| LessThan
|
||||
| LessThanEq
|
||||
| GreaterThan
|
||||
| GreaterThanEq
|
||||
| Equal
|
||||
| NotEqual
|
||||
| And
|
||||
| Or
|
||||
|
||||
data PyExpr
|
||||
= BinOp PyBinOp PyExpr PyExpr
|
||||
| IntLiteral Int
|
||||
| StrLiteral String
|
||||
| BoolLiteral Bool
|
||||
| ListLiteral [PyExpr]
|
||||
| DictLiteral [(PyExpr, PyExpr)]
|
||||
| Lambda [PyPat] PyExpr
|
||||
| Var String
|
||||
| TupleLiteral [PyExpr]
|
||||
| FunctionCall PyExpr [PyExpr]
|
||||
| Access PyExpr [PyExpr]
|
||||
| Ternary PyExpr PyExpr PyExpr
|
||||
| Member PyExpr String
|
||||
| In PyExpr PyExpr
|
||||
| NotIn PyExpr PyExpr
|
||||
| Slice (Maybe PyExpr) (Maybe PyExpr)
|
||||
|
||||
data PyPat
|
||||
= VarPat String
|
||||
| IgnorePat
|
||||
| TuplePat [PyPat]
|
||||
| AccessPat PyExpr [PyExpr]
|
||||
|
||||
data PyStmt
|
||||
= Assign PyPat PyExpr
|
||||
| IfElse PyExpr [PyStmt] [(PyExpr, [PyStmt])] (Maybe [PyStmt])
|
||||
| While PyExpr [PyStmt]
|
||||
| For PyPat PyExpr [PyStmt]
|
||||
| FunctionDef String [String] [PyStmt]
|
||||
| Return PyExpr
|
||||
| Standalone PyExpr
|
||||
| Import String
|
||||
| FromImport String [String]
|
||||
| Nonlocal [String]
|
||||
142
code/cs325-langs/src/PythonGen.hs
Normal file
142
code/cs325-langs/src/PythonGen.hs
Normal file
@@ -0,0 +1,142 @@
|
||||
module PythonGen where
|
||||
import PythonAst
|
||||
import Data.List
|
||||
import Data.Bifunctor
|
||||
import Data.Maybe
|
||||
|
||||
indent :: String -> String
|
||||
indent = (" " ++)
|
||||
|
||||
stmtBlock :: [PyStmt] -> [String]
|
||||
stmtBlock = concatMap translateStmt
|
||||
|
||||
block :: String -> [String] -> [String]
|
||||
block s ss = (s ++ ":") : map indent ss
|
||||
|
||||
prefix :: String -> PyExpr -> [PyStmt] -> [String]
|
||||
prefix s e sts = block (s ++ " " ++ translateExpr e) $ stmtBlock sts
|
||||
|
||||
if_ :: PyExpr -> [PyStmt] -> [String]
|
||||
if_ = prefix "if"
|
||||
|
||||
elif :: PyExpr -> [PyStmt] -> [String]
|
||||
elif = prefix "elif"
|
||||
|
||||
else_ :: [PyStmt] -> [String]
|
||||
else_ = block "else" . stmtBlock
|
||||
|
||||
while :: PyExpr -> [PyStmt] -> [String]
|
||||
while = prefix "while"
|
||||
|
||||
parenth :: String -> String
|
||||
parenth s = "(" ++ s ++ ")"
|
||||
|
||||
translateStmt :: PyStmt -> [String]
|
||||
translateStmt (Assign p e) = [translatePat p ++ " = " ++ translateExpr e]
|
||||
translateStmt (IfElse i t es e) =
|
||||
if_ i t ++ concatMap (uncurry elif) es ++ maybe [] else_ e
|
||||
translateStmt (While c t) = while c t
|
||||
translateStmt (For x in_ b) = block head body
|
||||
where
|
||||
head = "for " ++ translatePat x ++ " in " ++ translateExpr in_
|
||||
body = stmtBlock b
|
||||
translateStmt (FunctionDef s ps b) = block head body
|
||||
where
|
||||
head = "def " ++ s ++ "(" ++ intercalate "," ps ++ ")"
|
||||
body = stmtBlock b
|
||||
translateStmt (Return e) = ["return " ++ translateExpr e]
|
||||
translateStmt (Standalone e) = [translateExpr e]
|
||||
translateStmt (Import s) = ["import " ++ s]
|
||||
translateStmt (FromImport s ss) =
|
||||
["from " ++ s ++ " import " ++ intercalate "," ss]
|
||||
translateStmt (Nonlocal vs) =
|
||||
["nonlocal " ++ intercalate "," vs]
|
||||
|
||||
precedence :: PyBinOp -> Int
|
||||
precedence Add = 3
|
||||
precedence Subtract = 3
|
||||
precedence Multiply = 4
|
||||
precedence Divide = 4
|
||||
precedence FloorDiv = 4
|
||||
precedence LessThan = 2
|
||||
precedence LessThanEq = 2
|
||||
precedence GreaterThan = 2
|
||||
precedence GreaterThanEq = 2
|
||||
precedence Equal = 2
|
||||
precedence NotEqual = 2
|
||||
precedence And = 1
|
||||
precedence Or = 0
|
||||
|
||||
opString :: PyBinOp -> String
|
||||
opString Add = "+"
|
||||
opString Subtract = "-"
|
||||
opString Multiply = "*"
|
||||
opString Divide = "/"
|
||||
opString FloorDiv = "//"
|
||||
opString LessThan = "<"
|
||||
opString LessThanEq = "<="
|
||||
opString GreaterThan = ">"
|
||||
opString GreaterThanEq = ">="
|
||||
opString Equal = "=="
|
||||
opString NotEqual = "!="
|
||||
opString And = " and "
|
||||
opString Or = " or "
|
||||
|
||||
translateOp :: PyBinOp -> PyBinOp -> PyExpr -> String
|
||||
translateOp o o' =
|
||||
if precedence o > precedence o'
|
||||
then parenth . translateExpr
|
||||
else translateExpr
|
||||
|
||||
dictMapping :: PyExpr -> PyExpr -> String
|
||||
dictMapping f t = translateExpr f ++ ": " ++ translateExpr t
|
||||
|
||||
list :: String -> String -> [PyExpr] -> String
|
||||
list o c es = o ++ intercalate ", " (map translateExpr es) ++ c
|
||||
|
||||
translateExpr :: PyExpr -> String
|
||||
translateExpr (BinOp o l@(BinOp o1 _ _) r@(BinOp o2 _ _)) =
|
||||
translateOp o o1 l ++ opString o ++ translateOp o o2 r
|
||||
translateExpr (BinOp o l@(BinOp o1 _ _) r) =
|
||||
translateOp o o1 l ++ opString o ++ translateExpr r
|
||||
translateExpr (BinOp o l r@(BinOp o2 _ _)) =
|
||||
translateExpr l ++ opString o ++ translateOp o o2 r
|
||||
translateExpr (BinOp o l r) =
|
||||
translateExpr l ++ opString o ++ translateExpr r
|
||||
translateExpr (IntLiteral i) = show i
|
||||
translateExpr (StrLiteral s) = "\"" ++ s ++ "\""
|
||||
translateExpr (BoolLiteral b) = if b then "true" else "false"
|
||||
translateExpr (ListLiteral l) = list "[" "]" l
|
||||
translateExpr (DictLiteral l) =
|
||||
"{" ++ intercalate ", " (map (uncurry dictMapping) l) ++ "}"
|
||||
translateExpr (Lambda ps e) = parenth (head ++ ": " ++ body)
|
||||
where
|
||||
head = "lambda " ++ intercalate ", " (map translatePat ps)
|
||||
body = translateExpr e
|
||||
translateExpr (Var s) = s
|
||||
translateExpr (TupleLiteral es) = list "(" ")" es
|
||||
translateExpr (FunctionCall f ps) = translateExpr f ++ list "(" ")" ps
|
||||
translateExpr (Access (Var s) e) = s ++ list "[" "]" e
|
||||
translateExpr (Access e@Access{} i) = translateExpr e ++ list "[" "]" i
|
||||
translateExpr (Access e i) = "(" ++ translateExpr e ++ ")" ++ list "[" "]" i
|
||||
translateExpr (Ternary c t e) =
|
||||
translateExpr t ++ " if " ++ translateExpr c ++ " else " ++ translateExpr e
|
||||
translateExpr (Member (Var s) m) = s ++ "." ++ m
|
||||
translateExpr (Member e@Member{} m) = translateExpr e ++ "." ++ m
|
||||
translateExpr (Member e m) = "(" ++ translateExpr e ++ ")." ++ m
|
||||
translateExpr (In m c) =
|
||||
"(" ++ translateExpr m ++ ") in (" ++ translateExpr c ++ ")"
|
||||
translateExpr (NotIn m c) =
|
||||
"(" ++ translateExpr m ++ ") not in (" ++ translateExpr c ++ ")"
|
||||
translateExpr (Slice l r) =
|
||||
maybe [] (parenth . translateExpr) l ++ ":" ++ maybe [] (parenth . translateExpr) r
|
||||
|
||||
translatePat :: PyPat -> String
|
||||
translatePat (VarPat s) = s
|
||||
translatePat IgnorePat = "_"
|
||||
translatePat (TuplePat ps) =
|
||||
"(" ++ intercalate "," (map translatePat ps) ++ ")"
|
||||
translatePat (AccessPat e es) = translateExpr (Access e es)
|
||||
|
||||
translate :: [PyStmt] -> String
|
||||
translate = intercalate "\n" . concatMap translateStmt
|
||||
@@ -2,5 +2,5 @@
|
||||
title: Daniel's Blog
|
||||
---
|
||||
## Hello!
|
||||
Welcome to my blog. Here, I write abour various subjects, including (but not limited to)
|
||||
Welcome to my blog. Here, I write about various subjects, including (but not limited to)
|
||||
functional programming, compiler development, programming language theory, and occasionally video games. I hope you find something useful here!
|
||||
|
||||
511
content/blog/00_cs325_languages_hw1.md
Normal file
511
content/blog/00_cs325_languages_hw1.md
Normal file
@@ -0,0 +1,511 @@
|
||||
---
|
||||
title: A Language for an Assignment - Homework 1
|
||||
date: 2019-12-27T23:27:09-08:00
|
||||
tags: ["Haskell", "Python", "Algorithms"]
|
||||
---
|
||||
|
||||
On a rainy Oregon day, I was walking between classes with a group of friends.
|
||||
We were discussing the various ways to obfuscate solutions to the weekly
|
||||
homework assignments in our Algorithms course: replace every `if` with
|
||||
a ternary expression, use single variable names, put everything on one line.
|
||||
I said:
|
||||
|
||||
> The
|
||||
{{< sidenote "right" "chad-note" "chad" >}}
|
||||
This is in reference to a meme, <a href="https://knowyourmeme.com/memes/virgin-vs-chad">Virgin vs Chad</a>.
|
||||
A "chad" characteristic is masculine or "alpha" to the point of absurdity.
|
||||
{{< /sidenote >}} move would be to make your own, different language for every homework assignment.
|
||||
|
||||
It was required of us to use
|
||||
{{< sidenote "left" "python-note" "Python" >}}
|
||||
A friend suggested making a Haskell program
|
||||
that generates Python-based interpreters for languages. While that would be truly
|
||||
absurd, I'll leave <em>this</em> challenge for another day.
|
||||
{{< /sidenote >}} for our solutions, so that was the first limitation on this challenge.
|
||||
Someone suggested to write the languages in Haskell, since that's what we used
|
||||
in our Programming Languages class. So the final goal ended up:
|
||||
|
||||
* For each of the 10 homework assignments in CS325 - Analysis of Algorithms,
|
||||
* Create a Haskell program that translates a language into,
|
||||
* A valid Python program that works (nearly) out of the box and passes all the test cases.
|
||||
|
||||
It may not be worth it to create a whole
|
||||
{{< sidenote "right" "general-purpose-note" "general-purpose" >}}
|
||||
A general purpose language is one that's designed to be used in various
|
||||
domains. For instance, C++ is a general-purpose language because it can
|
||||
be used for embedded systems, GUI programs, and pretty much anything else.
|
||||
This is in contrast to a domain-specific language, such as Game Maker Language,
|
||||
which is aimed at a much narrower set of uses.
|
||||
{{< /sidenote >}} language for each problem,
|
||||
but nowhere in the challenge did we say that it had to be general-purpose. In
|
||||
fact, some interesting design thinking can go into designing a domain-specific
|
||||
language for a particular assignment. So let's jump right into it, and make
|
||||
a language for the first homework assignment.
|
||||
|
||||
### Homework 1
|
||||
There are two problems in Homework 1. Here they are, verbatim:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw1.txt" 32 38 >}}
|
||||
|
||||
And the second:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw1.txt" 47 68 >}}
|
||||
|
||||
We want to make a language __specifically__ for these two tasks (one of which
|
||||
is split into many tasks). What common things can we isolate? I see two:
|
||||
|
||||
First, __all the problems deal with lists__. This may seem like a trivial observation,
|
||||
but these two problems are the __only__ thing we use our language for. We have
|
||||
list access,
|
||||
{{< sidenote "right" "filterting-note" "list filtering" >}}
|
||||
Quickselect is a variation on quicksort, which itself
|
||||
finds all the "lesser" and "greater" elements in the input array.
|
||||
{{< /sidenote >}} and list creation. That should serve as a good base!
|
||||
|
||||
If you squint a little bit, __all the problems are recursive with the same base case__.
|
||||
Consider the first few lines of `search`, implemented naively:
|
||||
|
||||
```Python
|
||||
def search(xs, k):
|
||||
if xs == []:
|
||||
return false
|
||||
```
|
||||
|
||||
How about `sorted`? Take a look:
|
||||
|
||||
```Python
|
||||
def sorted(xs):
|
||||
if xs == []:
|
||||
return []
|
||||
```
|
||||
|
||||
I'm sure you see the picture. But it will take some real mental gymnastics to twist the
|
||||
rest of the problems into this shape. What about `qselect`, for instance? There's two
|
||||
cases for what it may return:
|
||||
|
||||
* `None` or equivalent if the index is out of bounds (we give it `4` an a list `[1, 2]`).
|
||||
* A number if `qselect` worked.
|
||||
|
||||
The test cases never provide a concrete example of what should be returned from
|
||||
`qselect` in the first case, so we'll interpret it like
|
||||
{{< sidenote "right" "undefined-note" "undefined behavior" >}}
|
||||
For a quick sidenote about undefined behavior, check out how
|
||||
C++ optimizes the <a href="https://godbolt.org/z/3skK9j">Collatz Conjecture function</a>.
|
||||
Clang doesn't know whether or not the function will terminate (whether the Collatz Conjecture
|
||||
function terminates is an <a href="https://en.wikipedia.org/wiki/Collatz_conjecture">unsolved problem</a>),
|
||||
but functions that don't terminate are undefined behavior. There's only one other way the function
|
||||
returns, and that's with "1". Thus, clang optimizes the entire function to a single "return 1" call.
|
||||
{{< /sidenote >}} in C++:
|
||||
we can do whatever we want. So, let's allow it to return `[]` in the `None` case.
|
||||
This makes this base case valid:
|
||||
|
||||
```Python
|
||||
def qselect(xs, k):
|
||||
if xs == []:
|
||||
return []
|
||||
```
|
||||
|
||||
"Oh yeah, now it's all coming together." With one more observation (which will come
|
||||
from a piece I haven't yet shown you!), we'll be able to generalize this base case.
|
||||
|
||||
The observation is this section in the assignment:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw1.txt" 83 98 >}}
|
||||
|
||||
The real key is the part about "returning the `[]` where x should be inserted". It so
|
||||
happens that when the list given to the function is empty, the number should be inserted
|
||||
precisely into that list. Thus:
|
||||
|
||||
```Python
|
||||
def _search(xs, k):
|
||||
if xs == []:
|
||||
return xs
|
||||
```
|
||||
|
||||
The same works for `qselect`:
|
||||
|
||||
```Python
|
||||
def qselect(xs, k):
|
||||
if xs == []:
|
||||
return xs
|
||||
```
|
||||
|
||||
And for sorted, too:
|
||||
|
||||
```Python
|
||||
def sorted(xs):
|
||||
if xs == []:
|
||||
return xs
|
||||
```
|
||||
|
||||
There are some functions that are exceptions, though:
|
||||
|
||||
```Python
|
||||
def insert(xs, k):
|
||||
# We can't return early here!
|
||||
# If we do, we'll never insert anything.
|
||||
```
|
||||
|
||||
Also:
|
||||
|
||||
```Python
|
||||
def search(xs, k):
|
||||
# We have to return true or false, never
|
||||
# an empty list.
|
||||
```
|
||||
|
||||
So, whenever we __don't__ return a list, we don't want to add a special case.
|
||||
We arrive at the following common base case: __whenever a function returns a list, if its first argument
|
||||
is the empty list, the first argument is immediately returned__.
|
||||
|
||||
We've largely exhasuted the conclusiosn we can draw from these problems. Let's get to designing a language.
|
||||
|
||||
### A Silly Language
|
||||
Let's start by visualizing our goals. Without base cases, the solution to `_search`
|
||||
would be something like this:
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw1.lang" 11 14 >}}
|
||||
|
||||
Here we have an __`if`-expression__. It has to have an `else`, and evaluates to the value
|
||||
of the chosen branch. That is, `if true then 0 else 1` evaluates to `0`, while
|
||||
`if false then 0 else 1` evaluates to `1`. Otherwise, we follow the binary tree search
|
||||
algorithm faithfully.
|
||||
|
||||
Using this definition of `_search`, we can define `search` pretty easily:
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw1.lang" 17 17 >}}
|
||||
|
||||
Let's use Haskell's `(++)` operator for concatentation. This will help us understand
|
||||
when the user is operating on lists, and when they're not. With this, `sorted` becomes:
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw1.lang" 16 16 >}}
|
||||
|
||||
Let's go for `qselect` now. We'll introduce a very silly language feature for this
|
||||
problem:
|
||||
{{< sidenote "right" "selector-note" "list selectors" >}}
|
||||
You've probably never heard of list selectors, and for a good reason:
|
||||
this is a <em>terrible</em> language feature. I'll go in more detail
|
||||
later, but I wanted to make this clear right away.
|
||||
{{< /sidenote >}}. We observe that `qselect` aims to partition the list into
|
||||
other lists. We thus add the following pieces of syntax:
|
||||
|
||||
```
|
||||
~xs -> {
|
||||
pivot <- xs[rand]!
|
||||
left <- xs[#0 <= pivot]
|
||||
...
|
||||
} -> ...
|
||||
```
|
||||
|
||||
There are three new things here.
|
||||
|
||||
1. The actual "list selector": `~xs -> { .. } -> ...`. Between the curly braces
|
||||
are branches which select parts of the list and assign them to new variables.
|
||||
Thus, `pivot <- xs[rand]!` assigns the element at a random index to the variable `pivot`.
|
||||
the `!` at the end means "after taking this out of `xs`, delete it from `xs`". The
|
||||
syntax {{< sidenote "right" "curly-note" "starts with \"~\"" >}}
|
||||
An observant reader will note that there's no need for the "xs" after the "~".
|
||||
The idea was to add a special case syntax to reference the "selected list", but
|
||||
I ended up not bothering. So in fact, this part of the syntax is useless.
|
||||
{{< /sidenote >}} to make it easier to parse.
|
||||
2. The `rand` list access syntax. `xs[rand]` is a special case that picks a random
|
||||
element from `xs`.
|
||||
3. The `xs[#0 <= pivot]` syntax. This is another special case that selects all elements
|
||||
from `xs` that match the given predicate (where `#0` is replaced with each element in `xs`).
|
||||
|
||||
The big part of qselect is to not evaluate `right` unless you have to. So, we shouldn't
|
||||
eagerly evaluate the list selector. We also don't want something like `right[|right|-1]` to evaluate
|
||||
`right` twice. So we settle on
|
||||
{{< sidenote "right" "lazy-note" "lazy evaluation" >}}
|
||||
Lazy evaluation means only evaluating an expression when we need to. Thus,
|
||||
although we might encounter the expression for <code>right</code>, we
|
||||
only evaluate it when the time comes. Lazy evaluation, at least
|
||||
the way that Haskell has it, is more specific: an expression is evaluated only
|
||||
once, or not at all.
|
||||
{{</ sidenote >}}.
|
||||
Ah, but the `!` marker introduces
|
||||
{{< sidenote "left" "side-effect-note" "side effects" >}}
|
||||
A side effect is a term frequently used when talking about functional programming.
|
||||
Evaluating the expression <code>xs[rand]!</code> doesn't just get a random element,
|
||||
it also changes <em>something else</em>. In this case, that something else is
|
||||
the <code>xs</code> list.
|
||||
{{< /sidenote >}}. So we can't just evaluate these things all willy-nilly.
|
||||
So, let's make it so that each expression in the selector list requires the ones above it. Thus,
|
||||
`left` will require `pivot`, and `right` will require `left` and `pivot`. So,
|
||||
lazily evaluated, ordered expressions. The whole `qselect` becomes:
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw1.lang" 1 9 >}}
|
||||
|
||||
We've now figured out all the language constructs. Let's start working on
|
||||
some implementation!
|
||||
|
||||
#### Implementation
|
||||
It would be silly of me to explain every detail of creating a language in Haskell
|
||||
in this post; this is neither the purpose of the post, nor is it plausible
|
||||
to do this without covering monads, parser combinators, grammars, abstract syntax
|
||||
trees, and more. So, instead, I'll discuss the _interesting_ parts of the
|
||||
implementation.
|
||||
|
||||
##### Temporary Variables
|
||||
Our language is expression-based, yes. A function is a single,
|
||||
arbitrarily complex expression (involving `if/else`, list
|
||||
selectors, and more). So it would make sense to translate
|
||||
a function to a single, arbitrarily complex Python expression.
|
||||
However, the way we've designed our language makes it
|
||||
not-so-suitable for converting to a single expression! For
|
||||
instance, consider `xs[rand]`. We need to compute the list,
|
||||
get its length, generate a random number, and then access
|
||||
the corresponding element in the list. We use the list
|
||||
here twice, and simply repeating the expression would not
|
||||
be very smart: we'd be evaluating twice. So instead,
|
||||
we'll use a variable, assign the list to that variable,
|
||||
and then access that variable multiple times.
|
||||
|
||||
To be extra safe, let's use a fresh temporary variable
|
||||
every time we need to store something. The simplest
|
||||
way is to simply maintain a counter of how many temporary
|
||||
variables we've already used, and generate a new variable
|
||||
by prepending the word "temp" to that number. We start
|
||||
with `temp0`, then `temp1`, and so on. To keep a counter,
|
||||
we can use a state monad:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 230 230 >}}
|
||||
|
||||
Don't worry about the `Map.Map String [String]`, we'll get to that in a bit.
|
||||
For now, all we have to worry about is the second element of the tuple,
|
||||
the integer counting how many temporary variables we've used. We can
|
||||
get the current temporary variable as follows:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 232 235 >}}
|
||||
|
||||
We can also get a fresh temporary variable like this:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 237 240 >}}
|
||||
|
||||
Now, the
|
||||
{{< sidenote "left" "code-note" "code" >}}
|
||||
Since we are translating an expression, we must have the result of
|
||||
the translation yield an Python expression we can use in generating
|
||||
larger Python expressions. However, as we've seen, we occasionally
|
||||
have to use statements. Thus, the <code>translateExpr</code> function
|
||||
returns a <code>Translator ([Py.PyStmt], Py.PyExpr)</code>.
|
||||
{{< /sidenote >}}for generating a random list access looks like
|
||||
{{< sidenote "right" "ast-note" "this:" >}}
|
||||
The <code>Py.*</code> constructors are a part of a Python AST module I quickly
|
||||
threw together. I won't showcase it here, but you can always look at the
|
||||
source code for the blog (which includes this project)
|
||||
<a href="https://dev.danilafe.com/Web-Projects/blog-static">here</a>.
|
||||
{{< /sidenote >}}
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 325 330 >}}
|
||||
|
||||
##### Implementing "lazy evaluation"
|
||||
Lazy evaluation in functional programs usually arises from
|
||||
{{< sidenote "right" "graph-note" "graph reduction" >}}
|
||||
Graph reduction, more specifically the <em>Spineless,
|
||||
Tagless G-machine</em> is at the core of the Glasgow Haskell
|
||||
Compiler (GHC). Simon Peyton Jones' earlier book,
|
||||
<em>Implementing Functional Languages: a tutorial</em>
|
||||
details an earlier version of the G-machine.
|
||||
{{< /sidenote >}}. However, Python is neither
|
||||
functional nor graph-based, and we only lazily
|
||||
evaluate list selectors. Thus, we'll have to do
|
||||
some work to get our lazy evaluation to work as we desire.
|
||||
Here's what I came up with:
|
||||
|
||||
1. It's difficult to insert Python statements where they are
|
||||
needed: we'd have to figure out in which scope each variable
|
||||
has already been declared, and in which scope it's yet
|
||||
to be assigned.
|
||||
2. Instead, we can use a Python dictionary, called `cache`,
|
||||
and store computed versions of each variable in the cache.
|
||||
3. It's pretty difficult to check if a variable
|
||||
is in the cache, compute it if not, and then return the
|
||||
result of the computation, in one expression. This is
|
||||
true, unless that single expression is a function call, and we have a dedicated
|
||||
function that takes no arguments, computes the expression if needed,
|
||||
and uses the cache otherwise. We choose this route.
|
||||
4. We have already promised that we'd evaluate all the selected
|
||||
variables above a given variable before evaluating the variable
|
||||
itself. So, each function will first call (and therefore
|
||||
{{< sidenote "right" "force-note" "force" >}}
|
||||
Forcing, in this case, comes from the context of lazy evaluation. To
|
||||
force a variable or an expression is to tell the program to compute its
|
||||
value, even though it may have been putting it off.
|
||||
{{< /sidenote >}}) the functions
|
||||
generated for variables declared above the function's own variable.
|
||||
5. To keep track of all of this, we use the already-existing state monad
|
||||
as a reader monad (that is, we clear the changes we make to the monad
|
||||
after we're done translating the list selector). This is where the `Map.Map String [String]`
|
||||
comes from.
|
||||
|
||||
The `Map.Map String [String]` keeps track of variables that will be lazily computed,
|
||||
and also of the dependencies of each variable (the variables that need
|
||||
to be access before the variable itself). We compute such a map for
|
||||
each selector as follows:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 298 298 >}}
|
||||
|
||||
We update the existing map using `Map.union`:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 299 299 >}}
|
||||
|
||||
And, after we're done generating expressions in the body of this selector,
|
||||
we clear it to its previous value `vs`:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 302 302 >}}
|
||||
|
||||
We generate a single selector as follows:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 268 281 >}}
|
||||
|
||||
This generates a function definition statement, which we will examine in
|
||||
generated Python code later on.
|
||||
|
||||
Solving the problem this way also introduces another gotcha: sometimes,
|
||||
a variable is produced by a function call, and other times the variable
|
||||
is just a Python variable. We write this as follows:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 283 288 >}}
|
||||
|
||||
##### Special Case Insertion
|
||||
This is a silly language for a single homework assignment. I'm not
|
||||
planning to implement Hindley-Milner type inference, or anything
|
||||
of that sort. For the purpose of this language, things will be
|
||||
either a list, or not a list. And as long as a function __can__ return
|
||||
a list, it can also return the list from its base case. Thus,
|
||||
that's all we will try to figure out. The checking code is so
|
||||
short that we can include the whole snippet at once:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 219 227 >}}
|
||||
|
||||
`mergePossibleType`
|
||||
{{< sidenote "right" "bool-identity-note" "figures out" >}}
|
||||
An observant reader will note that this is just a logical
|
||||
OR function. It's not, however, good practice to use
|
||||
booleans for types that have two constructors with no arguments.
|
||||
Check out this <a href="https://programming-elm.com/blog/2019-05-20-solving-the-boolean-identity-crisis-part-1/">
|
||||
Elm-based article</a> about this, which the author calls the
|
||||
Boolean Identity Crisis.
|
||||
{{< /sidenote >}}, given two possible types for an
|
||||
expression, the final type for the expression.
|
||||
|
||||
There's only one real trick to this. Sometimes, like in
|
||||
`_search`, the only time we return something _known_ to be a list, that
|
||||
something is `xs`. Since we're making a list manipulation language,
|
||||
let's __assume the first argument to the function is a list__, and
|
||||
__use this information to determine expression types__. We guess
|
||||
types in a very basic manner otherwise: If you use the concatenation
|
||||
operator, or a list literal, then obviously we're working on a list.
|
||||
If you're returning the first argument of the function, that's also
|
||||
a list. Otherwise, it could be anything.
|
||||
|
||||
My Haskell linter actually suggested a pretty clever way of writing
|
||||
the whole "add a base case if this function returns a list" code.
|
||||
Check it out:
|
||||
|
||||
{{< codelines "Haskell" "cs325-langs/src/LanguageOne.hs" 260 266 >}}
|
||||
|
||||
Specifically, look at the line with `let fastReturn = ...`. It
|
||||
uses a list comprehension: we take a parameter `p` from the list of
|
||||
parameter `ps`, but only produce the statements for the base case
|
||||
if the possible type computed using `p` is `List`.
|
||||
|
||||
### The Output
|
||||
What kind of beast have we created? Take a look for yourself:
|
||||
```Python
|
||||
def qselect(xs,k):
|
||||
if xs==[]:
|
||||
return xs
|
||||
cache = {}
|
||||
def pivot():
|
||||
if ("pivot") not in (cache):
|
||||
cache["pivot"] = xs.pop(0)
|
||||
return cache["pivot"]
|
||||
def left():
|
||||
def temp2(arg):
|
||||
out = []
|
||||
for arg0 in arg:
|
||||
if arg0<=pivot():
|
||||
out.append(arg0)
|
||||
return out
|
||||
pivot()
|
||||
if ("left") not in (cache):
|
||||
cache["left"] = temp2(xs)
|
||||
return cache["left"]
|
||||
def right():
|
||||
def temp3(arg):
|
||||
out = []
|
||||
for arg0 in arg:
|
||||
if arg0>pivot():
|
||||
out.append(arg0)
|
||||
return out
|
||||
left()
|
||||
pivot()
|
||||
if ("right") not in (cache):
|
||||
cache["right"] = temp3(xs)
|
||||
return cache["right"]
|
||||
if k>(len(left())+1):
|
||||
temp4 = qselect(right(), k-len(left())-1)
|
||||
else:
|
||||
if k==(len(left())+1):
|
||||
temp5 = [pivot()]
|
||||
else:
|
||||
temp5 = qselect(left(), k)
|
||||
temp4 = temp5
|
||||
return temp4
|
||||
def _search(xs,k):
|
||||
if xs==[]:
|
||||
return xs
|
||||
if xs[1]==k:
|
||||
temp6 = xs
|
||||
else:
|
||||
if xs[1]>k:
|
||||
temp8 = _search(xs[0], k)
|
||||
else:
|
||||
temp8 = _search(xs[2], k)
|
||||
temp6 = temp8
|
||||
return temp6
|
||||
def sorted(xs):
|
||||
if xs==[]:
|
||||
return xs
|
||||
return sorted(xs[0])+[xs[1]]+sorted(xs[2])
|
||||
def search(xs,k):
|
||||
return len(_search(xs, k))!=0
|
||||
def insert(xs,k):
|
||||
return _insert(k, _search(xs, k))
|
||||
def _insert(k,xs):
|
||||
if k==[]:
|
||||
return k
|
||||
if len(xs)==0:
|
||||
temp16 = xs
|
||||
temp16.append([])
|
||||
temp17 = temp16
|
||||
temp17.append(k)
|
||||
temp18 = temp17
|
||||
temp18.append([])
|
||||
temp15 = temp18
|
||||
else:
|
||||
temp15 = xs
|
||||
return temp15
|
||||
```
|
||||
It's...horrible! All the `tempX` variables, __three layers of nested function declarations__, hardcoded cache access. This is not something you'd ever want to write.
|
||||
Even to get this code, I had to come up with hacks __in a language I created__.
|
||||
The first is the hack is to make the `qselect` function use the `xs == []` base
|
||||
case. This doesn't happen by default, because `qselect` doesn't return a list!
|
||||
To "fix" this, I made `qselect` return the number it found, wrapped in a
|
||||
list literal. This is not up to spec, and would require another function
|
||||
to unwrap this list.
|
||||
|
||||
While `qselect` was struggling with not having the base case, `insert` had
|
||||
a base case it didn't need: `insert` shouldn't return the list itself
|
||||
when it's empty, it should insert into it! However, when we use the `<<`
|
||||
list insertion operator, the language infers `insert` to be a list-returning
|
||||
function itself, inserting into an empty list will always fail. So, we
|
||||
make a function `_insert`, which __takes the arguments in reverse__.
|
||||
The base case will still be generated, but the first argument (against
|
||||
which the base case is checked) will be a number, so the `k == []` check
|
||||
will always fail.
|
||||
|
||||
That concludes this post. I'll be working on more solutions to homework
|
||||
assignments in self-made languages, so keep an eye out!
|
||||
218
content/blog/01_cs325_languages_hw2.md
Normal file
218
content/blog/01_cs325_languages_hw2.md
Normal file
@@ -0,0 +1,218 @@
|
||||
---
|
||||
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 nomenclature 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 the variables below:
|
||||
|
||||
* `STATE`, to manipulate or check the current state.
|
||||
* `LEFT` and `RIGHT`, to access the two lists being merged.
|
||||
* `L` and `R`, constants that are used to compare against the `SOURCE` variable.
|
||||
* `SOURCE`, to denote which list a number came from.
|
||||
* `LSTATE` and `RSTATE`, to denote the final states from the two subproblems.
|
||||
|
||||
We use an `if`-statement to check if the element that was popped came
|
||||
from the right list (by checking `SOURCE == R`). If it did, we increment the counter
|
||||
(state) by the proper amount. In the combine step, which has access to the
|
||||
same variables, 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" 167 176 >}}
|
||||
|
||||
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" 101 161 >}}
|
||||
|
||||
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!
|
||||
|
||||
### Appendix (Missing Homework Question)
|
||||
I should not view homework assignments on a small-screen device. There __was__ a third problem
|
||||
on homework 2:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw2.txt" 46 65 >}}
|
||||
|
||||
This is not a mergesort variant, and adding support for it into our second language
|
||||
will prevent us from making it the neat specialized
|
||||
{{< sidenote "right" "dsl-note" "DSL" >}}
|
||||
DSL is a shortened form of "domain specific language", which was briefly
|
||||
described in another sidenote while solving homework 1.
|
||||
{{< /sidenote >}} that was just saw. We'll do something else, instead:
|
||||
we'll use the language we defined in homework 1 to solve this
|
||||
problem:
|
||||
|
||||
```
|
||||
empty() = [0, 0];
|
||||
longest(xs) =
|
||||
if |xs| != 0
|
||||
then _longest(longest(xs[0]), longest(xs[2]))
|
||||
else empty();
|
||||
_longest(l, r) = [max(l[0], r[0]) + 1, max(l[0]+r[0], max(l[1], r[1]))];
|
||||
```
|
||||
|
||||
{{< sidenote "right" "terrible-note" "This is quite terrible." >}}
|
||||
This is probably true with any program written in our first
|
||||
language.
|
||||
{{< /sidenote >}} In these 6 lines of code, there are two hacks
|
||||
to work around the peculiarities of the language.
|
||||
|
||||
At each recursive call, we want to keep track of both the depth
|
||||
of the tree and the existing longest path. This is because
|
||||
the longest path could be found either somewhere down
|
||||
a subtree, or from combining the largest depths of
|
||||
two subtrees. To return two values from a function in Python,
|
||||
we'd use a tuple. Here, we use a list.
|
||||
|
||||
Alarm bells should be going off here. There's no reason why we should
|
||||
ever return an empty list from the recursive call: at the very least, we
|
||||
want to return `[0,0]`. But placing such a list literal in a function
|
||||
will trigger the special case insertion. So, we have to hide this literal
|
||||
from the compiler. Fortunately, that's not too hard to do - the compiler
|
||||
is pretty halfhearted in its inference of types. Simply putting
|
||||
the literal behind a constant function (`empty`) does the trick.
|
||||
|
||||
The program uses the subproblem depths multiple times in the
|
||||
final computation. We thus probably want to assign these values
|
||||
to names so we don't have to perform any repeated work. Since
|
||||
the only two mechanisms for
|
||||
{{< sidenote "right" "binding-note" "binding variables" >}}
|
||||
To bind a variable means to assign a value to it.
|
||||
{{< /sidenote >}} in this language are function calls
|
||||
and list selectors, we use a helper function `_longest`,
|
||||
which takes two subproblem solutions an combines them
|
||||
into a new solution. It's pretty obvious that `_longest`
|
||||
returns a list, so the compiler will try insert a base
|
||||
case. Fortunately, subproblem solutions are always
|
||||
lists of two numbers, so this doesn't affect us too much.
|
||||
295
content/blog/02_cs325_languages_hw3.md
Normal file
295
content/blog/02_cs325_languages_hw3.md
Normal file
@@ -0,0 +1,295 @@
|
||||
---
|
||||
title: A Language for an Assignment - Homework 3
|
||||
date: 2020-01-02T22:17:43-08:00
|
||||
tags: ["Haskell", "Python", "Algorithms"]
|
||||
draft: true
|
||||
---
|
||||
|
||||
It rained in Sunriver on New Year's Eve, and it continued to rain
|
||||
for the next couple of days. So, instead of going skiing as planned,
|
||||
to the dismay of my family and friends, I spent the majority of
|
||||
those days working on the third language for homework 3. It
|
||||
was quite the language, too - the homework has three problems, each of
|
||||
which has a solution independent of the others. I invite you
|
||||
to join me in my descent into madness as we construct another language.
|
||||
|
||||
### Homework 3
|
||||
Let's take a look at the three homework problems. The first two are
|
||||
related, but are solved using a different technique:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw3.txt" 18 30 >}}
|
||||
|
||||
This problem requires us to find the `k` numbers closest to some
|
||||
query (which I will call `n`) from a list `xs`. The list isn't sorted, and the
|
||||
problem must run in linear time. Sorting the list would require
|
||||
the standard
|
||||
{{< sidenote "right" "n-note" "\(O(n\log n)\) time." >}}
|
||||
The \(n\) in this expression is not the same as the query <code>n</code>,
|
||||
but rather the length of the list. In fact, I have not yet assigned
|
||||
the length of the input <code>xs</code> to any variable. If we say that
|
||||
\(m\) is a number that denotes that length, the proper expression
|
||||
for the complexity is \(O(m \log m)\).
|
||||
{{< /sidenote >}} Thus, we have to take another route, which should
|
||||
already be familiar: quickselect. Using quickselect, we can find the `k`th
|
||||
closest number, and then collect all the numbers that are closer than the `kth`
|
||||
closest number. So, we need a language that:
|
||||
|
||||
* Supports quickselect (and thus, list partitioning and recursion).
|
||||
* Supports iteration, {{< sidenote "left" "iteration-note" "multiple times." >}}
|
||||
Why would we need to iterate multiple times? Note that we could have a list
|
||||
of numbers that are all the same, <code>[1,1,1,1,1]</code>. Then, we'll need
|
||||
to know how many of the numbers <em>equally close</em> as the <code>k</code>th
|
||||
element we need to include, which will require another pass through the list.
|
||||
{{< /sidenote >}}
|
||||
|
||||
That's a good start. Let's take a look at the second problem:
|
||||
|
||||
{{< codelines "text" "cs325-langs/hws/hw3.txt" 33 47 >}}
|
||||
|
||||
This problem really is easier. We have to find the position of _the_ closest
|
||||
element, and then try expand towards either the left or right, depending on
|
||||
which end is better. This expansion will take several steps, and will
|
||||
likely require a way to "look" at a given part of the list. So let's add two more
|
||||
rules. We need a language that also:
|
||||
|
||||
* Supports looping control flow, such as `while`.
|
||||
* {{< sidenote "right" "view-note" "Allows for a \"view\" into the list" >}}
|
||||
We could, of course, simply use list indexing. But then, we'd just be making
|
||||
a simple imperative language, and that's boring. So let's play around
|
||||
with our design a little, and experimentally add such a "list view" component.
|
||||
{{< /sidenote >}}
|
||||
(like an abstraction over indexing).
|
||||
|
||||
This is shaping up to be a fun language. Let's take a look at the last problem:
|
||||
{{< codelines "text" "cs325-langs/hws/hw3.txt" 50 64 >}}
|
||||
|
||||
This problem requires more iterations of a list. We have several
|
||||
{{< sidenote "right" "cursor-note" "\"cursors\"" >}}
|
||||
I always make the language before I write the post, since a lot of
|
||||
design decisions change mid-implementation. I realize now that
|
||||
"cursors" would've been a better name for this language feature,
|
||||
but alas, it is too late.
|
||||
{{< /sidenote >}} looking into the list, and depending if the values
|
||||
at each of the cursors add up, we do or do not add a new tuple to a list. So,
|
||||
two more requirements:
|
||||
|
||||
* The "cursors" must be able to interact.
|
||||
* The language can represent {{< sidenote "left" "tuple-note" "tuples." >}}
|
||||
We could, of course, hack some other way to return a list of tuples, but
|
||||
it turns out tuples are pretty simple to implement, and help make for nicer
|
||||
programming in our language.
|
||||
{{< /sidenote >}}
|
||||
|
||||
I think we've gathered what we want from the homework. Let's move on to the
|
||||
language!
|
||||
|
||||
### A Language
|
||||
As is now usual, let's envision a solution to the problems in our language. There
|
||||
are actually quite a lot of functions to look at, so let's see them one by one.
|
||||
First, let's look at `qselect`.
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw3.lang" 1 19 >}}
|
||||
|
||||
After the early return, the first interesting part of the language is the
|
||||
use of what I have decided to call a __list traverser__. The list
|
||||
traverser is a __generalization of a list index__. Whenever we use a list
|
||||
index variable, we generally use the following operations:
|
||||
|
||||
* __Initialize__: we set the list index to some initial value, such as 0.
|
||||
* __Step__: If we're walking the list from left to right, we increment the index.
|
||||
If we're walking the list from right to left, we decrement the index.
|
||||
* __Validity Check__: We check if the index is still valid (that is, we haven't
|
||||
gone past the edge of the list).
|
||||
* __Access__: Get the element the cursor is pointing to.
|
||||
|
||||
A {{< sidenote "right" "cpp-note" "traverser declaration" >}}
|
||||
A fun fact is that we've just rediscovered C++
|
||||
<a href="http://www.cplusplus.com/reference/iterator/">iterators</a>. C++
|
||||
containers and their iterators provide us with the operations I described:
|
||||
|
||||
We can initialize an iterator like <code>auto it = list.begin()</code>. We
|
||||
can step the iterator using <code>it++</code>. We can check its validity
|
||||
using <code>it != list.end()</code>, and access what it's pointing to using
|
||||
<code>*it</code>. While C++ uses templates and inheritance for this,
|
||||
we define a language feature specifically for lists.
|
||||
|
||||
{{< /sidenote >}} describes these operations. The declartion for the `bisector`
|
||||
traverser creates a "cursor" over the list `xs`, that goes between the 0th
|
||||
and last elements of `xs`. The declaration for the `pivot` traverser creates
|
||||
a "cursor" over the list `xs` that jumps around random locations in the list.
|
||||
|
||||
The next interesting part of the language is a __traverser macro__. This thing,
|
||||
that looks like a function call (but isn't), performs an operation on the
|
||||
cursor. For instance, `pop!` removes the element at the cursor from the list,
|
||||
whereas `bisect!` categorizes the remaining elements in the cursor's list
|
||||
into two lists, using a boolean-returning lambda (written in Java syntax).
|
||||
|
||||
Note that this implementation of `qselect` takes a function `c`, which it
|
||||
uses to judge the actual value of the number. This is because our `qselect`
|
||||
won't be finding _the_ smallest number, but the number with the smallest difference
|
||||
with `n`. `n` will be factored in via the function.
|
||||
|
||||
Next up, let's take a look at the function that uses `qselect`, `closestUnsorted`:
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw3.lang" 21 46 >}}
|
||||
|
||||
Like we discussed, it finds the `k`th closest element (calling it `min`),
|
||||
and counts how many elements that are __equal__ need to be included,
|
||||
by setting the number to `k` at first, and subtracting 1 for every number
|
||||
it encounters that's closer than `min`. Notice that we use the `valid!` and
|
||||
`step!` macros, which implement the opertions we described above. Notice
|
||||
that the user doesn't deal with adding and subtracting numbers, and doing
|
||||
comparisons. All they have to do is ask "am I still good to iterate?"
|
||||
|
||||
Next, let's take a look at `closestSorted`, which will require more
|
||||
traverser macros.
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw3.lang" 48 70 >}}
|
||||
|
||||
The first new macro is `canstep!`. This macro just verifies that
|
||||
the traverser can make another step. We need this for the "reverse" iterator,
|
||||
which indicates the lower bound of the range of numbers we want to return,
|
||||
because `subset!` (which itself is just Python's slice, like `xs[a:b]`), uses an inclusive bottom
|
||||
index, and thus, we can't afford to step it before knowing that we can, and that
|
||||
it's a better choice after the step.
|
||||
|
||||
Similarly, we have the `at!(t, i)` macro, which looks at the
|
||||
traverser `t`, with offset `i`.
|
||||
|
||||
We have two loops. The first loop runs as long as we can expand the range in both
|
||||
directions, and picks the better direction at each iteration. The second loop
|
||||
runs as long as we still want more numbers, but have already hit the edge
|
||||
of the list on the left or on the right.
|
||||
|
||||
Finally, let's look at the solution to `xyz`:
|
||||
|
||||
{{< codelines "text" "cs325-langs/sols/hw3.lang" 72 95 >}}
|
||||
|
||||
I won't go in depth, but notice that the expression in the `span` part
|
||||
of the `traverser` declaration can access another traverser. We treat
|
||||
as a feature the fact that this expression isn't immediately evaluated at the place
|
||||
of the traverser declaration. Rather, every time that a comparison for a traverser
|
||||
operation is performed, this expression is re-evaluated. This allows us to put
|
||||
dynamic bounds on traversers `y` and `z`, one of which must not exceed the other.
|
||||
|
||||
This is more than enough to work with. Let's move on to the implementation.
|
||||
|
||||
#### Implementation
|
||||
Again, let's not go too far into the details of implementing the language from scratch.
|
||||
Instead, let's take a look into specific parts of the language that deserve attention.
|
||||
|
||||
##### Revenge of the State Monad
|
||||
Our previous language was, indeed, a respite from complexity. Translation was
|
||||
straightforward, and the resulting expressions and statements were plugged straight
|
||||
into a handwritten AST. We cannot get away with this here; the language is powerful
|
||||
enough to implement three list-based problems, which comes at the cost of increased
|
||||
complexity.
|
||||
|
||||
We need, once again, to generate temporary variables. We also need to keep track of
|
||||
which variables are traversers, and the properties of these traversers, throughout
|
||||
each function of the language. We thus fall back to using `Control.Monad.State`:
|
||||
|
||||
{{< todo >}}Code for Translator Monad{{< /todo >}}
|
||||
|
||||
There's one part of the state tuple that we haven't yet explained: the list of
|
||||
statements.
|
||||
|
||||
##### Generating Statements
|
||||
Recall that our translation function for expressions in the first homework had the type:
|
||||
|
||||
```Haskell
|
||||
translateExpr :: Expr -> Translator ([Py.PyStmt], Py.PyExpr)
|
||||
```
|
||||
|
||||
We then had to use `do`-notation, and explicitly concatenate lists
|
||||
of emitted statements. In this language, I took an alternative route: I made
|
||||
the statements part of the state. They are thus implicitly generated and
|
||||
stored in the monad, and expression generators don't have to worry about
|
||||
concatenating them. When the program is ready to use the generated statements
|
||||
(say, when an `if`-statement needs to use the statements emitted by the condition
|
||||
expression), we retrieve them from the monad:
|
||||
|
||||
{{< todo >}}Code for getting statements{{< /todo >}}
|
||||
|
||||
##### Validating Traverser Declarations
|
||||
We declare two separate types that hold traverser data. The first is a kind of "draft"
|
||||
type, `TraverserData`. This record holds all possible configurations of a traverser
|
||||
that occur as the program is iterating through the various `key: value` pairs in
|
||||
the declaration. For instance, at the very beginning of processing a traverser declaration,
|
||||
our program will use a "default" `TraverserData`, with all fields set to `Nothing` or
|
||||
their default value. This value will then be modified by the first key/value pair,
|
||||
changing, for instance, the list that the traverser operates on. This new modified
|
||||
`TraverserData` will then be modified by the next key/value pair, and so on. This
|
||||
is, effectively, a fold operation.
|
||||
|
||||
{{< todo >}}Code for TraverserData{{< /todo >}}
|
||||
{{< todo >}}Maybe sidenote about fold?{{< /todo >}}
|
||||
|
||||
The data may not have all the required fields until the very end, and its type
|
||||
reflects that: `Maybe String` here, `Maybe TraverserBounds` there. We don't
|
||||
want to deal with unwrapping the `Maybe a` values every time we use the traverser,
|
||||
especially if we've done so before. So, we define a `ValidTraverserData` record,
|
||||
that does not have `Maybe` arguments, and thus, has all the required data. At the
|
||||
end of a traverser declaration, we attempt to translate a `TraverserData` into
|
||||
a `ValidTraverserData`, invoking `fail` if we can't, and storing the `ValidTraverserData`
|
||||
into the state otherwise. Then, every time we retrieve a traverser from the state,
|
||||
it's guaranteed to be valid, and we have to spend no extra work unpacking it. We
|
||||
define a lookup monadic operation like this:
|
||||
|
||||
{{< todo >}}Code for getting ValidTraverserData{{< /todo >}}
|
||||
|
||||
##### Compiling Macros
|
||||
I didn't call them macros for no reason. Clearly, we don't want to generate
|
||||
code that
|
||||
{{< sidenote "right" "increment-note" "calls functions only to increment an index." >}}
|
||||
In fact, there's no easy way to do this at all. Python's integers (if we choose to
|
||||
represent our traversers using integers), are immutable. Furthermore, unlike C++,
|
||||
where passing by reference allows a function to change its parameters "outside"
|
||||
the call, Python offers no way to reassign a different value to a variable given
|
||||
to a function.
|
||||
<br><br>
|
||||
For an example use of C++'s pass-by-reference mechanic, consider <code>std::swap</code>:
|
||||
it's a function, but it modifies the two variables given to it. There's no
|
||||
way to generically implement such a function in Python.
|
||||
{{< /sidenote >}} We also can't allow arbitrary expressions to serve as traversers:
|
||||
our translator keeps some context about which variables are traversers, what their
|
||||
bounds are, and how they behave. Thus, __calls to traverser macros are very much macros__:
|
||||
they operate on AST nodes, and __require__ that their first argument is a variable,
|
||||
named like the traverser. We use the `requireTraverser` monadic operation
|
||||
to get the traverser associated with the given variable name, and then perform
|
||||
the operation as intended. The `at!(t)` operation is straightforward:
|
||||
|
||||
{{< todo >}}Code for at!{{< /todo >}}
|
||||
|
||||
The `at!(t,i)` is less so, since it deals with the intricacies of accessing
|
||||
the list at either a positive of negative offset, depending on the direction
|
||||
of the traverser. We implement a function to properly generate an expression for the offset:
|
||||
|
||||
{{< todo >}}Code for traverserIncrement{{< /todo >}}
|
||||
|
||||
We then implement `at!(t,i)` as follows:
|
||||
|
||||
{{< todo >}}Code for at!{{< /todo >}}
|
||||
|
||||
The most complicated macro is `bisect!`. It must be able to step the traverser,
|
||||
and also return a tuple of two lists that the bisection yields. We also
|
||||
prefer that it didn't pollute the environment with extra variables. To
|
||||
achieve this, we want `bisect!` to be a function call. We want this
|
||||
function to implement the iteration and list construction.
|
||||
|
||||
`bisect!`, by definition, takes a lambda. This lambda, in our language, is declared
|
||||
in the lexical scope in which `bisect!` is called. Thus, to guarantee correct translation,
|
||||
we must do one of two things:
|
||||
|
||||
1. Translate 1-to-1, and create a lambda, passing it to a fixed `bisect` function declared
|
||||
elsewhere.
|
||||
2. Translate to a nested function declaration, inlining the lambda.
|
||||
|
||||
{{< todo >}}Maybe sidenote about inline?{{< /todo >}}
|
||||
|
||||
Since I quite like the idea of inlining a lambda, let's settle for that. To do this,
|
||||
we pull a fresh temporary variable and declare a function, into which we place
|
||||
the traverser iteration code, as well as the body of the lambda, with the variable
|
||||
substituted for the list access expression. Here's the code:
|
||||
|
||||
{{< todo >}}Code for bisect!{{< /todo >}}
|
||||
65
content/blog/09_compiler_polymorphism.md
Normal file
65
content/blog/09_compiler_polymorphism.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 9 - Polymorphism
|
||||
date: 2019-12-09T23:26:46-08:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
draft: true
|
||||
---
|
||||
|
||||
Last time, we wrote some pretty interesting programs in our little language.
|
||||
We successfully expressed arithmetic and recursion. But there's one thing
|
||||
that we cannot express in our language without further changes: an `if` statement.
|
||||
|
||||
Suppose we didn't want to add a special `if/else` expression into our language.
|
||||
Thanks to lazy evaluation, we can express it using a function:
|
||||
|
||||
```
|
||||
defn if c t e = {
|
||||
case c of {
|
||||
True -> { t }
|
||||
False -> { e }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
But an issue still remains: so far, our compiler remains __monomorphic__. That
|
||||
is, a particular function can only have one possible type for each one of its
|
||||
arguments. With our current setup, something like this
|
||||
{{< sidenote "right" "if-note" "would not work:" >}}
|
||||
In a polymorphically typed language, the inner <code>if</code> would just evaluate to
|
||||
<code>False</code>, and the whole expression to 3.
|
||||
{{< /sidenote >}}
|
||||
|
||||
```
|
||||
if (if True False True) 11 3
|
||||
```
|
||||
|
||||
This is because, for this to work, both of the following would need to hold (borrowing
|
||||
some of our notation from the [typechecking]({{< relref "03_compiler_typechecking.md" >}}) post):
|
||||
|
||||
$$
|
||||
\\text{if} : \\text{Int} \\rightarrow \\text{Int}
|
||||
$$
|
||||
$$
|
||||
\\text{if} : \\text{Bool} \\rightarrow \\text{Bool}
|
||||
$$
|
||||
|
||||
But using our rules so far, such a thing is impossible, since there is no way for
|
||||
\\(\text{Int}\\) to be unified with \\(\text{Bool}\\). We need a more powerful
|
||||
set of rules to describe our program's types. One such set of rules is
|
||||
the [Hindley-Milner type system](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system),
|
||||
which we have previously alluded to. In fact, the rules we came up
|
||||
with were already very close to Hindley-Milner, with the exception of two:
|
||||
__generalization__ and __instantiation__. Instantiation first:
|
||||
|
||||
$$
|
||||
\frac
|
||||
{\\Gamma \\vdash e : \\sigma \\quad \\sigma' \\sqsubseteq \\sigma}
|
||||
{\\Gamma \\vdash e : \\sigma'}
|
||||
$$
|
||||
|
||||
Next, generalization:
|
||||
$$
|
||||
\frac
|
||||
{\\Gamma \\vdash e : \\sigma \\quad \\alpha \\not \\in \\text{free}(\\Gamma)}
|
||||
{\\Gamma \\vdash e : \\forall a . \\sigma}
|
||||
$$
|
||||
@@ -10,7 +10,7 @@ I found that __sidenotes__ were a feature that I didn't even know I needed.
|
||||
A lot of my writing seems to use small parenthesized remarks (like this), which,
|
||||
although it doesn't break the flow in a grammatical sense, lengthens the
|
||||
sentence, and makes it harder to follow. Since I do my best to write content
|
||||
to help explain stuff (like the [compiler series]({{ relref "00_compiler_intro.md" }})),
|
||||
to help explain stuff (like the [compiler series]({{< relref "00_compiler_intro.md" >}})),
|
||||
making sentences __more__ difficult to understand is a no-go.
|
||||
|
||||
So, what do they look like?
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
@import "style.scss";
|
||||
|
||||
$sidenote-width: 350px;
|
||||
$sidenote-offset: 15px;
|
||||
$sidenote-width: 30rem;
|
||||
$sidenote-offset: 1.5rem;
|
||||
$sidenote-padding: 1rem;
|
||||
$sidenote-highlight-border-width: .2rem;
|
||||
|
||||
.sidenote {
|
||||
&:hover {
|
||||
@@ -11,15 +13,16 @@ $sidenote-offset: 15px;
|
||||
}
|
||||
|
||||
.sidenote-content {
|
||||
border: 2px dashed;
|
||||
padding: 9px;
|
||||
border: $sidenote-highlight-border-width dashed;
|
||||
padding: $sidenote-padding -
|
||||
($sidenote-highlight-border-width - $standard-border-width);
|
||||
border-color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidenote-label {
|
||||
border-bottom: 2px solid $primary-color;
|
||||
border-bottom: .2rem solid $primary-color;
|
||||
}
|
||||
|
||||
.sidenote-checkbox {
|
||||
@@ -30,7 +33,7 @@ $sidenote-offset: 15px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: $sidenote-width;
|
||||
margin-top: -1.5em;
|
||||
margin-top: -1.5rem;
|
||||
|
||||
&.sidenote-right {
|
||||
right: 0;
|
||||
@@ -45,8 +48,8 @@ $sidenote-offset: 15px;
|
||||
@media screen and
|
||||
(max-width: $container-width + 2 * ($sidenote-width + 2 * $sidenote-offset)) {
|
||||
position: static;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
display: none;
|
||||
|
||||
@@ -55,16 +58,16 @@ $sidenote-offset: 15px;
|
||||
}
|
||||
|
||||
&.sidenote-left {
|
||||
margin-left: 0px;
|
||||
margin-left: 0rem;
|
||||
}
|
||||
|
||||
&.sidenote-right {
|
||||
margin-right: 0px;
|
||||
margin-right: 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include bordered-block;
|
||||
padding: 10px;
|
||||
padding: $sidenote-padding;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
$container-width: 800px;
|
||||
$container-width: 50rem;
|
||||
$standard-border-width: .075rem;
|
||||
|
||||
$primary-color: #36e281;
|
||||
$primary-color-dark: darken($primary-color, 10%);
|
||||
$code-color: #f0f0f0;
|
||||
$code-color-dark: darken($code-color, 10%);
|
||||
$border-color: #bfbfbf;
|
||||
|
||||
$font-heading: "Lora", serif;
|
||||
$font-body: "Raleway", serif;
|
||||
$font-code: "Inconsolata", monospace;
|
||||
$standard-border: 1px solid $border-color;
|
||||
|
||||
$standard-border: $standard-border-width solid $border-color;
|
||||
|
||||
@mixin bordered-block {
|
||||
border: $standard-border;
|
||||
border-radius: 2px;
|
||||
border-radius: .2rem;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-body;
|
||||
font-size: 1.0em;
|
||||
font-size: 1.0rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 1rem;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
@@ -27,8 +31,8 @@ main {
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: .1em;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .1rem;
|
||||
margin-top: .5rem;
|
||||
font-family: $font-heading;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
@@ -49,7 +53,7 @@ code {
|
||||
|
||||
pre code {
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
padding: 0.5rem;
|
||||
overflow-x: auto;
|
||||
background-color: $code-color;
|
||||
}
|
||||
@@ -61,12 +65,12 @@ pre code {
|
||||
box-sizing: border-box;
|
||||
|
||||
@media screen and (max-width: $container-width){
|
||||
padding: 0em 1em 0em 1em;
|
||||
padding: 0rem 1rem 0rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.button, input[type="submit"] {
|
||||
padding: 0.5em;
|
||||
padding: 0.5rem;
|
||||
background-color: $primary-color;
|
||||
border: none;
|
||||
color: white;
|
||||
@@ -87,7 +91,7 @@ pre code {
|
||||
nav {
|
||||
background-color: $primary-color;
|
||||
width: 100%;
|
||||
margin: 1em 0px 1em 0px;
|
||||
margin: 1rem 0rem 1rem 0rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
@@ -110,7 +114,7 @@ nav a {
|
||||
}
|
||||
|
||||
.post-content {
|
||||
margin-top: .5em;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
|
||||
{{ $style := resources.Get "scss/style.scss" | resources.ToCSS | resources.Minify }}
|
||||
{{ $sidenotes := resources.Get "scss/sidenotes.scss" | resources.ToCSS | resources.Minify }}
|
||||
{{ $icon := resources.Get "img/favicon.png" }}
|
||||
<link rel="stylesheet" href="{{ $style.Permalink }}">
|
||||
<link rel="stylesheet" href="{{ $sidenotes.Permalink }}">
|
||||
<link rel="icon" type="image/png" href="{{ $icon.Permalink }}">
|
||||
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>
|
||||
{{ template "_internal/google_analytics.html" . }}
|
||||
|
||||
9
themes/vanilla/layouts/shortcodes/numberedsidenote
Normal file
9
themes/vanilla/layouts/shortcodes/numberedsidenote
Normal file
@@ -0,0 +1,9 @@
|
||||
{{ .Page.Scratch.Add "numbernote-id" 1 }}
|
||||
{{ $id := .Page.Scratch.Get "numbernote-id" }}
|
||||
<span class="sidenote">
|
||||
<label class="sidenote-label" for="numbernote-{{ $id }}">({{ $id }})</label>
|
||||
<input class="sidenote-checkbox" type="checkbox" id="numbernote-{{ $id }}"></input>
|
||||
<span class="sidenote-content sidenote-{{ .Get 0 }}">
|
||||
{{ .Inner }}
|
||||
</span>
|
||||
</span>
|
||||
Reference in New Issue
Block a user