1

I have the following code in kotlin and I'm trying to find a rust equivalent, but don't understand the chaining mechanism in rust to convert.

val windowSize = 2
val result = listOf(1, 2, 3, 4, 5, 6)
  .windowed(windowSize, 1) ; [[1,2], [2,3], [3,4], [4,5], [5,6]]
  .map { it.sum() }        ; [  3,     5,     7,     9,     11]
  .windowed(2, 1)          ; [[3,5], [5,7], [7,9], [9,11] ]
  .count { it[0] < it[1] } ; 4

;; result = 4, as there are 4 sequences that have first number less than 2nd,
;; when considering a sliding window over the original data of 2 items at a time.

It just takes a list of integers, splits them into pairs (but the windowSize will be a function parameter), sums those groups, splits the sums into pairs again, and finds where each second element is bigger than the previous, so finding increasing values over moving windows.

I'm converting this to the rust equivalent, but struggling to understand how to chain operations together.

What I've got so far is:

let input = [1, 2, 3, 4, 5, 6];
input.windows(2)
    .map(|es| es.iter().sum())
    // what goes here to do the next windows(2) operation?
    .for_each(|x: u32| println!("{}", x));

I can "for_each" over the map to do things on the iteration, but I can't split it with another "windows()", or don't know the magic to make that possible. IntelliJ is showing me the return type from map is impl Iterator<Item=?>

Can anyone enlighten me please? I am an absolute beginner on rust, so this is undoubtedly to do with my understanding of the language as a whole.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Mark Fisher
  • 9,838
  • 3
  • 32
  • 38
  • advent of code too? – Netwave Dec 01 '21 at 17:48
  • i was going to tag AOC, but yeah :) I'm trying to learn in rust, but the competition of leaderboards at work mean I'm doing it in my preferred language first, then trying to convert to rust. – Mark Fisher Dec 01 '21 at 17:50
  • Perhaps you're looking for [`Itertools::tuple_windows()`](https://docs.rs/itertools/latest/itertools/trait.Itertools.html#method.tuple_windows). – Chayim Friedman Dec 01 '21 at 18:24
  • 1
    Does this answer your question? [Are there equivalents to slice::chunks/windows for iterators to loop over pairs, triplets etc?](https://stackoverflow.com/questions/42134874/are-there-equivalents-to-slicechunks-windows-for-iterators-to-loop-over-pairs) – Chayim Friedman Dec 01 '21 at 18:26
  • @Stargateur done. let me know if there's anything else that I can add. I'm really trying to grasp the difference in the way 2 languages work than the results though. – Mark Fisher Dec 01 '21 at 21:59

4 Answers4

5

The Itertools crate provides a reasonably convenient way to do this with the tuple_windows method.

use itertools::Itertools; 

fn main() {
    let input = [1i32, 2, 3, 4, 5, 6];
    let output: usize = input
        .windows(2)
        .map(|es| es.iter().sum::<i32>())
        .tuple_windows()
        .filter(|(a, b)| a < b)
        .count();
    println!("{}", output);
}

Playground

The standard library does not have a way to do this without collecting the iterator first, which requires two passes through the data.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Aiden4
  • 2,504
  • 1
  • 7
  • 24
1

It is a bit convoluted to chain everything. You need to collect into a vec so you can access windows again. Then you can flat_map the windows to array references (taken from this other answer) to complete what you want to do:

fn main() {
    let input = [1usize, 2, 3, 4, 5, 6];
    let res = input
        .windows(2)
        .map(|es| es.iter().sum::<usize>())
        .collect::<Vec<_>>()
        .windows(2)
        .flat_map(<[usize; 2]>::try_from)
        .filter(|[a, b]| a < b)
        .count();
    println!("{}", res);
}

Playground


Note: Nightly feature array_windows that use const generic allow to remove the .flat_map(<&[usize; 2]>::try_from) call

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • is this normal practice in rust? feels a little OTT. is there a more idiomatic way to do this type of combinations, or is this just a feature of windows()? – Mark Fisher Dec 01 '21 at 18:10
  • While this does work, it feels wrong. Better to not use iterators instead of collecting for nothing. – Chayim Friedman Dec 01 '21 at 18:25
  • @MarkFisher, you can rely on itertools windows_tuple from the other answer. But I would say most probably your approach to the problem is not the best one. – Netwave Dec 01 '21 at 18:56
  • @MarkFisher it may be better to do it this way than to use itertools, one should benchmark to be sure, but this could be unexpected faster. – Stargateur Dec 01 '21 at 19:37
  • @Stargateur nice add-up, thanks! – Netwave Dec 01 '21 at 20:33
0

As stated in @Aiden4's answer, the best solution is to use itertools::tuple_windows. It is however possible using just the standard library and without collecting to an intermediate vector using Iterator::scan:

fn main() {
    let input = [1i32, 2, 3, 4, 5, 6];
    let output: usize = input
        .windows(2)
        .map(|es| es.iter().sum())
        .scan(0, |prev, cur| {
            let res = (*prev, cur);
            *prev = cur;
            Some(res)
        })
        .skip(1)
        .filter(|(a, b)| a < b)
        .count();
    println!("{}", output);
}

Playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • that's an interesting solution, if i read it correctly, you're creating a kind of pair in the iteration by scanning it as you go along, then skipping the first (presumably 0) element to get the first pair? – Mark Fisher Dec 02 '21 at 09:09
  • Exactly. The first element returned by `scan` will be the pair `(0, input[0]+input[1])` so I need to skip it, then subsequent elements have the form `(input[i]+input[i+1], input[i+1]+input[i+2])` – Jmb Dec 02 '21 at 11:14
0

Using std and stable only:

fn main() {
    let input = [1i32, 2, 3, 4, 5, 6];
    let mut iter = input.windows(2).map(|es| es.iter().sum::<i32>());

    let n = if let Some(mut prev) = iter.next() {
        iter.map(|i| {
            let ret = (prev, i);
            prev = i;
            ret
        })
        .filter(|(a, b)| a < b)
        .count()
    } else {
        0
    };

    println!("{}", n);
}

This should be very fast.

Stargateur
  • 24,473
  • 8
  • 65
  • 91