-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/3 implement linked list #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a7b1ef1
99dc6c9
5cc75d8
a1a0916
4c8d639
13c6db1
024c9e4
b7d935a
e1fde3b
4fd81e6
8d3f3ee
433d66c
032dba1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [package] | ||
| name = "linked-list" | ||
| version = "0.1.0" | ||
| edition = "2024" | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| mod node; | ||
|
|
||
| #[cfg(test)] | ||
| mod linked_list_tests { | ||
| use crate::node::Node; | ||
|
|
||
| #[test] | ||
| fn trivial() { | ||
| let mut node = Node::new(1); | ||
| node.insert(2).insert(3).insert(4); | ||
| println!("{node}"); | ||
| assert_eq!(node.to_string(), "1,2,3,4"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn easy() { | ||
| let mut node = Node::new(42); | ||
| assert_eq!(**node, 42); | ||
| **node = 13; | ||
| assert_eq!(**node, 13); | ||
| } | ||
|
|
||
| #[test] | ||
| fn normal() { | ||
| let mut node1 = Node::new(1); | ||
| let node2 = node1.insert(3); // node2 keeps reference to just inserted value which is owned by node1 * | ||
| node2.insert(4); | ||
| node1.insert(2); // * and thus we can't swap this line with the previous one because of ref lifetimes | ||
| // We can't get mut ref for node1 while having active immutable ref | ||
| assert_eq!( | ||
| // No need to implement collect for my iter type - blanket impl would work just fine here | ||
| // | ||
| // Why use "copied"? | ||
| // "copied" is method from Iterator trait which returns iterator which produces copies of original iter items | ||
| // When we iterate using Copied iter we would do: self.it.next().copied(); | ||
| // "self.it" is our iter forward. We just call its next method -> return copied value | ||
| // | ||
| // "collect" - one another method from Iterator trait which is used to convert iter -> collection | ||
| // Again, we are good using blanket implementation | ||
| // "collect" will accept Copied iter and use it to create collection, which we specified to be <Vec<_>> | ||
| // <Vec<_>> - that is called "turbofish" and it helps Rust to figure out the collection type we want to get | ||
| // and thus find the right "collect" implementation. We don't specify concrete type of elements in the vector | ||
| // as Rust can compare <Vec<_>> with B returned from "collect" which is trait bounded to B: FromIterator<Self::Item>>. | ||
| // We see that it specifies Self::Item which is already known to the compiler as i32 (taken now from the Copied iter, our | ||
| // original iter features "&i32" | ||
| // | ||
| // Default impl of "collect" calls FromIterator::from_iter(self) | ||
| // Compiler will find "from_iter" implementation which returns turbofished type <Vec<i32>> and call it | ||
| node1.iter_forward().copied().collect::<Vec<_>>(), | ||
| vec![1, 2, 3, 4] | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn hard() { | ||
| let mut node1 = Node::new(1); | ||
| node1.insert(2).insert(3).insert(4); | ||
| let node2 = node1.clone(); | ||
| assert_eq!( | ||
| node1.iter_forward().collect::<Vec<_>>(), | ||
| node2.iter_forward().collect::<Vec<_>>(), | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn nightmare() { | ||
| // let a = 1; | ||
| // println!("Stack init ptr: {:p}", &a); | ||
| // I used this to print top address of the stack; I also added print in node's drop method | ||
| // First stack address was 0x7f4dc13fe47c and the last printed in drop method 0x7f4dc1200404 | ||
| // Diff (0x7f4dc13fe47c−0x7f4dc1200404) = 2_089_080 = ~2MB | ||
| // That is exactly the stack size of thread which run tests under Rust test runner | ||
| // Then we crashed due to stack overflow issue | ||
|
|
||
| let mut node = Node::new(1); | ||
|
|
||
| for index in 0..10_000_000 { | ||
| node.insert(index); | ||
| } | ||
| // We can survive while dropping exactly 16322 items (and stack overflows as it tries dropping 16_323th node) | ||
| // First I figured out that all the values are inserted without any problems - I saw prints for all values up to 10M | ||
| // Then I implemented custom drop, put print there and figured out that the last value dropped was 9_983_678 | ||
| // Nodes are stored in the following order (featuring their values below): | ||
| // 1 -> 10M-1 -> 10M-2 -> ... -> 2 -> 1 -> 0 | ||
| // Values are deleted from head and the last value deleted was node with value 9_983_678. | ||
| // That means once I have deleted 1 + (9_999_999 - 9_983_678) = 1 + 16321 = 16322 and tried deleting 16_323th node, the stack overflowed | ||
|
|
||
| // did it panic ?? | ||
| } | ||
|
|
||
| #[test] | ||
| fn ultra_nightmare() { | ||
| // let mut node = Node::new(1); | ||
| // node.insert(2).insert(3).insert(4); | ||
| // let last = node.iter_forward().last().unwrap(); | ||
| // assert_eq!(last.iter_bacwards().collect(), [4, 3, 2, 1]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_6_from_iter() { | ||
| let node = Node::from_iter([1, 2, 3, 4]); | ||
| assert_eq!(Vec::from_iter(node.into_iter()), [1, 2, 3, 4]); | ||
|
|
||
| let node = Node::from_iter(vec![1, 2, 3, 4]); | ||
| assert_eq!(Vec::from_iter(node.into_iter()), [1, 2, 3, 4]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_7_extend() { | ||
| let mut node = Node::new(1); | ||
| node.extend([2, 3]); | ||
| node.extend(vec![4, 5]); | ||
| assert_eq!(Vec::from_iter(node.into_iter()), [1, 2, 3, 4, 5]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_8_filter_fn() { | ||
| let node = Node::from_iter([1, 2, 3, 4]); | ||
| // this "node" shadowing makes no harm here as previous value is moved into "remove_if" method | ||
| let node = node.remove_if(|e| e % 2 == 0).unwrap(); | ||
| assert_eq!(Vec::from_iter(node.into_iter()), [1, 3]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_9_filter_fn_with_capture() { | ||
| let removed_value = "1".to_string(); | ||
| let node = Node::from_iter(["1", "2"]); | ||
| let node = node.remove_if(|e| *e == removed_value).unwrap(); | ||
| assert_eq!(Vec::from_iter(node.into_iter()), ["2"]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_10_filter_all() { | ||
| let node = Node::from_iter([1, 2, 3, 4]); | ||
| let node = node.remove_if(|_| true); | ||
| assert!(node.is_none()); | ||
| } | ||
| } | ||
|
|
||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| use std::clone; | ||
| use std::fmt::{Debug, Display}; | ||
| use std::io::Write; | ||
| use std::ops::{Deref, DerefMut}; | ||
|
|
||
| use crate::node; | ||
|
|
||
| pub mod node_forward_iter; | ||
| pub mod node_owner_iter; | ||
|
|
||
| #[derive(Debug)] | ||
| pub struct Node<T: Debug + Clone> { | ||
| value: T, | ||
| next: Option<Box<Node<T>>>, | ||
| } | ||
|
|
||
| impl<T: Debug + Clone> Node<T> { | ||
| /// creates new node with a value | ||
| pub fn new(value: T) -> Box<Self> { | ||
| Box::new(Node { | ||
| value, | ||
| next: Option::None, | ||
| }) | ||
| } | ||
|
|
||
| /// creates new node with a value and inserts it after this one | ||
| pub fn insert(&mut self, value: T) -> &mut Node<T> { | ||
| let Some(_) = &self.next else { | ||
| return self.next.insert(Node::new(value)); | ||
| }; | ||
|
|
||
| let mut new_node = Node::new(value); | ||
| new_node.next = self.next.take(); | ||
|
|
||
| // this "insert" can be confusing here but it is called for the Option | ||
| // so there it no recursion | ||
| self.next.insert(new_node); | ||
|
|
||
| // let a = 1; | ||
| // println!("{} a: {:p}", val, &a); // this value always has constant address and seems that stack doesn't move here | ||
| // ok, so this method actually executes till the end and then fails | ||
| // maybe it has something to do with drop?? | ||
|
|
||
| return self.next.as_deref_mut().unwrap(); | ||
| } | ||
|
|
||
| // Keep it as "iter_forward" to match "iter_backward". | ||
| // If "iter_backward" didn't exist, it should be named "iter". | ||
| pub fn iter_forward(&self) -> node_forward_iter::NodeForwardIter<'_, T> { | ||
| node_forward_iter::NodeForwardIter { | ||
| current: Some(self), | ||
| } | ||
| } | ||
|
|
||
| pub fn remove_if<F>(self, mut closure: F) -> Option<Self> | ||
| where | ||
| F: Fn(&T) -> bool, // changed from "FnMut" -> "Fn"; seems to be saficient here | ||
| { | ||
| let mut head: Option<Self> = None; | ||
| let mut prev_node: Option<&mut Self> = None; | ||
| for value in self.into_iter() { | ||
| if !closure(&value) { | ||
| // get head for the new list | ||
| let Some(prev_node_unwrapped) = prev_node else { | ||
| head = Some(Node { value, next: None }); | ||
| prev_node = head.as_mut(); | ||
| continue; | ||
| }; | ||
|
|
||
| // insert nodes which satisfies the condition | ||
| prev_node_unwrapped.next = Some(Box::new(Node { value, next: None })); | ||
| prev_node = prev_node_unwrapped.next.as_deref_mut(); | ||
| } | ||
| } | ||
|
|
||
| head | ||
| } | ||
| } | ||
|
|
||
| impl<T: Debug + Display + Clone> Display for Node<T> { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| let mut iter = self.iter_forward(); | ||
|
|
||
| if let Some(value) = iter.next() { | ||
| write!(f, "{}", value)?; | ||
| while let Some(value) = iter.next() { | ||
| write!(f, ",{}", value)?; | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| // why does specifying Display + Clone for "T" throws compiler error?? | ||
| // because we can't specialize "Drop" impl for the type - it MUST always be single implementation | ||
| impl<T: Debug + Clone> Drop for Node<T> { | ||
| fn drop(&mut self) { | ||
| // println!("Dropping {:?}", self.value); // last value dropped here was 9_983_678 | ||
|
|
||
| // let a = 1; | ||
| // println!("{:p}", &a); <-- shows that stack increases as drop is called recursively | ||
|
|
||
| // do I need to clean it from the end?? | ||
| // then it wouldn't go into infinite recursion? nope I guess.. | ||
| // actually I would use list above then I wouldn't have a problem that I need to implement common dropper | ||
| // as list would be cleaned by itearting over nodes and deleting just one node at a time | ||
| // here node is itself a list, kind of.. | ||
|
|
||
| let mut preserved_node = self.next.take(); | ||
| // drop(self); | ||
| // ^^^ we don't need this; first orphaned node will be dropped automatically at the end of this method | ||
| while let Some(mut next_node) = preserved_node { | ||
| preserved_node = next_node.next.take(); // orphan "next_node", preserved_node keeps reference to the next node | ||
| // drop(next_node); // will automatically be dropped here; next_node is newly created on each iteration | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<T: Debug + Clone> Deref for Node<T> { | ||
| type Target = T; | ||
|
|
||
| fn deref(&self) -> &Self::Target { | ||
| &self.value | ||
| } | ||
| } | ||
|
|
||
| impl<T: Debug + Clone> DerefMut for Node<T> { | ||
| fn deref_mut(&mut self) -> &mut Self::Target { | ||
| &mut self.value | ||
| } | ||
| } | ||
|
|
||
| // "T" MUST implement Clone trait as we try cloning value here which is of type "T" | ||
| impl<T: Clone + Display + Debug> Clone for Node<T> { | ||
| fn clone(&self) -> Self { | ||
| // use clone on the value as we don't want to move it and most certainly it won't be copiable | ||
| let mut cloned_head_node = Node::new(self.value.clone()); | ||
|
|
||
| // start looping from the second node as we already copied the first just above ^^^ | ||
| // "skip" returns new iterator which skips first N elements (1 in this case) | ||
| // on the first call to "next" it will return value of (N + 1) element or None | ||
| // let mut iter = self.iter_forward().skip(1); | ||
| let mut last_node_ref = &mut *cloned_head_node; | ||
| for value in self.iter_forward().skip(1) { | ||
| last_node_ref = last_node_ref.insert(value.clone()); | ||
| } | ||
|
|
||
| // data will be moved from heap to stack | ||
| *cloned_head_node | ||
| } | ||
| } | ||
|
|
||
| /* There are three common methods which can create iterators from a collection: | ||
| * iter() - iterates over &T. | ||
| * iter_mut() - iterates over &mut T. | ||
| * into_iter() - iterates over T. This method is specifically used to convert a collection into an iterator by moving its ownership. */ | ||
| impl<T: Clone + Debug> IntoIterator for Node<T> { | ||
| type Item = T; | ||
| type IntoIter = node_owner_iter::NodeOwnerIter<Self::Item>; | ||
|
|
||
| fn into_iter(self) -> Self::IntoIter { | ||
| return node_owner_iter::NodeOwnerIter { | ||
| current: Some(self), | ||
| }; | ||
| // self.iter_forward() | ||
| } | ||
| } | ||
|
|
||
| impl<T: Clone + Debug> FromIterator<T> for Node<T> { | ||
| fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { | ||
| // We can also accept a collection here - that is why "I" MUST implement IntoIterator | ||
| let mut iter_i_swear = iter.into_iter(); | ||
|
|
||
| // TODO: is that the right behaviour to panic if Iter is empty?? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there any reason it should logically panic? why extending collection with empty collection is an error? |
||
| let mut from_iter_list = Node::new(iter_i_swear.next().expect("Iter is empty")); | ||
| let mut insert_position = &mut *from_iter_list; | ||
| for value in iter_i_swear { | ||
| insert_position = insert_position.insert(value); | ||
| } | ||
|
|
||
| *from_iter_list | ||
| } | ||
| } | ||
|
|
||
| impl<T: Clone + Debug> Extend<T> for Node<T> { | ||
| fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) { | ||
| /* All commented code below fails test as it tries to extend list | ||
| * from within while we should actually get to the end of it first | ||
| * and then extend it. | ||
| * However, it was pretty useful in terms of learning different Rust syntax and | ||
| * getting to know about Rust's NLL BC limitation. See below for more. | ||
| */ | ||
| // let mut collected_nodes = Node::from_iter(iter.into_iter()); | ||
| // let Some(next_node) = self.next.take() else { | ||
| // self.next = Some(Box::new(collected_nodes)); | ||
| // return; | ||
| // }; | ||
|
|
||
| // let mut cur_node = &mut collected_nodes; | ||
| // loop { | ||
| // // let Some(last_node) = &mut cur_node.next else { | ||
|
|
||
| // // Above line would fail compilation. | ||
| // // Though in both cases "last_node" has the same type "&mut Box<Node<T>>", | ||
| // // in first case we actually do "ref-match" which is same as borrowing whole "cur_node" | ||
| // // while in second case we do "match-ref" which means borrowing "cur_node.next" specifically. | ||
| // // This way, in the first case we would end up with compilation error, stating that we can't assign | ||
| // // to "cur_node" which was already borrowed. | ||
| // // That is limitation of current NLL borrow checker (BC), see: | ||
| // // https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md#problem-case-4-mutating-mut-references | ||
| // let Some(ref mut last_node) = cur_node.next else { | ||
| // break; | ||
| // }; | ||
| // // cur_node = last_node.deref_mut(); // <--- this line is identical to the below | ||
| // cur_node = &mut *last_node; | ||
| // // Same as | ||
| // // cur_node = last_node.deref() | ||
| // // is identical to | ||
| // // cur_node = *last_node; | ||
| // } | ||
|
|
||
| // cur_node.next = Some(next_node); | ||
| // self.next = Some(Box::new(collected_nodes)); | ||
|
|
||
| // >>>>>>> Right solution which gets to the end of the list and then extend it | ||
| let mut cur_node = self; | ||
| while let Some(ref mut last_node) = cur_node.next { | ||
| cur_node = last_node.deref_mut(); | ||
| } | ||
|
|
||
| // Q: What actually happens here when Box::new receives Node from "from_iter" method?? | ||
| cur_node.next = Some(Box::new(Node::from_iter(iter.into_iter()))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is sufficient here but usually libraries expose
FnMutjust in case called needs to modify something inside the closure. you can checkiterator.filter(...)(or similar) as an example.