How to avoid underflow pitfalls when iterating manually? - rust

The code below would panic because j is typed as usize.
fn foo(vec: &Vec<i32>) {
let mut i = 0;
let mut j = vec.len() - 1;
while i < j {
while i < j && !some_condition(vec[i]) {
i += 1;
}
while i < j && !some_condition(vec[j]) {
j -= 1;
}
if i < j {
vec.swap(i, j);
i += 1;
j -= 1;
}
}
}
foo(&vec![]);
Most of the time iterator helps to avoid such issues. But when we have to manually iterate with indexes we really need to be careful. I could check the size of vec ahead to avoid panic. But the real problem here is I tend to think the program would work as expected even without empty check until a corner case bites me. So I wonder if there is any idiomatic way to do this kind of things in Rust.

You could look at saturating_sub or checked_sub:
fn foo(vec: &Vec<i32>) {
let mut i = 0;
let mut j = vec.len().saturating_sub(1);
while i < j {
// Some other logic
i += 1;
// EITHER
j.saturating_sub(1); // avoid getting below 0
//OR
j = j.checked_sub(1).expect("usize underflow");
}
}
foo(&vec![]);
If you want some more complex handling instead of .expect() you can use:
j = j.checked_sub(1).ok_or_else(|| some_your_error_creator())?;
which seems more idiomatic, but will require some additional work with error-handling.

Personally, I'd just do as you suggest vec.is_empty() and then return, to keep the logic clear.
If you don't want that, then instead you can perform a saturating subtraction using saturating_sub(). If the subtraction would result in an underflow then it would remain at 0.
// let mut j = vec.len() - 1;
let mut j = vec.len().saturating_sub(1);

Related

Getting "Use of moved value" in a loop

I'm making Tic-tac-toe in Rust. I want to count the amount of cells in a line, when the player is moving. Like -- x --. So I call count_result two times with the same dx and dy (that's why I pass dx and dy by reference). But here's the problem. My linter in IntelliJ IDEA complains that I use moved value. The compiler compiles without any warnings. What is the problem? Can you help me?
IntelliJ IDEA screenshot
Just to clarify - game_data.board is [[Option<Cell>; 3]; 3]
struct Data {
board: [[Option<Cell>; 3]; 3],
}
fn count_result(game_data: &Data, point: &Point, cell: &Cell, dx: &isize, dy: &isize) -> isize {
let mut count: isize = 0;
let point = (*point).clone();
let mut x = point.x;
let mut y = point.y;
while (x + dx < 3 && x + dx >= 0) && (y + dy >= 0 && y + dy < 3) {
x += 1;
y += 1;
if game_data.board[x as usize][y as usize] == Some(*cell) {
count += 1;
} else {
break;
}
}
count
}
Here is another example of the same problem.
game_data: &mut Data
IntelliJ IDEA screenshot
This is just a linter indexing invalid state. Just invalidate caches and you'll be fine.

Bad rust code optimization or I just haven't done enough? (Euler #757)

I'm trying to solve my first ever project Euler problem just to have fun with Rust, and got stuck on what seems to be an extremely long compute time to solve
Problem:
https://projecteuler.net/problem=757
I came up with this code to try to solve it, which I'm able to solve the base problem (up to 10^6) in ~245 ms and get the expected result of 2,851.
use std::time::Instant;
fn factor(num: u64) -> Vec<u64> {
let mut counter = 1;
let mut factors = Vec::with_capacity(((num as f64).log(10.0)*100.0) as _);
while counter <= (num as f64).sqrt() as _ {
let div = num / counter;
let rem = num % counter;
if rem == 0 {
factors.push(counter);
factors.push(div);
}
counter += 1
}
factors.shrink_to_fit();
factors
}
fn main() {
let now = Instant::now();
let max = 10u64.pow(6);
let mut counter = 0;
'a: for i in 1..max {
// Optimization: All numbers in the pattern appear to be evenly divisible by 4
let div4 = i / 4;
let mod4 = i % 4;
if mod4 != 0 {continue}
// Optimization: And the remainder of that divided by 3 is always 0 or 1
if div4 % 3 > 1 {continue}
let mut factors = factor(i);
if factors.len() >= 4 {
// Optimization: The later found factors seem to be the most likely to fit the pattern, so try them first
factors.reverse();
let pairs: Vec<_> = factors.chunks(2).collect();
for paira in pairs.iter() {
for pairb in pairs.iter() {
if pairb[0] + pairb[1] == paira[0] + paira[1] + 1 {
counter += 1;
continue 'a;
}
}
}
}
}
println!("{}, {} ms", counter, now.elapsed().as_millis());
}
It looks like my code is spending the most amount of time on factoring, and in my search for a more efficient factoring algorithm than what I was able to come up with on my own, I couldn't find any rust code already made (the code I did find was actually slower.) But I did a simulation to estimate how long it would take even if I had a perfect factoring algorithm, and it would take 13 days to find all numbers up to 10^14 with the non-factoring portions of this code. Probably not what the creator of this problem intends.
Given I'm relatively new to programming, is there some concept or programming method that I'm not aware of (like say using a hashmap to do fast lookups) that can be used in this situation? Or is the solution going to involve spotting patterns in the numbers and making optimizations like the ones I have found so far?
If Vec::push is called when the vector is at its capacity, it will re-allocate its internal buffer to double the size and copy all its elements to this new allocation.
Vec::new() creates a vector with no space allocated so it will be doing this re-allocation.
You can use Vec::with_capacity((num/2) as usize) to avoid this and just allocate the max you might need.

