Comparing consecutive elements of a vector to find local extrema in an idiomatic way [duplicate] - vector

This question already has an answer here:
Comparing every element in a vector with the next one
(1 answer)
Closed 5 months ago.
I want to find local maxima (and minima) in a vector. So for every entry in a vector I have to check if it is larger (smaller) than the one before it AND the one after it. So I have to access 3 consecutive elements and perform 2 comparisons.
The most simple way would be this:
let v: Vec<64>; // this will hold some numeric values.
for i in 1..v.len() - 2 {
if v[i-1] < v[i] && v[i] > v[i+1] { println!("maximum!"); }
if v[i-1] > v[i] && v[i] < v[i+1] { println!("minimum!"); }
}
But I want to learn more about Rust and find out if there is a more idiomatic way for this. I tried doing it with an iterator, but then I still need to access 3 different locations and the iterator doesn't know its position. Also: wouldn't I need 3 iterators?
I tried to circumvent this by defining 2 mutable variables which "remember" the previous two vector entries and then skipping the first 2 elements:
let v: Vec<64>; // this will hold some numeric values.
let mut two_before = v[0];
let mut one_before = v[1];
for el in v.into_iter.skip(2) {
check_if_middle_greatest(two_before, one_before, el); // i put the comparisons into this function.
two_before = one_before;
one_before = *el;
}
Now this works, but it is very ugly in my opinion and doesn't really feel smart. I was wandering if this could be made more idiomatic. More "rusty".

You could use .windows(3) or .array_windows::<3>(), once it is stabilized.
fn print_max_min(l: i32, m: i32, r: i32) {
if m > l && m > r {
println!("max");
} else if m < l && m < r {
println!("min");
} else {
println!("-");
}
}
fn main() {
let v = [1, 2, 3, 2, 1, 2, 3];
for el in v.windows(3) {
print_max_min(el[0], el[1], el[2]);
}
}
-
max
-
min
-

Related

rust vector iterator in multi-layer loop

I try to use iterator on vector slices, but it just doesn't work.
My code are as follows
pub fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut res: Vec<Vec<i32>> = Vec::new();
for (n1, &i1) in nums.iter().enumerate() {
for (n2, &i2) in nums[(n1 + 1)..].iter().enumerate() {
for (n3, &i3) in nums[(n2 + 1)..].iter().enumerate() {
if i1 + i2 + i3 == 0 {
res.push(Vec::from([i1, i2, i3]));
}
}
}
}
return res;
}
I expected the n2 loop in nums ranging n1 to the end, but it just loop from the beginning, regardless of what n1 is.
Same happened on n3.
Did I use iterators and slices correctly?
As you can see in the docs, enumerate just counts the number of iterations. If you want to skip the first n elements of an iterator, you should use the skip function instead, which also more clearly expresses your intent.
nums.iter().enumerate().skip(n)
Note the order of enumerate and skip however: this way you're first constructing an enumerated iterator, then skipping some elements, so your index will also "start from" n. The other way around you'll skip th elements first, then count them, starting from 0.
There is no way for enumerate to know where you want to start counting from so it always starts at 0.
You can use zip with a range if you want to start counting from a different number.
pub fn three_sum(nums: Vec<i32>) -> Vec<[i32;3]> {
let mut res = Vec::new();
for (n1, &i1) in nums.iter().enumerate() {
for (n2, &i2) in (n1+1..).zip(nums[(n1 + 1)..].iter()) {
for (n3, &i3) in (n2+1..).zip(nums[(n2 + 1)..].iter()) {
if i1 + i2 + i3 == 0 {
res.push([i1, i2, i3]);
}
}
}
}
res
}

Why am I getting double reference to type when iterating over vector of references? [duplicate]

