From b0ee36a9ff9fd3a77492934ee1f59ec41bc57074 Mon Sep 17 00:00:00 2001 From: woojin Date: Fri, 18 Aug 2023 22:10:35 +0900 Subject: [PATCH] add 8, 11, 12 --- Cargo.lock | 267 +++++++++++---- Cargo.toml | 5 + scripts/grade-11.sh | 2 +- scripts/grade-12.sh | 2 +- src/assignments/assignment08.rs | 27 +- src/assignments/assignment08_grade.rs | 17 + src/assignments/assignment11/bst.rs | 202 ++++++++++++ src/assignments/assignment11/bst_grade.rs | 53 +++ .../assignment11/doubly_linked_list.rs | 101 ++++++ .../assignment11/doubly_linked_list_grade.rs | 43 +++ src/assignments/assignment11/graph.rs | 93 ++++++ src/assignments/assignment11/graph_grade.rs | 63 ++++ src/assignments/assignment11/linked_list.rs | 4 +- .../assignment11/linked_list_grade.rs | 27 ++ src/assignments/assignment11/mock_storage.rs | 4 +- .../assignment11/mock_storage_grade.rs | 31 ++ src/assignments/assignment11/mod.rs | 10 + .../assignment11/turing_machine.rs | 139 ++++++++ .../assignment11/turing_machine_grade.rs | 309 ++++++++++++++++++ src/assignments/assignment11/tv_room.rs | 2 + src/assignments/assignment11/tv_room_grade.rs | 32 ++ src/assignments/assignment12/card.rs | 52 +++ src/assignments/assignment12/card_grade.rs | 141 ++++++++ src/assignments/assignment12/demux.rs | 37 +++ src/assignments/assignment12/demux_grade.rs | 36 ++ src/assignments/assignment12/funnel.rs | 21 ++ src/assignments/assignment12/funnel_grade.rs | 33 ++ src/assignments/assignment12/mod.rs | 15 + .../small_exercises.rs} | 14 +- .../assignment12/small_exercises_grade.rs | 86 +++++ src/assignments/mod.rs | 2 - 31 files changed, 1797 insertions(+), 73 deletions(-) create mode 100644 src/assignments/assignment11/bst.rs create mode 100644 src/assignments/assignment11/bst_grade.rs create mode 100644 src/assignments/assignment11/doubly_linked_list.rs create mode 100644 src/assignments/assignment11/doubly_linked_list_grade.rs create mode 100644 src/assignments/assignment11/graph.rs create mode 100644 src/assignments/assignment11/graph_grade.rs create mode 100644 src/assignments/assignment11/linked_list_grade.rs create mode 100644 src/assignments/assignment11/mock_storage_grade.rs create mode 100644 src/assignments/assignment11/turing_machine.rs create mode 100644 src/assignments/assignment11/turing_machine_grade.rs create mode 100644 src/assignments/assignment11/tv_room_grade.rs create mode 100644 src/assignments/assignment12/card.rs create mode 100644 src/assignments/assignment12/card_grade.rs create mode 100644 src/assignments/assignment12/demux.rs create mode 100644 src/assignments/assignment12/demux_grade.rs create mode 100644 src/assignments/assignment12/funnel.rs create mode 100644 src/assignments/assignment12/funnel_grade.rs create mode 100644 src/assignments/assignment12/mod.rs rename src/assignments/{assignment12.rs => assignment12/small_exercises.rs} (60%) create mode 100644 src/assignments/assignment12/small_exercises_grade.rs diff --git a/Cargo.lock b/Cargo.lock index 7132052..e95c3d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,16 @@ version = 3 name = "anyhow" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] [[package]] name = "autocfg" @@ -47,11 +56,21 @@ version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim", "termcolor", ] @@ -145,14 +164,19 @@ name = "cs220" version = "0.1.0" dependencies = [ "anyhow", + "approx", "clap", "etrace", "itertools", "lazy_static", + "ndarray", + "ndarray-rand", "ntest", + "num-traits", "pest", "pest_derive", "rayon", + "thiserror", ] [[package]] @@ -209,10 +233,27 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.4.0" +name = "getrandom" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -225,11 +266,18 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "libc", + "autocfg", + "hashbrown", ] [[package]] @@ -273,13 +321,35 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "libc" version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "linux-raw-sys" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "matrixmultiply" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -290,6 +360,30 @@ dependencies = [ "autocfg", ] +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "ndarray-rand" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65608f937acc725f5b164dcf40f4f0bc5d67dc268ab8a649d3002606718c4588" +dependencies = [ + "ndarray", + "rand", + "rand_distr", +] + [[package]] name = "ntest" version = "0.9.0" @@ -320,7 +414,36 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", + "libm", ] [[package]] @@ -335,15 +458,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" - -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "pest" @@ -389,6 +506,12 @@ dependencies = [ "sha1", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-crate" version = "1.2.1" @@ -442,6 +565,52 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.6.0" @@ -486,16 +655,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "serde" -version = "1.0.149" +name = "sha2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" - -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", @@ -582,41 +745,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "winapi" -version = "0.3.9" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", diff --git a/Cargo.toml b/Cargo.toml index 280b42e..543cac1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,9 @@ lazy_static = "1.4.0" pest = "2.5.1" pest_derive = "2.5.1" rayon = "1.6.0" +thiserror = "1.0" ntest = "0.9.0" +approx = "0.5.1" +num-traits = "0.2" +ndarray = "0.15.0" +ndarray-rand = "0.14.0" diff --git a/scripts/grade-11.sh b/scripts/grade-11.sh index 8666182..34fb51a 100755 --- a/scripts/grade-11.sh +++ b/scripts/grade-11.sh @@ -24,7 +24,7 @@ run_linters || exit 1 for RUNNER in "${RUNNERS[@]}"; do echo "Running with $RUNNER..." - TESTS=("--lib assignment11_grade") + TESTS=("--lib assignment11") if [ $(run_tests) -ne 0 ]; then exit 1 fi diff --git a/scripts/grade-12.sh b/scripts/grade-12.sh index 6a87f98..34fb51a 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 assignment11") if [ $(run_tests) -ne 0 ]; then exit 1 fi diff --git a/src/assignments/assignment08.rs b/src/assignments/assignment08.rs index 972b1d3..d6ce58d 100644 --- a/src/assignments/assignment08.rs +++ b/src/assignments/assignment08.rs @@ -5,22 +5,45 @@ //! You should fill out the `todo!()` placeholders in such a way that `/scripts/grade-08.sh` works fine. //! See `assignment08_grade.rs` and `/scripts/grade-08.sh` for the test script. -/// Returns an anonymous function that applies the given function `f` for `n` times. +/// Repeat /// +/// Returns an anonymous function that applies the given function `f` for `n` times. /// For instance, `repeat(3, f)(x)` roughly translates to `f(f(f(x)))`. +/// +/// Refer `test_repeat` in `assignment08_grade.rs` for detailed examples. pub fn repeat T>(n: usize, mut f: F) -> impl FnMut(T) -> T { todo!(); f // This line has been added to prevent compile error. You can erase this line. } +/// Funny Map +/// /// Applies the given function `f` for `i` times for the `i`-th element of the given vector. /// /// For instance, `funny_map(f, [v0, v1, v2, v3])` roughly translates to `[v0, f(v1), f(f(v2)), f(f(f(v3)))]`. +/// +/// Refer `test_funny_map` in `assignment08_grade.rs` for detailed examples. pub fn funny_map T>(f: F, vs: Vec) -> Vec { todo!() } +/// Count Repeat +/// +/// Returns the number of the elements of the set +/// {x, f(x), f(f(x)), f(f(f(x))), ...}. +/// You may assume that the answer is finite and small enough. +/// +/// Refer `test_count_repeat` in `assignment08_grade.rs` for detailed examples. +pub fn count_repeat T>(f: F, x: T) -> usize +where + T: PartialEq + Copy, +{ + todo!() +} + /// Either `T1`, or `T2`. +/// +/// Fill out `map` method for this type. #[derive(Debug, PartialEq, Eq)] pub enum Either2 { /// Case 1. @@ -39,6 +62,8 @@ impl Either2 { /// Maps the inner value. /// /// If the inner value is case 1, apply `f1`, and if it is case 2, apply `f2`. + /// + /// Refer `test_either2_map` in `assignment08_grade.rs` for detailed examples. pub fn map(self, f1: F1, f2: F2) -> Either2 where F1: FnOnce(T1) -> U1, diff --git a/src/assignments/assignment08_grade.rs b/src/assignments/assignment08_grade.rs index e6299ed..bce3398 100644 --- a/src/assignments/assignment08_grade.rs +++ b/src/assignments/assignment08_grade.rs @@ -27,4 +27,21 @@ mod test { let u2 = Either2::::Case2 { inner: 43.0 }; assert_eq!(u2, v2.map(|i| i + 1, |f| f + 1.0)); } + + #[test] + fn test_count_repeat() { + let inc_mod_100 = |x| (x + 1) % 100; + assert_eq!(count_repeat(inc_mod_100, 10), 100); + assert_eq!(count_repeat(inc_mod_100, 12345), 101); + + let p_lookup = |n| vec![1, 0, 2, 4, 5, 6, 7, 3][n]; + assert_eq!(count_repeat(p_lookup, 0), 2); + assert_eq!(count_repeat(p_lookup, 1), 2); + assert_eq!(count_repeat(p_lookup, 2), 1); + assert_eq!(count_repeat(p_lookup, 3), 5); + assert_eq!(count_repeat(p_lookup, 4), 5); + assert_eq!(count_repeat(p_lookup, 5), 5); + assert_eq!(count_repeat(p_lookup, 6), 5); + assert_eq!(count_repeat(p_lookup, 7), 5); + } } diff --git a/src/assignments/assignment11/bst.rs b/src/assignments/assignment11/bst.rs new file mode 100644 index 0000000..8d78a57 --- /dev/null +++ b/src/assignments/assignment11/bst.rs @@ -0,0 +1,202 @@ +//! Binary Search Tree +//! +//! Refer `bst_grade.rs` for test cases. + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::fmt::Debug; +use std::ops::Deref; +use std::rc::{Rc, Weak}; + +/// Node struct of tree +#[derive(Debug, Clone)] +struct Node +where + T: Ord, +{ + value: T, + parent: Option>>>, + left: Option>>>, + right: Option>>>, +} + +impl Node +where + T: Ord, +{ + fn new(value: T) -> Rc>> { + Rc::new(RefCell::new(Node { + value, + parent: None, + left: None, + right: None, + })) + } + + fn with_parent(value: T, parent: Weak>>) -> Rc>> { + Rc::new(RefCell::new(Node { + value, + parent: Some(parent), + left: None, + right: None, + })) + } + + /// Minimum node starting from cursor + fn min_node(mut cursor: Rc>>) -> Rc>> { + todo!(); + } + + /// Upgraded parent node. + /// `None` if the node has no parent. + fn parent(&self) -> Option>>> { + self.parent.as_ref().and_then(|p| p.upgrade()) + } +} + +/// Binary Search Tree +#[derive(Debug)] +pub struct Tree +where + T: Ord, +{ + root: Option>>>, + len: usize, +} + +impl Default for Tree { + fn default() -> Self { + Self::new() + } +} + +impl Tree +where + T: Ord, +{ + /// New tree + pub fn new() -> Tree { + Tree { root: None, len: 0 } + } + + /// Length of the tree + pub fn len(&self) -> usize { + self.len + } + + /// Check if the tree is empty. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Check if the tree contains the value. + pub fn contains(&self, value: &T) -> bool { + if let Some(mut cursor) = self.root.clone() { + todo!(); + } else { + false + } + } + + /// Insert the value into the tree. + /// If there was no equal value in the tree, it returns `true`. + /// Otherwise, it returns `false`. + pub fn insert(&mut self, value: T) -> bool { + self.len += 1; + if let Some(mut cursor) = self.root.clone() { + todo!(); + } else { + self.root = Some(Node::new(value)); + true + } + } + + /// Remove the node from the tree. + /// Returns the value of the removed node. + fn remove_node(&mut self, mut node: Rc>>) -> T { + todo!(); + } + + /// Remove the value from the tree. + /// If there is an equal value in the tree, it returns `true`. + /// Otherwise, it returns `false`. + pub fn remove(&mut self, value: &T) -> Option { + let res = if let Some(root) = self.root.clone() { + let mut cursor = root; + + loop { + let mut cursor_ref = cursor.deref().borrow_mut(); + let child = match value.cmp(&cursor_ref.value) { + Ordering::Less => cursor_ref.left.clone(), + Ordering::Greater => cursor_ref.right.clone(), + Ordering::Equal => { + drop(cursor_ref); + break Some(self.remove_node(cursor)); + } + }; + + if let Some(child) = child { + drop(cursor_ref); + cursor = child; + } else { + break None; + } + } + } else { + None + }; + + self.len -= res.is_some() as usize; + res + } +} + +impl Clone for Tree +where + T: Ord + Clone, +{ + fn clone(&self) -> Self { + Tree { + root: self.root.clone(), + len: self.len, + } + } +} + +/// IntoIterator for Tree +#[derive(Debug)] +pub struct IntoIter +where + T: Ord, +{ + tree: Tree, + len: usize, +} + +impl IntoIterator for Tree +where + T: Ord, +{ + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let len = self.len; + IntoIter { tree: self, len } + } +} + +impl Iterator for IntoIter +where + T: Ord, +{ + type Item = T; + + fn next(&mut self) -> Option { + todo!(); + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} diff --git a/src/assignments/assignment11/bst_grade.rs b/src/assignments/assignment11/bst_grade.rs new file mode 100644 index 0000000..40b82cb --- /dev/null +++ b/src/assignments/assignment11/bst_grade.rs @@ -0,0 +1,53 @@ +//! Test cases for assignment11/bst.rs + +#[cfg(test)] +mod test_bst { + use super::super::bst::*; + + #[test] + fn bst_insert_test() { + let mut tree = Tree::new(); + + let _ = tree.insert(1); + let _ = tree.insert(5); + let _ = tree.insert(3); + let _ = tree.insert(7); + + assert_eq!(tree.into_iter().collect::>(), vec![1, 3, 5, 7]); + } + + #[test] + fn bst_remove_test() { + let mut tree = Tree::new(); + + let _ = tree.insert(1); + let _ = tree.insert(5); + let _ = tree.insert(3); + let _ = tree.insert(7); + let _ = tree.remove(&7); + + assert_eq!(tree.into_iter().collect::>(), vec![1, 3, 5]); + } + + #[test] + fn bst_complex_test() { + let mut tree = Tree::new(); + + let _ = tree.insert(1); + let _ = tree.insert(5); + let _ = tree.insert(3); + let _ = tree.insert(7); + let _ = tree.remove(&7); + let _ = tree.insert(7); + let _ = tree.insert(6); + let _ = tree.insert(8); + let _ = tree.remove(&5); + let _ = tree.remove(&1); + let _ = tree.remove(&3); + let _ = tree.remove(&7); + let _ = tree.remove(&6); + let _ = tree.remove(&8); + + assert_eq!(tree.into_iter().collect::>(), vec![]); + } +} diff --git a/src/assignments/assignment11/doubly_linked_list.rs b/src/assignments/assignment11/doubly_linked_list.rs new file mode 100644 index 0000000..d3cc7bf --- /dev/null +++ b/src/assignments/assignment11/doubly_linked_list.rs @@ -0,0 +1,101 @@ +//! Doubly Linked List. +//! +//! Refer `doubly_linked_list_grade.rs` for test cases. + +use std::{cell::RefCell, fmt::Debug, rc::Rc}; + +type Link = Option>>>; + +/// Node of a doubly-linked list. +#[derive(Debug)] +pub struct Node { + /// Value of current node. + value: RefCell, + + /// Pointer to the next node. If it is `None`, there is no next node. + next: Link, + + /// Pointer to the previous node. If it is `None`, there is no previous node. + prev: Link, +} + +impl Node { + /// Creates a new node. + fn new(value: T) -> Self { + Self { + value: RefCell::new(value), + next: None, + prev: None, + } + } + + /// Fetch the value contained in node + pub fn get(&self) -> T { + self.value.borrow().clone() + } + + /// Replace the data contained in the node + pub fn replace(&self, new_value: T) -> T { + self.value.replace(new_value) + } + + /// Fetch previous node + pub fn prev(&self) -> Link { + self.prev.clone() + } + + /// Fetch next node + pub fn next(&self) -> Link { + self.next.clone() + } +} + +/// A doubly-linked list. +#[derive(Default, Debug)] +pub struct DoublyLinkedList { + /// Head node of the list. If it is `None`, the list is empty. + head: Link, + + /// Tail node of the list. If it is `None`, the list is empty. + tail: Link, +} + +impl DoublyLinkedList { + /// Creates a new list. + pub fn new() -> Self { + Self { + head: None, + tail: None, + } + } + + /// Adds the given node to the front of the list. + pub fn push_front(&mut self, value: T) { + todo!() + } + + /// Adds the given node to the back of the list. + pub fn push_back(&mut self, value: T) { + todo!() + } + + /// Removes and returns the node at the front of the list. + pub fn pop_front(&mut self) -> Option { + todo!() + } + + /// Removes and returns the node at the back of the list. + pub fn pop_back(&mut self) -> Option { + todo!() + } +} + +impl Drop for DoublyLinkedList { + fn drop(&mut self) { + while let Some(node) = self.head.take() { + let _ = node.borrow_mut().prev.take(); + self.head = node.borrow_mut().next.take(); + } + let _unused = self.tail.take(); + } +} diff --git a/src/assignments/assignment11/doubly_linked_list_grade.rs b/src/assignments/assignment11/doubly_linked_list_grade.rs new file mode 100644 index 0000000..0827f11 --- /dev/null +++ b/src/assignments/assignment11/doubly_linked_list_grade.rs @@ -0,0 +1,43 @@ +//! Test cases for assignment11/doubly_linked_list.rs + +#[cfg(test)] +mod test_doubly_linked_list { + use super::super::doubly_linked_list::*; + + #[test] + fn test_works() { + let mut list = DoublyLinkedList::new(); + + list.push_back(3); + list.push_back(4); + assert_eq!(list.pop_front(), Some(3)); + + list.push_front(5); + assert_eq!(list.pop_back(), Some(4)); + assert_eq!(list.pop_back(), Some(5)); + assert_eq!(list.pop_back(), None); + assert_eq!(list.pop_front(), None); + } + + #[test] + fn test_can_push_back() { + let mut list = DoublyLinkedList::new(); + assert_eq!(list.pop_back(), None); + + list.push_back(3); + list.push_back(4); + list.push_back(5); + assert_eq!(list.pop_back(), Some(5)); + + list.push_back(6); + list.push_back(7); + assert_eq!(list.pop_back(), Some(7)); + assert_eq!(list.pop_back(), Some(6)); + assert_eq!(list.pop_back(), Some(4)); + assert_eq!(list.pop_back(), Some(3)); + + list.push_back(2); + assert_eq!(list.pop_back(), Some(2)); + assert_eq!(list.pop_back(), None); + } +} diff --git a/src/assignments/assignment11/graph.rs b/src/assignments/assignment11/graph.rs new file mode 100644 index 0000000..65a92f3 --- /dev/null +++ b/src/assignments/assignment11/graph.rs @@ -0,0 +1,93 @@ +//! A small graph library. +//! +//! A node has a i32 value and (directed) edges to other nodes. A node does not have multiple edges +//! to the same node. Nodes are not associated with a particular domain, and users can freely +//! create nodes however they like. However, after a node is created, it can be added to a +//! `SubGraph`, which form a subgraph of the graph of all nodes. A node can be added to multiple +//! subgraphs. `SubGraph` has a method to check if the it has a cycle. +//! +//! The goal of this assignment is to learn how to deal with inherently shared mutable data in +//! Rust. Design the types and fill in the `todo!()`s in methods. There are several possible +//! approaches to this problem and you may import anything from the std library accordingly. +//! +//! Refer `graph_grade.rs` for test cases. + +#[derive(PartialEq, Eq, Debug)] +enum VisitStatus { + Unvisited, + Visiting, + Visited, +} + +/// Handle to a graph node. +/// `NodeHandle` should implement `Clone`, which clones the handle without cloning the underlying +/// node. That is, there can be multiple handles to the same node. +/// The user can access the node through a handle if it does not violate Rust's aliasing rules. +#[derive(Debug, Clone)] +pub struct NodeHandle; + +/// Error type for graph operations. +#[derive(Debug)] +pub struct GraphError; + +/// Subgraph +#[derive(Debug)] +pub struct SubGraph; + +impl NodeHandle { + /// Creates a node and returns the handle to it. + pub fn new(value: i32) -> Self { + todo!() + } + + /// Adds an edge to `to`. + /// If the modification cannot be done, e.g. because of aliasing issues, returns `Err(GraphError)`. + /// Returns `Ok(true)` if the edge is successfully added. + /// Returns `Ok(false)` if an edge to `to` already exits. + pub fn add_edge(&self, to: NodeHandle) -> Result { + todo!() + } + + /// Removes the edge to `to`. + /// If the modification cannot be done, e.g. because of aliasing issues, returns `Err(GraphError)`. + /// Returns `Ok(true)` if the edge is successfully removed. + /// Returns `Ok(false)` if an edge to `to` does not exist. + pub fn remove_edge(&self, to: &NodeHandle) -> Result { + todo!() + } + + /// Removes all edges. + /// If the modification cannot be done, e.g. because of aliasing issues, returns `Err(GraphError)`. + pub fn clear_edges(&self) -> Result<(), GraphError> { + todo!() + } +} + +impl Default for SubGraph { + fn default() -> Self { + Self::new() + } +} + +impl SubGraph { + /// Creates a new subgraph. + pub fn new() -> Self { + todo!() + } + + /// Adds a node to the subgraph. Returns true iff the node is newly added. + pub fn add_node(&mut self, node: NodeHandle) -> bool { + todo!() + } + + /// Adds a node to the subgraph. Returns true iff the node is successfully removed. + pub fn remove_node(&mut self, node: &NodeHandle) -> bool { + todo!() + } + + /// Returns true iff the subgraph contains a cycle. Nodes that do not belong to this subgraph + /// are ignored. See https://en.wikipedia.org/wiki/Cycle_(graph_theory) for an algorithm. + pub fn detect_cycle(&self) -> bool { + todo!() + } +} diff --git a/src/assignments/assignment11/graph_grade.rs b/src/assignments/assignment11/graph_grade.rs new file mode 100644 index 0000000..fcdac3b --- /dev/null +++ b/src/assignments/assignment11/graph_grade.rs @@ -0,0 +1,63 @@ +//! Test cases for assignment11/graph.rs + +#[cfg(test)] +mod test_graph { + use super::super::graph::*; + + #[test] + fn test_graph() { + let mut nodes = (0..6).map(NodeHandle::new).collect::>(); + let edges = [ + (0, 1), + (0, 3), + (1, 4), + (2, 4), + (2, 5), + (3, 1), + (4, 3), + (5, 5), + ]; + + for (from, to) in edges { + assert!(nodes[from].add_edge(nodes[to].clone()).unwrap()); + } + + let mut graph1 = SubGraph::new(); + for n in 0..6 { + assert!(graph1.add_node(nodes[n].clone())); + } + assert!(graph1.detect_cycle()); + assert!(!graph1.add_node(nodes[0].clone())); + + let mut graph2 = SubGraph::new(); + for n in [0, 1, 3] { + assert!(graph2.add_node(nodes[n].clone())); + } + assert!(!graph2.detect_cycle()); + + assert!(graph2.add_node(nodes[4].clone())); + assert!(graph2.detect_cycle()); + + assert!(nodes[4].remove_edge(&nodes[3]).unwrap()); + assert!(!graph2.detect_cycle()); + + let mut graph3 = SubGraph::new(); + for n in [0, 1, 2, 3] { + assert!(graph3.add_node(nodes[n].clone())); + } + assert!(!graph3.detect_cycle()); + + let more_edges = [(1, 2), (2, 3)]; + for (from, to) in more_edges { + assert!(nodes[from].add_edge(nodes[to].clone()).unwrap()); + } + assert!(graph3.detect_cycle()); + + assert!(graph3.remove_node(&nodes[2])); + assert!(!graph3.detect_cycle()); + + for n in nodes { + n.clear_edges().unwrap(); + } + } +} diff --git a/src/assignments/assignment11/linked_list.rs b/src/assignments/assignment11/linked_list.rs index 5d772e3..704b6e9 100644 --- a/src/assignments/assignment11/linked_list.rs +++ b/src/assignments/assignment11/linked_list.rs @@ -1,6 +1,8 @@ //! Singly linked list. //! -//! Consult . +//! Hint: Consult . +//! +//! Refer `linked_list_grade.rs` for test cases. use std::fmt::Debug; diff --git a/src/assignments/assignment11/linked_list_grade.rs b/src/assignments/assignment11/linked_list_grade.rs new file mode 100644 index 0000000..2db6fe0 --- /dev/null +++ b/src/assignments/assignment11/linked_list_grade.rs @@ -0,0 +1,27 @@ +//! Test cases for assignment11/linked_list.rs + +#[cfg(test)] +mod test_linked_list { + use super::super::linked_list::*; + + #[derive(Debug, PartialEq, Eq)] + struct V(usize); + + #[test] + fn test_linked_list() { + let mut list = SinglyLinkedList::new(); + list.push_back(V(3)); + list.push_front(V(2)); + list.push_back(V(4)); + list.push_front(V(1)); + list.push_back(V(5)); + + assert_eq!(list.pop_front(), Some(V(1))); + assert_eq!(list.pop_back(), Some(V(5))); + assert_eq!(list.pop_front(), Some(V(2))); + assert_eq!(list.pop_back(), Some(V(4))); + assert_eq!(list.pop_front(), Some(V(3))); + assert_eq!(list.pop_back(), None); + assert_eq!(list.pop_front(), None); + } +} diff --git a/src/assignments/assignment11/mock_storage.rs b/src/assignments/assignment11/mock_storage.rs index a5d9604..4e0edc2 100644 --- a/src/assignments/assignment11/mock_storage.rs +++ b/src/assignments/assignment11/mock_storage.rs @@ -1,6 +1,8 @@ //! Mock storage. //! -//! Consult . +//! Hint: Consult . +//! +//! Refer `mock_storage_grade.rs` for test cases. use std::cell::RefCell; use std::collections::HashMap; diff --git a/src/assignments/assignment11/mock_storage_grade.rs b/src/assignments/assignment11/mock_storage_grade.rs new file mode 100644 index 0000000..b8e62d3 --- /dev/null +++ b/src/assignments/assignment11/mock_storage_grade.rs @@ -0,0 +1,31 @@ +//! Test cases for assignment11/mock_storage.rs + +#[cfg(test)] +mod test_mock_storage { + use super::super::mock_storage::*; + + #[test] + fn test_mock_storage() { + let mock_storage = MockStorage::new(100); + + let uploader1 = FileUploader::new(&mock_storage); + let uploader2 = FileUploader::new(&mock_storage); + + let usage_analyzer = UsageAnalyzer::new(&mock_storage, 0.75); + + assert!(uploader1.upload("file1.txt", 20).is_ok()); + assert!(usage_analyzer.is_usage_under_bound()); + + assert!(uploader2.upload("file2.txt", 30).is_ok()); + assert!(usage_analyzer.is_usage_under_bound()); + + assert!(uploader1.upload("file3.txt", 40).is_ok()); + assert!(!usage_analyzer.is_usage_under_bound()); + + assert_eq!(uploader2.upload("file4.txt", 50), Err(40)); + assert!(!usage_analyzer.is_usage_under_bound()); + + assert!(uploader1.upload("file3.txt", 10).is_ok()); + assert!(usage_analyzer.is_usage_under_bound()); + } +} diff --git a/src/assignments/assignment11/mod.rs b/src/assignments/assignment11/mod.rs index 64b4553..266f7d7 100644 --- a/src/assignments/assignment11/mod.rs +++ b/src/assignments/assignment11/mod.rs @@ -4,6 +4,16 @@ //! 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; 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; diff --git a/src/assignments/assignment11/turing_machine.rs b/src/assignments/assignment11/turing_machine.rs new file mode 100644 index 0000000..438f30a --- /dev/null +++ b/src/assignments/assignment11/turing_machine.rs @@ -0,0 +1,139 @@ +//! Simple Turing machien emulator +//! +//! Simple One-head, One-tape turing machine +//! See that describes what turing machine is. +//! See `test_turing_machine` module in `assignment11_grade.rs` for examples. +//! +//! Goal: To be accustomed with `RefCell`, `HashMap` +//! +//! Refer `turing_machine.rs` for test cases. + +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::{self, Formatter}; +use thiserror::Error; + +/// Error type for Turing machine +/// +#[derive(Debug, Error, PartialEq, Eq)] +pub enum TuringMachineError { + /// Invalid movement + /// You can't move left from the leftmost location + /// or move right from the rightmost location. + #[error("Invalid movement")] + InvalidMovement, + + /// Exceeded maximum steps + #[error("Exceeded maximum steps")] + ExceedMaxSteps, + + /// Invalid state or value + /// Occurs when you cannot find instruction for the current state and value + #[error("Invalid state or value")] + InvalidStateOrValue, +} + +/// Turing Machine implementation +#[derive(Debug)] +pub struct TuringMachine +where + TMState: Default + Eq + PartialEq + std::hash::Hash + Clone, + TMValue: Eq + PartialEq + std::hash::Hash + Clone, +{ + /// Number of steps taken by the Turing machine + pub steps: RefCell, + + /// Table of instructions for the Turing machine + pub table: HashMap<(TMState, TMValue), (TMState, Move, TMValue)>, + + /// Tape of the Turing machine. Finite length + pub tape: Vec>, +} + +/// Implementation of the movement instructions of the head of the tape. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Move { + /// Move Left + L, + /// Move Right + R, + /// Don't move + N, +} + +/// Cursor for Turing machine +#[derive(Debug)] +pub struct Cursor<'a, TMState, TMValue> +where + TMState: Default + Eq + PartialEq + std::hash::Hash + Clone, + TMValue: Eq + PartialEq + std::hash::Hash + Clone, +{ + // Turing mahcine + tm: &'a TuringMachine, + // Index of the tape + index: usize, + // Current state of the Turing machine + state: TMState, +} + +impl<'a, TMState, TMValue> Cursor<'a, TMState, TMValue> +where + TMState: Default + Eq + PartialEq + std::hash::Hash + Clone, + TMValue: Eq + PartialEq + std::hash::Hash + Clone, +{ + /// Generate new cursor + pub fn new(tm: &'a TuringMachine, state: TMState, index: usize) -> Self { + Cursor { tm, index, state } + } + + /// Run the Turing machine until it halts (if it halts). Print every step of that. + pub fn run(&mut self, max_step: usize) -> Result<(TMValue, usize), TuringMachineError> { + let mut steps = self.tm.steps.borrow_mut(); + while self.state != TMState::default() { + *steps += 1; + if *steps > max_step { + return Err(TuringMachineError::ExceedMaxSteps); + } + self.step()?; + // println!("{}", self); + } + Ok((self.get(), *steps)) + } + + /// Set tape value at the current index with `value` + /// You may need this function for `mov` function + fn set(&mut self, value: TMValue) -> TMValue { + todo!(); + } + + /// Step the Turing machine + /// Look at the `run` function to see how this function is used. + fn step(&mut self) -> Result<(), TuringMachineError> { + todo!(); + } + + /// Move the cursor while setting the value of the current index + fn mov(&mut self, new_value: TMValue, movement: &Move) -> Result<(), TuringMachineError> { + todo!(); + } + + /// Get the value of the current index + /// Look at the `run` function to see how this function is used. + fn get(&self) -> TMValue { + todo!(); + } +} + +impl TuringMachine +where + TMState: Default + Eq + PartialEq + std::hash::Hash + Clone, + TMValue: Eq + PartialEq + std::hash::Hash + Clone, +{ + /// Generate new Turing machine + pub fn new( + table: HashMap<(TMState, TMValue), (TMState, Move, TMValue)>, + tape: Vec>, + ) -> Self { + todo!() + } +} diff --git a/src/assignments/assignment11/turing_machine_grade.rs b/src/assignments/assignment11/turing_machine_grade.rs new file mode 100644 index 0000000..89b617b --- /dev/null +++ b/src/assignments/assignment11/turing_machine_grade.rs @@ -0,0 +1,309 @@ +//! Test cases for assignment11/turing_machine.rs + +#[cfg(test)] +mod test_turing_machine { + use super::super::turing_machine::*; + use std::cell::RefCell; + use std::collections::HashMap; + + #[test] + fn test_invalid_movement() { + /// Cell value of the tape + #[derive(Debug, Clone, Hash, Eq, PartialEq)] + enum TMValue { + /// Zero + Zero, + + /// One + One, + } + + /// State for Turing machine + /// TODO: Modify this so that users can implement their own state + #[derive(Default, Debug, Eq, PartialEq, Hash, Clone)] + enum TMState { + /// Halt + #[default] + Halt, + + /// A + A, + + /// B + B, + } + + let tape: Vec> = vec![ + TMValue::One, + TMValue::Zero, + TMValue::One, + TMValue::One, + TMValue::One, + TMValue::Zero, + TMValue::One, + ] + .into_iter() + .map(|x| RefCell::new(x)) + .collect(); + + let instr = HashMap::from([ + ( + (TMState::A, TMValue::Zero), + (TMState::B, Move::R, TMValue::One), + ), + ( + (TMState::A, TMValue::One), + (TMState::B, Move::L, TMValue::Zero), + ), + ( + (TMState::B, TMValue::Zero), + (TMState::A, Move::L, TMValue::One), + ), + ( + (TMState::B, TMValue::One), + (TMState::A, Move::R, TMValue::Zero), + ), + ]); + + let mut tm = TuringMachine::new(instr, tape); + let mut cursor = Cursor::new(&tm, TMState::A, 0); + + let result = cursor.run(1000); + assert_eq!(result, Err(TuringMachineError::InvalidMovement)); + } + + #[test] + fn test_write_15_fail_move() { + /// Cell value of the tape + #[derive(Debug, Clone, Hash, Eq, PartialEq)] + enum TMValue { + /// Zero + Zero, + + /// One + One, + } + + /// State for Turing machine + /// TODO: Modify this so that users can implement their own state + #[derive(Default, Debug, Eq, PartialEq, Hash, Clone)] + enum TMState { + /// Halt + #[default] + Halt, + + /// A + A, + + /// B + B, + } + + let tape: Vec> = vec![TMValue::Zero; 10] + .into_iter() + .map(|x| RefCell::new(x)) + .collect(); + + let instr = HashMap::from([ + ( + (TMState::A, TMValue::Zero), + (TMState::B, Move::R, TMValue::One), + ), + ( + (TMState::A, TMValue::One), + (TMState::B, Move::L, TMValue::One), + ), + ( + (TMState::B, TMValue::Zero), + (TMState::A, Move::L, TMValue::One), + ), + ( + (TMState::B, TMValue::One), + (TMState::Halt, Move::R, TMValue::One), + ), + ]); + + let mut tm = TuringMachine::new(instr, tape); + let mut cursor = Cursor::new(&tm, TMState::A, 0); + + let result = cursor.run(1000); + assert_eq!(result, Err(TuringMachineError::InvalidMovement)); + } + + #[test] + fn test_write_15_pass() { + /// Cell value of the tape + #[derive(Debug, Clone, Hash, Eq, PartialEq)] + enum TMValue { + /// Zero + Zero, + + /// One + One, + } + + /// State for Turing machine + /// TODO: Modify this so that users can implement their own state + #[derive(Default, Debug, Eq, PartialEq, Hash, Clone)] + enum TMState { + /// Halt + #[default] + Halt, + + /// A + A, + + /// B + B, + } + + let tape: Vec> = vec![TMValue::Zero; 10] + .into_iter() + .map(|x| RefCell::new(x)) + .collect(); + + let instr = HashMap::from([ + ( + (TMState::A, TMValue::Zero), + (TMState::B, Move::R, TMValue::One), + ), + ( + (TMState::A, TMValue::One), + (TMState::B, Move::L, TMValue::One), + ), + ( + (TMState::B, TMValue::Zero), + (TMState::A, Move::L, TMValue::One), + ), + ( + (TMState::B, TMValue::One), + (TMState::Halt, Move::R, TMValue::One), + ), + ]); + + let mut tm = TuringMachine::new(instr, tape); + let mut cursor = Cursor::new(&tm, TMState::A, 2); + + let result = cursor.run(1000); + for (idx, val) in tm.tape.iter().enumerate() { + if idx < 4 { + assert_eq!(*val.borrow(), TMValue::One); + } else { + assert_eq!(*val.borrow(), TMValue::Zero); + } + } + } + + #[test] + fn test_write_zero_ones_fail_step() { + /// Cell value of the tape + #[derive(Debug, Clone, Hash, Eq, PartialEq)] + enum TMValue { + /// Empty + Empty, + + /// Zero + Zero, + + /// One + One, + } + + /// State for Turing machine + /// TODO: Modify this so that users can implement their own state + #[derive(Default, Debug, Eq, PartialEq, Hash, Clone)] + enum TMState { + /// Halt + #[default] + Halt, + + /// A + A, + + /// B + B, + } + + let tape: Vec> = vec![TMValue::Empty; 100] + .into_iter() + .map(|x| RefCell::new(x)) + .collect(); + + let instr = HashMap::from([ + ( + (TMState::A, TMValue::Empty), + (TMState::B, Move::R, TMValue::Zero), + ), + ( + (TMState::B, TMValue::Empty), + (TMState::A, Move::R, TMValue::One), + ), + ]); + + let mut tm = TuringMachine::new(instr, tape); + let mut cursor = Cursor::new(&tm, TMState::A, 0); + + let result = cursor.run(10); + assert_eq!(result, Err(TuringMachineError::ExceedMaxSteps)); + } + + #[test] + fn test_write_zero_ones() { + /// Cell value of the tape + #[derive(Debug, Clone, Hash, Eq, PartialEq)] + enum TMValue { + /// Empty + Empty, + + /// Zero + Zero, + + /// One + One, + } + + /// State for Turing machine + /// TODO: Modify this so that users can implement their own state + #[derive(Default, Debug, Eq, PartialEq, Hash, Clone)] + enum TMState { + /// Halt + #[default] + Halt, + + /// A + A, + + /// B + B, + } + + let tape: Vec> = vec![TMValue::Empty; 100] + .into_iter() + .map(|x| RefCell::new(x)) + .collect(); + + let instr = HashMap::from([ + ( + (TMState::A, TMValue::Empty), + (TMState::B, Move::R, TMValue::Zero), + ), + ( + (TMState::B, TMValue::Empty), + (TMState::A, Move::R, TMValue::One), + ), + ]); + + let mut tm = TuringMachine::new(instr, tape); + let mut cursor = Cursor::new(&tm, TMState::A, 0); + + let result = cursor.run(1000); + assert_eq!(result, Err(TuringMachineError::InvalidMovement)); + for (idx, val) in tm.tape.iter().enumerate() { + if idx % 2 == 0 { + assert_eq!(*val.borrow(), TMValue::Zero); + } else { + assert_eq!(*val.borrow(), TMValue::One); + } + } + } +} diff --git a/src/assignments/assignment11/tv_room.rs b/src/assignments/assignment11/tv_room.rs index a3cec53..1e9dbd0 100644 --- a/src/assignments/assignment11/tv_room.rs +++ b/src/assignments/assignment11/tv_room.rs @@ -15,6 +15,8 @@ //! Consult the following documentations: //! - //! - +//! +//! Refer `tv_room_grade.rs` for test cases. use std::{cell::RefCell, rc::Rc}; diff --git a/src/assignments/assignment11/tv_room_grade.rs b/src/assignments/assignment11/tv_room_grade.rs new file mode 100644 index 0000000..63495eb --- /dev/null +++ b/src/assignments/assignment11/tv_room_grade.rs @@ -0,0 +1,32 @@ +//! Test cases for assignment11/tv_room.rs + +#[cfg(test)] +mod test_tv_room { + use super::super::tv_room::*; + + #[test] + fn test_tv_room() { + let tv_room = TVRoom::new(); + assert!(!tv_room.is_opened()); + + // Turn on and add new guests. + let manager = tv_room.open().unwrap(); + assert!(tv_room.is_opened()); + let guest1 = manager.new_guest(); + let guest2 = manager.new_guest(); + drop(manager); + drop(guest1); + assert!(tv_room.open().is_none()); + drop(guest2); + assert!(!tv_room.is_opened()); + + // Turn on and add new guests. + let manager = tv_room.open().unwrap(); + assert!(tv_room.is_opened()); + let guest3 = manager.new_guest(); + drop(guest3); + assert!(tv_room.is_opened()); + drop(manager); + assert!(!tv_room.is_opened()); + } +} diff --git a/src/assignments/assignment12/card.rs b/src/assignments/assignment12/card.rs new file mode 100644 index 0000000..1a00da9 --- /dev/null +++ b/src/assignments/assignment12/card.rs @@ -0,0 +1,52 @@ +//! Flipping card game. +//! +//! For this assignment, you have to see `play` function in `card_grade.rs` file. +//! Multiple threads will be created and they will run as enemy bots(`bot_threads`). +//! Strategy of the enemy bots is implemented in the closure of the `thread::spawn` function. +//! Your goal is to beat them so that there are more white cards than blue cards in the ground. +//! Write your strategy in the `flip_card_strategy` function of the `Player` struct. +//! +//! Have fun! + +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +/// Color represents the color of the card. +/// The color of a card can be either Blue or White. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Color { + /// blue + Blue, + + /// white + White, +} + +/// Player struct represents a player in the card game. +/// Each player has a memory which is represented as a HashMap. +#[derive(Debug)] +pub struct Player { + memory: HashMap, +} + +impl Default for Player { + fn default() -> Self { + Self::new() + } +} + +impl Player { + /// Creates a new player with an empty memory. + pub fn new() -> Self { + Self { + memory: HashMap::new(), + } + } + + /// This function should return the index of the card to flip and the color to change to. + pub fn flip_card_strategy(&mut self) -> (usize, Color) { + todo!() + } +} diff --git a/src/assignments/assignment12/card_grade.rs b/src/assignments/assignment12/card_grade.rs new file mode 100644 index 0000000..4f3ec84 --- /dev/null +++ b/src/assignments/assignment12/card_grade.rs @@ -0,0 +1,141 @@ +//! Test cases for assignment12/card.rs + +#[cfg(test)] +mod test_card { + use super::super::card::*; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::{Arc, Barrier, Mutex}; + use std::thread; + use std::time::Duration; + + const NUM_CARDS: usize = 10_000; + const DURATION: u64 = 1; + const NUM_ENEMIES: usize = 100; + + #[derive(Clone, Debug)] + struct Card { + color: Arc>, + } + + impl Card { + fn new() -> Self { + Card { + color: Arc::new(Mutex::new(Color::Blue)), + } + } + + fn flip(&self, new_color: Color) { + let mut color = self.color.lock().unwrap(); + *color = new_color; + } + + fn get_color(&self) -> Color { + let color = self.color.lock().unwrap(); + *color + } + } + + #[derive(Clone, Debug)] + struct Ground { + cards: Vec, + } + + impl Ground { + fn new() -> Self { + let cards: Vec<_> = (0..NUM_CARDS).map(|_| Card::new()).collect(); + Ground { cards } + } + + fn flip_card(&self, idx: usize, color: Color) { + self.cards[idx % NUM_CARDS].flip(color); + } + + fn get_card_color(&self, idx: usize) -> Color { + self.cards[idx % NUM_CARDS].get_color() + } + } + + #[test] + fn play() { + let ground = Ground::new(); + let barrier = Arc::new(Barrier::new( + NUM_ENEMIES + 1 /*Player*/ + 1, /*Referee*/ + )); + let playing = Arc::new(AtomicBool::new(true)); + + // Create a thread for the student's strategy + let mut player = Player::new(); + let player_thread = { + let ground = ground.clone(); + let barrier = barrier.clone(); + let playing = playing.clone(); + + // The player's strategy thread + thread::spawn(move || { + // Get, Set, Ready, Go! + let _ = barrier.wait(); + + // As long as the game is still playing... + while playing.load(Ordering::SeqCst) { + let (idx, color) = player.flip_card_strategy(); + ground.flip_card(idx, color); + } + }) + }; + + // Create multiple threads for the computer's strategy + let dist = NUM_CARDS / NUM_ENEMIES; + let bot_threads: Vec<_> = (0..NUM_ENEMIES) + .map(|i| { + let ground = ground.clone(); + let barrier = barrier.clone(); + let playing = playing.clone(); + + let init = i * dist; + let mut cnt = 0; + + thread::spawn(move || { + // Get, Set, Ready, Go! + let _ = barrier.wait(); + + // As long as the game is still playing... + while playing.load(Ordering::SeqCst) { + let idx = init + (cnt % dist); + match ground.get_card_color(idx) { + Color::White => ground.flip_card(idx, Color::Blue), + Color::Blue => thread::sleep(Duration::from_micros(1)), + }; + cnt += 1; + } + }) + }) + .collect(); + + // Get, Set, Ready, Go! + let _ = barrier.wait(); + + // Wait for a while and stop the game + thread::sleep(Duration::from_secs(DURATION)); + playing.store(false, Ordering::SeqCst); + + // Wait for all threads to finish + player_thread.join().unwrap(); + for bot_thread in bot_threads { + bot_thread.join().unwrap(); + } + + // Count the number of white and blue cards + let mut white_cnt = 0; + let mut blue_cnt = 0; + for card in ground.cards { + match card.get_color() { + Color::White => white_cnt += 1, + Color::Blue => blue_cnt += 1, + } + } + + // Print the winner + println!("[White: {white_cnt}, Blue: {blue_cnt}]"); + assert!(white_cnt > blue_cnt, "You lose...",); + } +} diff --git a/src/assignments/assignment12/demux.rs b/src/assignments/assignment12/demux.rs new file mode 100644 index 0000000..2d98008 --- /dev/null +++ b/src/assignments/assignment12/demux.rs @@ -0,0 +1,37 @@ +//! Demultiplexing sender +//! +//! Implement demultiplexing sender +//! Demultiplexer, `Demux` in short, is a device that has one input and many outputs. +//! It distributes the input to the outputs according to the control signal. +//! It is used when a circuit wishes to send a signal to one of many devices. +//! For more information, refer +//! +//! In this assignment, closure `f` will be given as an argument to `demux` function. +//! This closure will be used to determine which destination to send the input data. +//! +//! Refer `demux_grade.rs` for test cases + +use std::sync::mpsc::{channel, Receiver, SendError, Sender}; +use std::thread; + +/// Sender for demux. +#[derive(Debug)] +pub struct DemuxSender bool> { + tx_true: Sender, + tx_false: Sender, + f: F, +} + +impl bool> DemuxSender { + /// send + /// + /// If `f(&value)` is true, send `value` to `tx_true`. Otherwise, send `value` to `tx_false`. + pub fn send(&self, value: T) -> Result<(), SendError> { + todo!() + } +} + +/// Demux. +pub fn demux bool>(f: F) -> (DemuxSender, Receiver, Receiver) { + todo!() +} diff --git a/src/assignments/assignment12/demux_grade.rs b/src/assignments/assignment12/demux_grade.rs new file mode 100644 index 0000000..66e11f7 --- /dev/null +++ b/src/assignments/assignment12/demux_grade.rs @@ -0,0 +1,36 @@ +//! Test cases for assignment12/demux.rs + +#[cfg(test)] +mod test_demux { + use super::super::demux::*; + use ntest::timeout; + + use std::sync::mpsc::channel; + use std::thread; + + #[test] + #[timeout(5000)] + fn test_demux() { + let (tx, rx1, rx2) = demux::(|x| x % 2 == 0); + + let thread_tx = thread::spawn(move || { + for i in 0..100 { + tx.send(i).unwrap(); + } + }); + + let thread_rx1 = thread::spawn(move || { + let sum: u32 = rx1.iter().sum(); + assert_eq!(sum, (0..100).filter(|x| x % 2 == 0).sum()); + }); + + let thread_rx2 = thread::spawn(move || { + let sum: u32 = rx2.iter().sum(); + assert_eq!(sum, (0..100).filter(|x| x % 2 != 0).sum()); + }); + + thread_tx.join().unwrap(); + thread_rx1.join().unwrap(); + thread_rx2.join().unwrap(); + } +} diff --git a/src/assignments/assignment12/funnel.rs b/src/assignments/assignment12/funnel.rs new file mode 100644 index 0000000..8b050e8 --- /dev/null +++ b/src/assignments/assignment12/funnel.rs @@ -0,0 +1,21 @@ +//! Funnel +//! +//! Spawn a thread that executes a funnel. +//! Funnel will receive data from multiple receivers and send it to a single sender. +//! Also, the funnel will filter out data that does not pass the filter function. +//! +//! Refer `funnel_grade.rs` for test cases + +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Arc; +use std::thread; +use std::thread::JoinHandle; + +/// Spawn a thread that concurrently receive datas from `rxs`, send it to `tx` if it makes `f` true. Returns its handle. +pub fn spawn_funnel(rxs: Vec>, tx: Sender, f: F) -> JoinHandle<()> +where + T: Send + 'static, + F: Send + Sync + Fn(&T) -> bool + 'static, +{ + todo!() +} diff --git a/src/assignments/assignment12/funnel_grade.rs b/src/assignments/assignment12/funnel_grade.rs new file mode 100644 index 0000000..8de4b99 --- /dev/null +++ b/src/assignments/assignment12/funnel_grade.rs @@ -0,0 +1,33 @@ +//! Test cases for assignment12/funnel.rs + +#[cfg(test)] +mod test_funnel { + use super::super::funnel::*; + use ntest::timeout; + + use std::sync::mpsc::channel; + use std::thread; + + #[test] + #[timeout(5000)] + fn test_funnel_concurrent() { + let (txs, rxs): (Vec<_>, Vec<_>) = (0..10).map(|_| channel::()).unzip(); + let (tx, rx) = channel::(); + let filter = |x: &u32| x % 2 == 0; + + let thread_txs_rx = thread::spawn(move || { + for i in 0..100 { + let idx = (i * 7) % 13 * 17 % 10; + txs[idx].send(i as u32).unwrap(); + if i % 2 == 0 { + let x = rx.recv().unwrap(); + assert_eq!(x, i as u32); + } + } + }); + let thread_funnel = spawn_funnel(rxs, tx, filter); + + thread_txs_rx.join().unwrap(); + thread_funnel.join().unwrap(); + } +} diff --git a/src/assignments/assignment12/mod.rs b/src/assignments/assignment12/mod.rs new file mode 100644 index 0000000..ae03552 --- /dev/null +++ b/src/assignments/assignment12/mod.rs @@ -0,0 +1,15 @@ +//! Assignment 12: Concurrency. +//! +//! The primary goal of this assignment is to get used to concurrency. +//! +//! You should fill out the `todo!()` placeholders in such a way that `/scripts/grade-12.sh` works fine. +//! See `assignment12_grade.rs` and `/scripts/grade-12.sh` for the test script. + +pub mod card; +pub mod card_grade; +pub mod demux; +pub mod demux_grade; +pub mod funnel; +pub mod funnel_grade; +pub mod small_exercises; +pub mod small_exercises_grade; diff --git a/src/assignments/assignment12.rs b/src/assignments/assignment12/small_exercises.rs similarity index 60% rename from src/assignments/assignment12.rs rename to src/assignments/assignment12/small_exercises.rs index 73d4d84..121b670 100644 --- a/src/assignments/assignment12.rs +++ b/src/assignments/assignment12/small_exercises.rs @@ -1,23 +1,25 @@ #![allow(single_use_lifetimes)] -//! Assignment 12: Concurrency. +//! Small exercises //! -//! The primary goal of this assignment is to get used to concurrency. -//! -//! You should fill out the `todo!()` placeholders in such a way that `/scripts/grade-12.sh` works fine. -//! See `assignment12_grade.rs` and `/scripts/grade-12.sh` for the test script. +//! Refer `small_exercises_grade.rs` for test cases use std::sync::mpsc::{Receiver, RecvError, Sender}; use std::thread; use etrace::*; -/// The "pong" function (read the test script to figure out what it should do). +/// The "pong" function +/// +/// Data will be sent and received through `rx` and `tx`. +/// Read the `test_ping_pong` function in `small_exercises_grade.rs` to figure out what it should do. pub fn pong(rx1: &mut Receiver, tx2: &mut Sender) -> bool { todo!() } /// Executes the given functions (f1, f2) in concurrent and returns the results. +/// +/// Read the `test_scoped_thread` function in `small_exercises_grade.rs` to figure out what it should do. pub fn use_scoped_thread<'scope, 'env, T1, T2, F1, F2>( s: &'scope thread::Scope<'scope, 'env>, f1: F1, diff --git a/src/assignments/assignment12/small_exercises_grade.rs b/src/assignments/assignment12/small_exercises_grade.rs new file mode 100644 index 0000000..e043ace --- /dev/null +++ b/src/assignments/assignment12/small_exercises_grade.rs @@ -0,0 +1,86 @@ +//! Test cases for assignment12/small_exercises_grade.rs + +#[cfg(test)] +mod test_pingpong { + use super::super::small_exercises::*; + use ntest::timeout; + + use std::sync::mpsc::channel; + use std::thread; + + #[test] + fn test_ping_pong() { + let (tx1, mut rx1) = channel(); + let (mut tx2, rx2) = channel(); + + let thread_ping = thread::spawn(move || { + for i in 0..100 { + tx1.send(i).unwrap(); + let x = rx2.recv().unwrap(); + assert_eq!(x, i + 1); + } + }); + + let thread_pong = thread::spawn(move || while pong(&mut rx1, &mut tx2) {}); + + thread_ping.join().unwrap(); + thread_pong.join().unwrap(); + } + + #[test] + fn test_scoped_thread() { + for i in 0..100 { + let v = (0..i).collect::>(); + + thread::scope(|s| { + let (r1, r2) = use_scoped_thread( + s, + || v.iter().sum::(), + || v.windows(2).map(|x| x[0] * x[1]).sum::(), + ); + + assert_eq!(r1, v.iter().sum()); + assert_eq!(r2, v.windows(2).map(|x| x[0] * x[1]).sum()); + }); + } + } + + #[test] + #[timeout(5000)] + fn test_scoped_thread_concurrent() { + use std::sync::Mutex; + + let m = Mutex::new(0); + let (r1, r2) = thread::scope(|s| { + use_scoped_thread( + s, + || { + for i in 0..100 { + loop { + let mut a = m.lock().unwrap(); + if *a == 2 * i { + *a += 1; + break; + } + } + } + thread::current().id() + }, + || { + for i in 0..100 { + loop { + let mut a = m.lock().unwrap(); + if *a == 2 * i + 1 { + *a += 1; + break; + } + } + } + thread::current().id() + }, + ) + }); + + assert!(r1 != r2); + } +} diff --git a/src/assignments/mod.rs b/src/assignments/mod.rs index 74e67fe..ff16701 100644 --- a/src/assignments/mod.rs +++ b/src/assignments/mod.rs @@ -25,8 +25,6 @@ mod assignment09_grade; pub mod assignment10; mod assignment10_grade; pub mod assignment11; -mod assignment11_grade; pub mod assignment12; -mod assignment12_grade; pub mod assignment13; mod assignment13_grade;