Remove value if part of vector, and if so accumulate it to another variable

I currently do it this way:
// v is a vector with thousands of sorted unsigned int value.
let mut total = 0;
// [...]
// some loop
let a = 5;
if v.iter().any(|&x| x == a as u16) {
total += a;
v.retain(|&x| x != a as u16);
}
// end loop
But it is quite inefficient since I iterate twice over v (although perhaps the compiler would catch this and optimize), isn't it a more elegant way to do it with Rust?
NB: The vector is sorted and contains no duplicate values if it can help
If I understand correctly your request, here a solution:
You say your vector is sorted so you can use binary_search()
And so you can use remove()
fn foo(data: &mut Vec<u16>) -> u64 {
let mut total: u64 = 0;
let mut a = 0;
while data.len() > 0 {
if let Ok(i) = data.binary_search(&a) {
total += data.remove(i) as u64;
}
a += 1;
}
total
}
fn main() {
let mut data = vec![1, 3, 8, 9, 46];
assert_eq!(foo(&mut data), 67);
}
This keep the vector sorted while removing, note that this is a dummy example. If you don't care about sorting you can use swap_remove() but this disallow the use of binary_search().
It's hard to say what would be the better.

How to idiomatically iterate one half of an array and modify the structure of the other?

What is the idiomatic way to iterate (read) over the first half of the vector and change the structure of the second half of the vector depending on the first? This is very abstract but some algorithms could be boiled down to this problem. I want to write this simplified C++ example in Rust:
for (var i = 0; i < vec.length; i++) {
for (var j = i + 1 ; j < vec.length; j++) {
if (f(vec[i], vec[j])) {
vec.splice(j, 1);
j--;
}
}
}
An idiomatic solution of this generic problem will be the same for Rust and C, as there's no constraints which would allow simplification.
We need to use indexes because vector reallocation will invalidate the references contained by the iterators. We need to compare the index against the current length of the vector on each cycle because the length could be changed. Thus an idiomatic solution will look like this:
let mut i = 0;
while i < v.len() {
let mut j = i + 1;
while j < v.len() {
if f(v[i], v[j]) {
v.splice(j, 1);
} else {
j += 1;
}
}
i += 1;
}
Playground link
While this code covers the general case, it is rarely useful. It doesn't capture specifics, which are usually inherent to the problem at hand. In turn, the compiler is unable to catch any errors at compile time. I don't advise writing something like this without considering another approaches first.

How to use multiple variables in Rust's for loop?

In the C family of languages, I can do this on one line:
for(int i = lo, int j = mid+1; i <= mid && j <= hi; i++, j++){
...
}
But in Rust... I can only write it like this:
for i in lo..mid+1 {
let mut j = mid+1;
if j <= hi {
break;
}
...
j += 1;
}
Is there's a more efficient way to implement this?
Using an iterator works for above, but using an iterator makes some occasions like using arithmetic troublesome, such as
for (int i = 0; i < n; i ++) {
if (a[i] == ...) {
i += 5;
}
}
In Rust, this does not work. The variable i will not be incremented by 5, but by 1 instead:
for i in 0..n {
if a[i] == ... {
i += 5;
}
}
You can create two parallel range iterators, zip them, then iterate though the combination:
fn main() {
let values = [10, 20, 30, 40, 50, 60, 70, 80, 90];
let lo = 2;
let mid = 5;
let hi = 7;
let early_indexes = lo..(mid + 1);
let late_indexes = (mid + 1)..(hi + 1);
for (i, j) in early_indexes.zip(late_indexes) {
println!("{}, {}", i, j);
println!("{} - {}", values[i], values[j]);
}
}
Someday, inclusive ranges will be stabilized, and you should be able to something like this (depending on the eventual syntax):
let early_indexes = lo...mid;
let late_indexes = (mid + 1)...hi;
for (i, j) in early_indexes.zip(late_indexes) {
println!("{}, {}", i, j);
println!("{} - {}", values[i], values[j]);
}
If you are actually iterating though a slice as I've shown for my example, you can also just combine the two iterators directly and ignore the index:
let early_values = values[lo..(mid + 1)].iter();
let late_values = values[(mid + 1)..(hi + 1)].iter();
for (i, j) in early_values.zip(late_values) {
println!("{}, {}", i, j);
}
The variable i will not be incremented by 5, but by 1 instead.
Yes, incrementing by a step is annoying, and some day it will also be stabilized. In the meantime:
What is a stable way to iterate on a range with custom step?
How do I iterate over a range with a custom step?
If you need full control, you can always use while or loop:
let mut i = 0;
while i < n {
if a[i] == ... {
i += 5;
}
i += 1;
}

Resources