This question already has answers here:
What is an idiomatic way to collect an iterator of &T into a collection of Ts?
(2 answers)
Closed 12 months ago.
Consider following minimum reproducible. I have a vector of objects and I have a hashmap where each value is a vector of references to objects from the objects vector.
I then get a value from HashMap (which is a reference to vector of references) and I want to filter it so I get vector of references to objects. However, when I do iter-filter-collect, compiler claims that I am iterating over &&O (instead of &O I expect) and errors out.
I can "fix" that error by uncommenting the map function which dereferences iterated objects, however I don't think that is the correct thing to do here.
use std::collections::HashMap;
struct O {
a: i32,
b: i32,
}
fn main() {
let mut objects = vec![
O { a: 1, b: 0 },
O { a: 2, b: 0 },
O { a: 3, b: 0 },
O { a: 4, b: 1 },
];
let mut b_to_o: HashMap<i32, Vec<&O>> = HashMap::new();
for o in &objects {
b_to_o.entry(o.b).or_insert(Vec::new()).push(&o);
}
let b_zero: &Vec<&O> = b_to_o.get(&0).unwrap();
let b_zero_a_odd: Vec<&O> = b_zero
.iter()
.filter(|o| o.a % 2 == 0)
//.map(|o| *o)
.collect::<Vec<&O>>();
}
NOTE: Vector of objects doesn't have to be mut in this example, but it has to be in the code I am reproducing.
You have a Vec<&T> and iter yields &Output, where Output is the inner type of the Vec, in this case &T, you you get: &Output => &&T.
There is nothing wrong on dereferencing the double reference, you can use map, or you can use filter_map as a single step (notice the pattern mathing in the closure parameter to dereference directly |&o|):
let b_zero_a_odd: Vec<&O> = b_zero
.iter()
.filter_map(|&o| if o.a % 2 == 0 { Some(o) } else { None })
.collect::<Vec<&O>>();
Playground

Why is finding the intersection of integer sets faster with a Vec compared to BTreeSet?

I need to find out how many integers are present in both of two given sets, fast. The sets are written to only once but this operation will be performed many times with different pairs of sets. The sets contain 5-30 integers and the largest of these integers is 840000.
I have initially tried to iterate over one Vec and for each element check if its present in the other Vec. I then decided to use BTreeSet instead since it should be significantly faster at checking if an integer is present in the set, but that does not seem to be the case. The Vec implementation takes ~72ms and the BTreeSet ~96ms when called on couple thousands of sets in release mode under stable Rust 1.34 with same performance when using nightly.
This is the Vec implementation:
use std::cmp;
fn main() {
let mut sets = Vec::with_capacity(1000);
for i in 1..1000 {
let mut set = Vec::new();
for j in 1..i % 30 {
set.push(i * j % 50000);
}
sets.push(set);
}
for left_set in sets.iter() {
for right_set in sets.iter() {
calculate_waste(left_set, right_set);
}
}
}
fn calculate_waste(left_nums: &Vec<usize>, right_nums: &Vec<usize>) -> usize {
let common_nums = left_nums.iter().fold(0, |intersection_count, num| {
intersection_count + right_nums.contains(num) as usize
});
let left_side = left_nums.len() - common_nums;
let right_side = right_nums.len() - common_nums;
let score = cmp::min(common_nums, cmp::min(left_side, right_side));
left_side - score + right_side - score + common_nums - score
}
And this is the BTreeSet implementation:
use std::cmp;
use std::collections::BTreeSet;
fn main() {
let mut sets = Vec::with_capacity(1000);
for i in 1..1000 {
let mut set = BTreeSet::new();
for j in 1..i % 30 {
set.insert(i * j % 50000);
}
sets.push(set);
}
for left_set in sets.iter() {
for right_set in sets.iter() {
calculate_waste(left_set, right_set);
}
}
}
fn calculate_waste(left_nums: &BTreeSet<usize>, right_nums: &BTreeSet<usize>) -> usize {
let common_nums = left_nums.intersection(&right_nums).count();
let left_side = left_nums.len() - common_nums;
let right_side = right_nums.len() - common_nums;
let score = cmp::min(common_nums, cmp::min(left_side, right_side));
left_side - score + right_side - score + common_nums - score
}
It was ran with the command (-w 50 makes it ignore the first 50 runs):
hyperfine "cargo run --release" -w 50 -m 100
Full code of the program available here.
Is the BTreeSet implementation slower because there are too few integers in the set to allow its O(log n) access time to shine? If so, is there anything else I can do to speed up this function?
Since your sets don't change over time, I think your best option is to use sorted vectors. Sorting the vectors will be required only once, at initialization time. The intersection of two sorted vectors can be computed in linear time by iterating over them simultaneously, always advancing the iterator that currently points to the lower number. Here is an attempt at an implementation:
fn intersection_count_sorted_vec(a: &[u32], b: &[u32]) -> usize {
let mut count = 0;
let mut b_iter = b.iter();
if let Some(mut current_b) = b_iter.next() {
for current_a in a {
while current_b < current_a {
current_b = match b_iter.next() {
Some(current_b) => current_b,
None => return count,
};
}
if current_a == current_b {
count += 1;
}
}
}
count
}
This probably isn't particularly well optimised; regardless, benchmarking with Criterion-based code indicates this version is more than three times as fast as your solution using vectors.

