Because of cache locality issues, linked lists are a bad data structure for almost all purposes. A plain old vector, or maybe a VecDeque, wins for almost all workloads.
For the few times they are useful (e.g. if you're writing lock-free algorithms, you want a hard upper bound O(1) append rather than just amortized O(1) append, or you don't have malloc available), Rust is a fine language to write them in. But also there are plenty of high-quality libraries on crates.io, so you probably won't have to write your own.
The borrow checker makes a doublily linked list difficult, from my understanding.
They're almost always a bad data structure to pick though, as the speed difference between being able to iterate a contiguous list, and even completely rebuild it, vs having to follow arbitrary pointers mean that the 'complexity' improvements of a linked list often become irrelevant.
------
Open Question)
I have this intuition that the borrow checker might enforce a way of working that fundamentally stops you from doing things that aren't _really_ compatible with the way a CPU actually works on the lowest level.
Is there any credence to that, and if so, is it happenstance, or is there some more fundamental reason?
> I have this intuition that the borrow checker might enforce a way of working that fundamentally stops you from doing things that aren't _really_ compatible with the way a CPU actually works on the lowest level.
No, I think you are reading too much into it. One is free to Box-allocate/pointer-chase on every line in Rust without any trouble. Sure, as other low level languages, rust also prefers/makes stack-allocation more ergonomic/the default, which is a quite cache-friendly way of operation but not even the stack is “native” in and of itself - it is just a frequently and extensively used part of the “heap”.
Depends whether by "safe" you mean "not using unsafe" or "the implementation is safe". Many core data structures in Rust use unsafe, but I think most people would consider them safe implementations.
I think the Karatsuba implementation is kind of missing the point of the whole exercise by just leaning on i128’s methods. It should really be defining a big num type or doing all the work on strings etc.
It’s also going to explode if I put in a number with several thousand digits as it is unwrap()ing everywhere
So long as it’s never trying to parse a non-digit, the unwraps should be fine. Since it’s using i128s as arguments (and then converting them to strings to do the maths) that should be safe. For actual use, a proper bignum type for the arguments seems like it should be the biggest change needed though.
Depends on what you mean by "best". For e.g. comparison-based sorting will already cost O(n log n) time but the HashMap solution will run in O(n) HashMap operations.
If the caller wants to do something weird like converting the index to an i32 (which immediately means you can’t handle 2³¹ or more (~2 billion) items) or turning it into a zero-or-two-element Vec (which is inefficient and far easier to make mistakes with), they can do that themselves.
This speaks very clearly to me that either there is some unreasonable constraint in place that I don’t immediately see, or neither the author nor the reviewer was at all competent in Rust.
28 comments
[ 2.8 ms ] story [ 61.3 ms ] threadhttps://github.com/TheAlgorithms/Rust/search?q=unsafe
Almost there...
But with a doubly linked list, who owns the memory to each node?
In reality, you can do a doubly linked list with something like `RefCell`.
See the famous blog post on linked lists in rust here: https://rust-unofficial.github.io/too-many-lists/fourth-buil...
For the few times they are useful (e.g. if you're writing lock-free algorithms, you want a hard upper bound O(1) append rather than just amortized O(1) append, or you don't have malloc available), Rust is a fine language to write them in. But also there are plenty of high-quality libraries on crates.io, so you probably won't have to write your own.
They're almost always a bad data structure to pick though, as the speed difference between being able to iterate a contiguous list, and even completely rebuild it, vs having to follow arbitrary pointers mean that the 'complexity' improvements of a linked list often become irrelevant.
------ Open Question)
I have this intuition that the borrow checker might enforce a way of working that fundamentally stops you from doing things that aren't _really_ compatible with the way a CPU actually works on the lowest level.
Is there any credence to that, and if so, is it happenstance, or is there some more fundamental reason?
No, I think you are reading too much into it. One is free to Box-allocate/pointer-chase on every line in Rust without any trouble. Sure, as other low level languages, rust also prefers/makes stack-allocation more ergonomic/the default, which is a quite cache-friendly way of operation but not even the stack is “native” in and of itself - it is just a frequently and extensively used part of the “heap”.
It feels like a very early attempt. Skip the parent pointers and use Option rather than raw null pointers. Should get rid of the unsafe.
Those guarantees go away if you use "unsafe". In that case, you may just use C or C++.
Also - many of the borrow checker’s guarantees are enforced inside unsafe as well.
It’s also going to explode if I put in a number with several thousand digits as it is unwrap()ing everywhere
Surely the best way is to sort the numbers and then walk from both ends?
Nice work anyway!
Sorting an array is O(n*log(n))
Hashmap operations are constant time, and walking through array to put stuff in it is O(n)
So, implementation based on Hashmap should be slightly faster. It requires extra memory, though.
This speaks very clearly to me that either there is some unreasonable constraint in place that I don’t immediately see, or neither the author nor the reviewer was at all competent in Rust.