diff --git a/assets/why3/eucl_div.mlw b/assets/why3/eucl_div.mlw new file mode 100644 index 0000000..27cf0d1 --- /dev/null +++ b/assets/why3/eucl_div.mlw @@ -0,0 +1,30 @@ +(* Euclidean division + + 1. Prove correctness of euclideian divison: + `division a b` returns an integer `q` such that + `a = bq+r` and `0 <= r < b` for some `r`. + + - You have to strengthen the loop invariant. +*) + +module Division + + use int.Int + + (* IMPORTANT: DON'T MODIFY LINES EXCEPT `TODO`s OR YOU WILL GET ZERO POINTS *) + let division (a b: int) : int + requires { a >= 0 } + requires { b > 0 } + ensures { exists r: int. a = b * result + r /\ 0 <= r < b } + = + let ref q = 0 in + let ref r = a in + while r >= b do + invariant { true (*TODO*) } + variant { r } + q <- q + 1; + r <- r - b + done; + q + +end diff --git a/assets/why3/ex1_eucl_div.mlw b/assets/why3/ex1_eucl_div.mlw deleted file mode 100644 index 00e7d58..0000000 --- a/assets/why3/ex1_eucl_div.mlw +++ /dev/null @@ -1,34 +0,0 @@ -(* Euclidean division - - 1. Prove soundness, i.e. (division a b) returns an integer q such that - a = bq+r and 0 <= r < b for some r. - (You have to strengthen the precondition.) - - Do you have to require b <> 0? Why? - - 2. Prove termination. - (You may have to strengthen the precondition even further.) -*) - -module Division - - use int.Int - - let division (a b: int) : int - requires { a > 0 /\ b > 0 } - ensures { exists r: int. a = b * result + r /\ 0 <= r < b } - = - let ref q = 0 in - let ref r = a in - while r >= b do - invariant { a = b * q + r /\ r >= 0 } - variant { r } - q <- q + 1; - r <- r - b - done; - q - - let main () = - division 1000 42 - -end diff --git a/assets/why3/ex2_fact.mlw b/assets/why3/fact.mlw similarity index 54% rename from assets/why3/ex2_fact.mlw rename to assets/why3/fact.mlw index 343ae67..42d995d 100644 --- a/assets/why3/ex2_fact.mlw +++ b/assets/why3/fact.mlw @@ -1,23 +1,17 @@ (* Two programs to compute the factorial - Note: function "fact" from module int.Fact (already imported) - can be used in specifications. - Questions: 1. In module FactRecursive: - a. Prove soundness of function fact_rec. - - b. Prove its termination. + a. Implement the program that satisfies specification. 2. In module FactLoop: - a. Prove soundness of function fact_loop. + a. Strengthen the invariant to prove correctness of the given implementation. - b. Prove its termination. - - c. Change the code to use a for loop instead of a while loop. + b. Select a correct variant to prove the termination. + *) module FactRecursive @@ -29,9 +23,9 @@ module FactRecursive requires { n >= 0 } ensures { result = fact n } variant { n } - = - if n = 0 then 1 else n * fact_rec (n - 1) - + = (* IMPORTANT: DON'T MODIFY THE ABOVE LINES *) + 0 (*TODO*) + end module FactLoop @@ -45,8 +39,10 @@ module FactLoop = let ref m = 0 in let ref r = 1 in while m < n do - invariant { 0 <= m <= n /\ r = fact m } - variant { n - m } + (* IMPORTANT: DON'T MODIFY THE ABOVE LINES *) + invariant { true (* TODO *) } + variant { n (* TODO *) } + (* IMPORTANT: DON'T MODIFY THE BELOW LINES *) m <- m + 1; r <- r * m done; diff --git a/assets/why3/max.mlw b/assets/why3/max.mlw new file mode 100644 index 0000000..92ef8dd --- /dev/null +++ b/assets/why3/max.mlw @@ -0,0 +1,29 @@ +(* Max + + Given an array `a` of natural numbers with length `n`, + return the maximum element of the array. + + You should stengthen the loop invariant. + +*) + +module Max + + use int.Int + use ref.Ref + use array.Array + + let max (a: array int) (n: int) : (max: int) + requires { n = length a } + requires { forall i. 0 <= i < n -> a[i] >= 0 } + ensures { forall i. 0 <= i < n -> a[i] <= max } + ensures { exists i. 0 <= i < n -> a[i] = max } + = let ref max = 0 in + for i = 0 to n - 1 do + (* IMPORTANT: MODIFY ONLY THIS INVARIANT, OR YOU'LL GET ZERO POINTS *) + invariant { true (*TODO*) } + if max < a[i] then max <- a[i]; + done; + max + +end diff --git a/assets/why3/pascal.mlw b/assets/why3/pascal.mlw new file mode 100644 index 0000000..7b838ca --- /dev/null +++ b/assets/why3/pascal.mlw @@ -0,0 +1,32 @@ +module Pascal + use int.Int + use ref.Ref + use array.Array + + (* HINT: https://en.wikipedia.org/wiki/Pascal%27s_triangle *) + (* You should understand the Pascal's triangle first to find good invariants *) + let rec function comb (n k: int) : int + requires { 0 <= k <= n } + variant { n } + ensures { result >= 1 } + = if k = 0 || k = n then 1 else comb (n-1) k + comb (n-1) (k-1) + + (* Insert appropriate invariants so that Why3 can verify this function. *) + let chooses (n : int) : array int + requires { n > 0 } + ensures { forall i: int. + 0 <= i < length result -> result[i] = comb n i } + = + let ref row = Array.make 1 1 in + for r = 1 to n do + invariant { length row = r } + invariant { true (*TODO*) } + let new_row = Array.make (r+1) 1 in + for c = 1 to r-1 do + invariant { true (*TODO*) } + new_row[c] <- row[c-1] + row[c] + done; + row <- new_row + done; + row +end \ No newline at end of file diff --git a/assets/why3/ex4_two_way.mlw b/assets/why3/two_way.mlw similarity index 50% rename from assets/why3/ex4_two_way.mlw rename to assets/why3/two_way.mlw index bfbe00e..371ce71 100644 --- a/assets/why3/ex4_two_way.mlw +++ b/assets/why3/two_way.mlw @@ -1,22 +1,12 @@ (* Two Way Sort The following program sorts an array of Boolean values, with False a[i1] << a[i2] let two_way_sort (a: array bool) : unit - ensures { true } + ensures { sorted a } + ensures { permut_all (old a) a } = - label Init in let ref i = 0 in let ref j = length a - 1 in while i < j do - invariant { forall i1: int. 0 <= i1 < i -> a[i1] = False } - invariant { forall i2: int. j < i2 < length a -> a[i2] = True } invariant { 0 <= i /\ j < length a } + (* IMPORTANT: DON'T MODIFY THE ABOVE LINES *) + invariant { forall i1: int. 0 <= i1 < i + -> true (* TODO *) } + invariant { forall i2: int. j < i2 < length a + -> true (* TODO *) } + invariant { true (* TODO *) } + (* IMPORTANT: DON'T MODIFY THE BELOW LINES *) variant { j - i } if not a[i] then incr i diff --git a/scripts/grade-12.sh b/scripts/grade-12.sh index 6a87f98..3c1609e 100755 --- a/scripts/grade-12.sh +++ b/scripts/grade-12.sh @@ -24,7 +24,7 @@ run_linters || exit 1 for RUNNER in "${RUNNERS[@]}"; do echo "Running with $RUNNER..." - TESTS=("--lib assignment12_grade") + TESTS=("--lib assignment12") if [ $(run_tests) -ne 0 ]; then exit 1 fi diff --git a/src/assignments/assignment02.rs b/src/assignments/assignment02.rs index e0944eb..b747baf 100644 --- a/src/assignments/assignment02.rs +++ b/src/assignments/assignment02.rs @@ -135,6 +135,39 @@ pub fn fibonacci(n: u64) -> u64 { (FIBONACCI_MAT.power(n) * FIBONACCI_VEC).get_upper() } +/// 2x2 floating-point matrix of the following configuration: +/// +/// a, b +/// c, d +#[derive(Debug, Clone, Copy)] +pub struct FMat2 { + /// row 1, column 1 + pub a: f64, + /// row 1, column 2 + pub b: f64, + /// row 2, column 1 + pub c: f64, + /// row 2, column 2 + pub d: f64, +} + +impl FMat2 { + /// Returns the inverse of the given matrix. (We assume the given matrix is always invertible.) + /// Hint: https://www.cuemath.com/algebra/inverse-of-2x2-matrix/ + /// + /// # Example + /// + /// ``` + /// assert_eq!( + /// Mat2 { a: 1.0, b: 1.0, c: 2.0, d: 3.0 }.inverse(), + /// Mat2 { a: 3.0, b: -1.0, c: -2.0, d: 1.0} + /// ); + /// ``` + pub fn inverse(self) -> Self { + todo!() + } +} + /// Writes down the lyrics of "twelve days of christmas". /// /// Hint: Google the song title for lyrics and look at the test code for the expected result. diff --git a/src/assignments/assignment02_grade.rs b/src/assignments/assignment02_grade.rs index de75737..6fd72ac 100644 --- a/src/assignments/assignment02_grade.rs +++ b/src/assignments/assignment02_grade.rs @@ -149,6 +149,48 @@ mod test { assert_eq!(fibonacci(92), 12200160415121876738); } + // Equivalence between two floating-point matrices, as element-wise equivalence + use std::cmp::PartialEq; + impl PartialEq for FMat2 { + fn eq(&self, other: &FMat2) -> bool { + self.a == other.a && self.b == other.b && self.c == other.c && self.d == other.d + } + } + + #[test] + fn test_inverse() { + assert_eq!( + FMat2 { + a: 1.0, + b: 1.0, + c: 2.0, + d: 3.0 + } + .inverse(), + FMat2 { + a: 3.0, + b: -1.0, + c: -2.0, + d: 1.0 + } + ); + assert_eq!( + FMat2 { + a: 2.0, + b: 3.0, + c: 5.0, + d: 7.0 + } + .inverse(), + FMat2 { + a: -7.0, + b: 3.0, + c: 5.0, + d: -2.0 + } + ); + } + #[test] fn test_lyrics() { assert_eq!(twelve_days_of_christmas_lyrics(), LYRICS) diff --git a/src/assignments/assignment03.rs b/src/assignments/assignment03.rs index 68d22da..ce37c1f 100644 --- a/src/assignments/assignment03.rs +++ b/src/assignments/assignment03.rs @@ -1,12 +1,10 @@ -//! Assignment 3: Mastering common programming concepts (2/2). -//! -//! The primary goal of this assignment is to re-learn the common programming concepts in Rust, especially those in the Rust Book chapters 6, 7, 8, and 9. -//! Please make sure you're comfortable with the concepts to proceed on to the next assignments. +//! Assignment 3: Mastering common programming concepts (2/2) //! //! You should fill out the `todo!()` placeholders in such a way that `/scripts/grade-03.sh` works fine. //! See `assignment03_grade.rs` and `/scripts/grade-03.sh` for the test script. use std::collections::{HashMap, HashSet}; +use std::fmt; /// Day of week. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -166,3 +164,125 @@ pub fn piglatin(input: String) -> String { pub fn organize(commands: Vec) -> HashMap> { todo!() } + +/// Custom operator: `option_op_or(v1, v2, f)` +/// If neither `v1` nor `v2` is `Some`, returns `None`. +/// If exactly one is `Some`, returns the same `Some` value. +/// If both are `Some`, apply the values inside `Some` to `f` and wrap the resulting value inside `Some`. +/// +/// # Examples +/// +/// ``` +/// fn product(a: i32, b: i32) -> i32 { +/// a * b +/// } +/// +/// assert_eq!(option_op_or(None, None, product), None); +/// assert_eq!(option_op_or(Some(3), None, product), Some(3)); +/// assert_eq!(option_op_or(Some(3), Some(5), product), Some(15)); +/// ``` +pub fn option_op_or T>(v1: Option, v2: Option, f: F) -> Option { + todo!() +} + +/// Events in a text editor. +#[derive(Debug)] +pub enum TypeEvent { + /// A character is typed. + Type(char), + /// The last character is removed. + Backspace, + /// The whole string is copied to the clipboard. + Copy, + /// The string in the clipboard is appended. + Paste, +} + +/// Starting from an empty string and an empty clipboard, +/// processes the given `events` in order and returns the resulting string. +/// +/// See the test function `test_editor` for examples. +pub fn use_editor(events: Vec) -> String { + todo!() +} + +/// Parse the string as a shell command. +/// +/// Usually, a shell command is whitespace-separated array of strings. +/// ```text +/// cat file --> ["cat", "file"] +/// ``` +/// But sometimes, you may want to include whitespaces in each argument. +/// In that case, you can use quotes. +/// ```text +/// ls 'VirtualBox VMs' --> ["ls", 'VirtualBox VMs'] +/// ls VirtualBox' 'VMs --> ["ls", 'VirtualBox VMs'] +/// ``` +/// +/// For simplicity, you may assume that the string only contains alphanumeric characters, spaces +/// (" "), and single quotes ("'"). +/// +/// See `test_shell` for more examples. +pub fn parse_shell_command(command: &str) -> Vec { + todo!() +} + +/// Represents a JSON value. See https://en.wikipedia.org/wiki/JSON. +/// +/// For simplicity, you may assume that numbers are of type `i64`, and strings do not contain +/// special characters that need to be escaped (e.g. '"', '\n', ...). +#[derive(Debug, PartialEq, Eq)] +pub enum JsonValue { + /// null + Null, + /// true, false + Boolean(bool), + /// integers + Number(i64), + /// strings + String(String), + /// array of JSON values + Array(Vec), + /// objects + Object(HashMap), +} + +/// Parse a string into a JSON value. Returns `Err(())` if it contains syntax errors. +/// +/// See `test_json` for examples. +pub fn parse_json(json_string: &str) -> Result { + todo!() +} + +impl fmt::Display for JsonValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JsonValue::Null => write!(f, "null"), + JsonValue::Boolean(b) => write!(f, "{}", b), + JsonValue::Number(n) => write!(f, "{}", n), + JsonValue::String(s) => write!(f, "\"{}\"", s), + JsonValue::Array(arr) => { + write!(f, "[")?; + let mut iter = arr.iter(); + if let Some(item) = iter.next() { + write!(f, "{}", item)?; + for item in iter { + write!(f, ", {}", item)?; + } + } + write!(f, "]") + } + JsonValue::Object(obj) => { + write!(f, "{{")?; + let mut iter = obj.iter(); + if let Some((key, value)) = iter.next() { + write!(f, "\"{}\": {}", key, value)?; + for (key, value) in iter { + write!(f, ", \"{}\": {}", key, value)?; + } + } + write!(f, "}}") + } + } + } +} diff --git a/src/assignments/assignment03_grade.rs b/src/assignments/assignment03_grade.rs index f72f64b..04980eb 100644 --- a/src/assignments/assignment03_grade.rs +++ b/src/assignments/assignment03_grade.rs @@ -165,4 +165,162 @@ mod test { .into() ); } + + fn product(a: i32, b: i32) -> i32 { + a * b + } + + #[test] + fn test_option_op_or() { + assert_eq!(option_op_or(None, None, product), None); + assert_eq!(option_op_or(Some(3), None, product), Some(3)); + assert_eq!(option_op_or(None, Some(5), product), Some(5)); + assert_eq!(option_op_or(Some(3), Some(5), product), Some(15)); + } + + #[test] + fn test_editor() { + assert_eq!( + use_editor(vec![ + TypeEvent::Type('a'), + TypeEvent::Backspace, + TypeEvent::Backspace, + TypeEvent::Type('b'), + TypeEvent::Type('c') + ]), + "bc" + ); + + assert_eq!( + use_editor(vec![ + TypeEvent::Type('a'), + TypeEvent::Copy, + TypeEvent::Paste, + TypeEvent::Paste, + TypeEvent::Type('b'), + TypeEvent::Copy, + TypeEvent::Paste + ]), + "aaabaaab" + ); + + assert_eq!( + use_editor(vec![ + TypeEvent::Paste, // clipboard starts empty + TypeEvent::Type('a'), + TypeEvent::Type('n'), + TypeEvent::Copy, + TypeEvent::Backspace, + TypeEvent::Backspace, + TypeEvent::Type('b'), + TypeEvent::Paste, + TypeEvent::Paste, + TypeEvent::Paste, + TypeEvent::Backspace + ]), + "banana" + ); + + assert_eq!( + use_editor(vec![ + TypeEvent::Copy, + TypeEvent::Backspace, + TypeEvent::Backspace, + TypeEvent::Paste, + TypeEvent::Paste, + TypeEvent::Copy, + TypeEvent::Backspace + ]), + "" + ); + } + + #[test] + fn test_shell() { + assert_eq!( + parse_shell_command("cat file"), + vec!["cat".to_string(), "file".to_string()] + ); + assert_eq!( + parse_shell_command("ls 'VirtualBox VMs'"), + vec!["ls".to_string(), "VirtualBox VMs".to_string()] + ); + assert_eq!( + parse_shell_command("ls VirtualBox' 'VMs"), + vec!["ls".to_string(), "VirtualBox VMs".to_string()] + ); + assert_eq!( + parse_shell_command("echo once upon a midnight dreary"), + vec![ + "echo".to_string(), + "once".to_string(), + "upon".to_string(), + "a".to_string(), + "midnight".to_string(), + "dreary".to_string(), + ] + ); + assert_eq!( + parse_shell_command("echo 'once upon a midnight dreary'"), + vec![ + "echo".to_string(), + "once upon a midnight dreary".to_string(), + ] + ); + } + + #[test] + fn test_json() { + use std::collections::HashMap; + let json_str = r#"{ + "name": "John Doe", + "age": 30, + "city": "New York", + "active": true, + "address": { + "street": "123 Main St", + "zipCode": "10001" + }, + "skills": ["Rust", "Python", "JavaScript"], + "organization": null + }"#; + println!("{}", parse_json(json_str).unwrap()); + assert_eq!( + parse_json(json_str), + Ok(JsonValue::Object(HashMap::from([ + ( + "name".to_string(), + JsonValue::String("John Doe".to_string()) + ), + ("age".to_string(), JsonValue::Number(30)), + ( + "city".to_string(), + JsonValue::String("New York".to_string()) + ), + ("active".to_string(), JsonValue::Boolean(true)), + ( + "address".to_string(), + JsonValue::Object(HashMap::from([ + ( + "street".to_string(), + JsonValue::String("123 Main St".to_string()) + ), + ( + "zipCode".to_string(), + JsonValue::String("10001".to_string()) + ) + ])) + ), + ( + "skills".to_string(), + JsonValue::Array(vec![ + JsonValue::String("Rust".to_string()), + JsonValue::String("Python".to_string()), + JsonValue::String("JavaScript".to_string()) + ]) + ), + ("organization".to_string(), JsonValue::Null), + ]))) + ); + } } diff --git a/src/assignments/assignment11/linked_list.rs b/src/assignments/assignment11/linked_list.rs index 704b6e9..161a7a4 100644 --- a/src/assignments/assignment11/linked_list.rs +++ b/src/assignments/assignment11/linked_list.rs @@ -1,8 +1,6 @@ //! Singly linked list. //! -//! Hint: Consult . -//! -//! Refer `linked_list_grade.rs` for test cases. +//! Consult . use std::fmt::Debug; @@ -61,4 +59,82 @@ impl SinglyLinkedList { pub fn pop_back(&mut self) -> Option { todo!() } + + /// Create a new list from the given vector `vec`. + pub fn from_vec(vec: Vec) -> Self { + todo!() + } + + /// Convert the current list into a vector. + pub fn as_vec(&self) -> Vec { + todo!() + } + + /// Return the length (i.e., number of nodes) of the list. + pub fn length(&self) -> usize { + todo!() + } + + /// Apply function `f` on every element of the list. + /// + /// # Examples + /// + /// `self`: `[1, 2]`, `f`: `|x| x + 1` ==> `[2, 3]` + pub fn map T>(&mut self, f: F) { + todo!() + } + + /// Insert given list `another` at the specified index `idx`. + /// If `idx` is out-of-bound of `self`, append `another` at the end of `self`. + /// + /// # Examples + /// + /// `self`: `[1, 2]`, `another`: `[3, 4]`, `idx`: `1` ==> `[1, 3, 4, 2]` + /// `self`: `[1, 2]`, `another`: `[3, 4]`, `idx`: `5` ==> `[1, 2, 3, 4]` + pub fn insert(&mut self, another: &Self, idx: usize) { + todo!() + } + + /// Reverse the list in a chunk of size `n`. + /// If `n == 0`, do nothing. + /// + /// # Examples + /// + /// `self`: `[1, 2, 3, 4, 5, 6, 7, 8, 9]`, `n`: `3` + /// // each chunk of size `3`: `[1, 2, 3]`, `[4, 5, 6]`, `[7, 8, 9]` + /// // reversed sequence of chunks: `[7, 8, 9]`, `[4, 5, 6]`, `[1, 2, 3]` + /// ==> `[7, 8, 9, 4, 5, 6, 1, 2, 3]`, + /// + /// `self`: `[1, 2, 3, 4, 5, 6, 7, 8, 9]`, `n`: `4` + /// // each chunk of size `4`: `[1, 2, 3, 4]`, `[5, 6, 7, 8]`, `[9]` + /// // reversed sequence of chunks: `[9]`, `[5, 6, 7, 8]`, `[1, 2, 3, 4]` + /// ==> `[9, 5, 6, 7, 8, 1, 2, 3, 4]` + pub fn chunk_reverse(&mut self, n: usize) { + todo!() + } + + /// Apply given function `f` for each adjacent pair of elements in the list. + /// If `self.length() < 2`, do nothing. + /// + /// # Examples + /// + /// `self`: `[1, 2, 3, 4]`, `f`: `|x, y| x + y` + /// // each adjacent pair of elements: `(1, 2)`, `(2, 3)`, `(3, 4)` + /// // apply `f` to each pair: `f(1, 2) == 3`, `f(2, 3) == 5`, `f(3, 4) == 7` + /// ==> `[3, 5, 7]` + pub fn pair_map T>(&mut self, f: F) { + todo!() + } +} + +// A list of lists. +impl SinglyLinkedList> { + /// Flatten the list of lists into a single list. + /// + /// # Examples + /// `self`: `[[1, 2, 3], [4, 5, 6], [7, 8]]` + /// ==> `[1, 2, 3, 4, 5, 6, 7, 8]` + pub fn flatten(self) -> SinglyLinkedList { + todo!() + } } diff --git a/src/assignments/assignment11/linked_list_grade.rs b/src/assignments/assignment11/linked_list_grade.rs index 2db6fe0..6805972 100644 --- a/src/assignments/assignment11/linked_list_grade.rs +++ b/src/assignments/assignment11/linked_list_grade.rs @@ -1,14 +1,12 @@ -//! Test cases for assignment11/linked_list.rs - #[cfg(test)] mod test_linked_list { - use super::super::linked_list::*; + use crate::assignments::assignment11::linked_list::*; #[derive(Debug, PartialEq, Eq)] struct V(usize); #[test] - fn test_linked_list() { + fn test_push_pop() { let mut list = SinglyLinkedList::new(); list.push_back(V(3)); list.push_front(V(2)); @@ -24,4 +22,101 @@ mod test_linked_list { assert_eq!(list.pop_back(), None); assert_eq!(list.pop_front(), None); } + + #[test] + fn test_from_as_vec() { + assert_eq!(SinglyLinkedList::::new().as_vec(), vec![]); + assert_eq!( + SinglyLinkedList::from_vec(vec![1, 2, 3]).as_vec(), + vec![1, 2, 3] + ); + } + + #[test] + fn test_length() { + let list = SinglyLinkedList::from_vec(vec![1, 2, 3]); + assert_eq!(list.length(), 3); + } + + #[test] + fn test_map() { + let mut list = SinglyLinkedList::from_vec(vec![1, 2, 3]); + let incr = |x: i32| x + 1; + list.map(incr); + assert_eq!(list.as_vec(), vec![2, 3, 4]); + } + + #[test] + fn test_insert() { + let mut list1 = SinglyLinkedList::from_vec(vec![1, 2, 3]); + let mut list2 = SinglyLinkedList::from_vec(vec![1, 2, 3]); + let mut list3 = SinglyLinkedList::from_vec(vec![1, 2, 3]); + let list4 = SinglyLinkedList::from_vec(vec![4, 5, 6]); + + list1.insert(&list4, 0); + assert_eq!(list1.as_vec(), vec![4, 5, 6, 1, 2, 3]); + + list2.insert(&list4, 1); + assert_eq!(list2.as_vec(), vec![1, 4, 5, 6, 2, 3]); + + list3.insert(&list4, 4); + assert_eq!(list3.as_vec(), vec![1, 2, 3, 4, 5, 6]); + } + + #[test] + fn test_chunk_reverse() { + let mut list1 = SinglyLinkedList::from_vec(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); + list1.chunk_reverse(3); + assert_eq!(list1.as_vec(), vec![7, 8, 9, 3, 4, 5, 1, 2, 3]); + + let mut list2 = SinglyLinkedList::from_vec(vec![1, 2, 3, 4, 5, 6, 7, 8]); + list2.chunk_reverse(3); + assert_eq!(list2.as_vec(), vec![7, 8, 4, 5, 6, 1, 2, 3]); + + let mut list3 = SinglyLinkedList::from_vec(vec![1, 2, 3]); + list3.chunk_reverse(4); + assert_eq!(list3.as_vec(), vec![1, 2, 3]); + + let mut list4 = SinglyLinkedList::from_vec(vec![1, 2, 3, 4]); + list4.chunk_reverse(1); + assert_eq!(list4.as_vec(), vec![4, 3, 2, 1]); + + let mut list5 = SinglyLinkedList::from_vec(vec![1, 2, 3, 4]); + list4.chunk_reverse(0); + assert_eq!(list4.as_vec(), vec![1, 2, 3, 4]); + } + + #[test] + fn test_pair_map() { + let mut list = SinglyLinkedList::from_vec(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); + let add = |x: i32, y: i32| x + y; + + list.pair_map(add); + assert_eq!(list.as_vec(), vec![3, 5, 7, 9, 11, 13, 15, 17]); + + list.pair_map(add); + assert_eq!(list.as_vec(), vec![8, 12, 16, 20, 24, 28, 32]); + + list.pair_map(add); + assert_eq!(list.as_vec(), vec![20, 28, 36, 44, 52, 60]); + + list.pair_map(add); + assert_eq!(list.as_vec(), vec![48, 64, 80, 96, 112]); + } + + #[test] + fn test_flatten() { + let list1 = SinglyLinkedList::from_vec(vec![1, 2]); + let list2 = SinglyLinkedList::from_vec(vec![3]); + let list3 = SinglyLinkedList::from_vec(vec![4, 5, 6, 7]); + let list4 = SinglyLinkedList::::new(); + let list5 = SinglyLinkedList::from_vec(vec![8, 9, 10]); + + let list_list = SinglyLinkedList::from_vec(vec![list1, list2, list3, list4, list5]); + + assert_eq!( + list_list.flatten().as_vec(), + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ); + } } diff --git a/src/assignments/assignment11/mod.rs b/src/assignments/assignment11/mod.rs index 266f7d7..53dacb5 100644 --- a/src/assignments/assignment11/mod.rs +++ b/src/assignments/assignment11/mod.rs @@ -4,16 +4,26 @@ //! See `assignment11_grade.rs` and `/scripts/grade-11.sh` for the test script. //! Run `/scripts/prepare-submissions.sh` and submit `/target/assignment11.zip` to . -pub mod bst; -pub mod bst_grade; -pub mod doubly_linked_list; -pub mod doubly_linked_list_grade; -pub mod graph; -pub mod graph_grade; pub mod linked_list; +mod linked_list_grade; + +pub mod peano_nat; +mod peano_nat_grade; + +pub mod bst; +mod bst_grade; + +pub mod doubly_linked_list; +mod doubly_linked_list_grade; + +pub mod graph; +mod graph_grade; + pub mod mock_storage; pub mod mock_storage_grade; -pub mod turing_machine; -pub mod turing_machine_grade; + pub mod tv_room; pub mod tv_room_grade; + +pub mod turing_machine; +pub mod turing_machine_grade; diff --git a/src/assignments/assignment11/peano_nat.rs b/src/assignments/assignment11/peano_nat.rs new file mode 100644 index 0000000..c79ab3c --- /dev/null +++ b/src/assignments/assignment11/peano_nat.rs @@ -0,0 +1,56 @@ +//! Peano natural number. + +/// We can represent any natural number using only two symbols: 0 and S. +/// +/// E.g. +/// O == 0 +/// S(O) == 1 +/// S(S(O)) == 2 +/// ... so on. +#[derive(Debug, Clone, PartialEq)] +pub enum Nat { + /// Zero + O, + /// Plus one + S(Box), +} + +impl Nat { + /// Create `Nat` from `usize` + pub fn from_usize(n: usize) -> Nat { + todo!() + } + + /// Convert `Nat` into nonnegative integer + pub fn as_usize(&self) -> usize { + todo!() + } +} + +// Implement `Add` operator (i.e. `+`) for `Nat`. +impl std::ops::Add for Nat { + type Output = Nat; + + fn add(self, rhs: Self) -> Self::Output { + todo!() + } +} + +// Implement `Sub` operator (i.e. `-`) for `Nat`. +// If the result is negative, return `Nat::O`. +impl std::ops::Sub for Nat { + type Output = Nat; + + fn sub(self, rhs: Self) -> Self::Output { + todo!() + } +} + +// Implement `Mul` operator (i.e. `*`) for `Nat`. +impl std::ops::Mul for Nat { + type Output = Nat; + + fn mul(self, rhs: Self) -> Self::Output { + todo!() + } +} diff --git a/src/assignments/assignment11/peano_nat_grade.rs b/src/assignments/assignment11/peano_nat_grade.rs new file mode 100644 index 0000000..edada7f --- /dev/null +++ b/src/assignments/assignment11/peano_nat_grade.rs @@ -0,0 +1,38 @@ +#[cfg(test)] +mod test_peano_nat { + use crate::assignments::assignment11::peano_nat::*; + + #[test] + fn test_from_as_usize() { + assert_eq!(Nat::from_usize(0), Nat::O); + assert_eq!( + Nat::from_usize(2), + Nat::S(Box::new(Nat::S(Box::new(Nat::O)))) + ); + + for n in 0..100 { + assert_eq!(Nat::from_usize(n).as_usize(), n); + } + } + + fn safe_sub(i: usize, j: usize) -> usize { + if i > j { + i - j + } else { + 0 + } + } + + #[test] + fn test_add_sub_mul() { + for i in 0..30 { + let n = Nat::from_usize(i); + for j in 0..30 { + let m = Nat::from_usize(j); + assert_eq!((n.clone() + m.clone()).as_usize(), i + j); + assert_eq!((n.clone() - m.clone()).as_usize(), safe_sub(i, j)); + assert_eq!((n.clone() * m.clone()).as_usize(), i * j); + } + } + } +}