How to randomly split a map in Go as evenly as possible?

I have a quick question. I am fairly new to golang. Say I have a map like so:
map[int]string
How could I randomly split it into two maps or arrays and as close to even as possible? So for example, if there are 15 items, it would be split 7 - 8.
For example:
func split(m map[int]string) (odds map[int]string, evens map[int]string) {
n := 1
odds = make(map[int]string)
evens = make(map[int]string)
for key, value := range m {
if n % 2 == 0 {
evens[key] = value
} else {
odds[key] = value
}
n++
}
return odds, evens
}
It is actually an interesting example, because it shows a few aspects of Go that are not obvious for beginners:
range m iterates in a random order, unlike in any other language as far as I know,
the modulo operator % returns the remainder of the integer division,
a function can return several values.
You could do something like this:
myStrings := make(map[int]string)
// Values are added to myStrings
myStrings2 := make(map[int]string)
// Seed system time for random numbers
rand.Seed(time.Now().UTC().UnixNano())
for k, v := range myStrings {
if rand.Float32() < 0.5 {
myStrings2[k] = v
delete(myStrings, k)
}
}
https://play.golang.org/p/6OnH1k4FMu

Alternative to Vec::chunks() that consumes the vector

I have the following function, which takes a vector as argument and returns a vector of its pairs of elements:
fn to_pairs(flat: Vec<u64>) -> Vec<(u64, u64)> {
assert!(flat.len() % 2 == 0);
let mut pairs = Vec::new();
pairs.reserve(flat.len() / 2);
for pair in flat.chunks(2) {
assert!(pair.len() == 2);
pairs.push((pair.get(0).unwrap().clone(), pair.get(1).unwrap().clone()));
}
pairs
}
I want consume the vector flat so I don't have to clone its elements when constructing the pair.
Is it possible to do so without reimplementing a variation of Vec::chunks() myself?
I want consume the vector flat so I don't have to clone its elements when constructing the pair.
Convert the input Vec into an iterator, then take two things from the iterator at a time. Essentially, you want the same thing as processing a Range (an iterator) in chunks:
fn to_pairs<T>(flat: Vec<T>) -> Vec<(T, T)> {
let len = flat.len();
assert!(len % 2 == 0);
let mut pairs = Vec::with_capacity(len / 2);
let mut input = flat.into_iter().peekable();
while input.peek().is_some() {
match (input.next(), input.next()) {
(Some(a), Some(b)) => pairs.push((a, b)),
_ => unreachable!("Cannot have an odd number of values"),
}
}
pairs
}
fn main() {
assert_eq!(vec![(1,2), (3,4)], to_pairs(vec![1,2,3,4]));
assert_eq!(vec![(true,true), (false,false)], to_pairs(vec![true,true,false,false]));
}
The assert!(len % 2 == 0); is quite important here, as Iterator makes no guarantees about what happens after the first time next returns None. Since we call next twice without checking the first value, we could be triggering that case. In other cases, you'd want to use fuse.
As pointed out by Kha, you could simplify the while loop a bit:
let mut input = flat.into_iter();
while let (Some(a), Some(b)) = (input.next(), input.next()) {
pairs.push((a, b));
}

Resources