0

I have been slowly teaching myself some Rust, and in my latest endeavor, I am trying my hand at implementing some of the Maelstrom 'workloads' in Rust.

As part of the Broadcast 'workload', I am trying to keep track of a list of messages other 'nodes' know about, and return only a set of messages that they don't, plus a few random extras (just in case some messages get 'dropped' or something..?)...

But I'm running into some weirdness I can't understand. Here's a snippet of what I'm trying to do (full source here):

    // start by getting all the values we know the src node doesn't know 
    let src_known = self.neighbors_known_msgs.get(&msg.src).unwrap(); // this ends up being a HashSet<usize> (already checked it exists earlier in method)
    let mut src_unknown: HashSet<_> = self.known_msgs
        .difference(src_known)
        .copied()
        .collect();


    // Now extend that list with an extra set of values
    let list:Vec<_> = self.known_msgs.iter().copied().collect();
    let mut window_size = MIN_WIDOWING_SIZE.max(list.len()/5);
    window_size = window_size.min(list.len());

    // for a first pass, just select the extras randomly, 
    // before trying to implement proper windowing
    let mut rng = rand::thread_rng();
    let extras = (0..=window_size).filter_map(|_| {
        let idx: usize = rng.gen();
        list.get(idx)
    }).cloned();

    // at this point, we have a list of 20% of of the 
    // total messages we know about in the 'extras' range/iterator

    src_unknown.extend(extras);

    // `src_unknown` is unchanged at this point...???
    // and `extras` is not exhausted...???

I setup a test where the self.known_msgs and self.neighbors_known_msgs.get(&msg.src) have exactly the same set of 100 values, so a .difference(..) results in set of no values.

So I would expect the extras with 20 values (which shows up in debug), would be the only values in src_unknown after the src_unknown.extend(extras); statement executes...

But the actual result is src_unknown is unchanged, and the extras is still a Cloned<FilterMap<RangeInclusive<usize> that is showing iter:{start:0, end:20, exhausted:false} in the debugger...??

Im at a loss to understand what is going wrong here... Help! (And thanks in advance!)

Teknein
  • 106
  • 1
  • 1
  • 7
  • 3
    `rng.gen()` to get a `usize` is very unlikely to be less than 100, so your `list.get(idx)` will almost always be `None`, meaning your iterator yields no values. Perhaps you wanted to [generate within a range](https://stackoverflow.com/questions/19671845/how-can-i-generate-a-random-number-within-a-range-in-rust) instead? – kmdreko May 27 '23 at 01:23

1 Answers1

0

Look at this line.

let idx: usize = rng.gen();

This creates a random usize. For example, here's ten random usize values.

271997752541285776
12713891318535398239
13268520945444188811
14835789233757821964
16139701502018186282
14874722521942862824
2896522772732415062
12799049362493230697
10493000952461094302
13549460499054734951

There's only a 0.0000000000000005% chance that a random usize is less than 100, so if you are using it to index a list of length 100, it will pretty much never be in bounds. So what you have created is a filter that sees 21 None values and then finishes.

To get the behavior you intended to have, you can use SliceRandom::choose.

use rand::seq::SliceRandom;
let extras = (0..=window_size).filter_map(|_| {
    list.choose(&mut rng)
}).cloned();

However, if you don't want repeated elements, you can use choose_multiple.

use rand::seq::SliceRandom;
let extras = list.choose_multiple(&mut rng, window_size).cloned();
drewtato
  • 6,783
  • 1
  • 12
  • 17
  • Okay, that makes more sense (I had a feeling I was having a 'cant see the forest through the trees moment')... But what was tripping me up when looking at this in the debugger, was when inspecting `extras` it was showing as `iter:{start:0, end:20, exhausted:false}` before the `.extend(..)` statement and still showed `iter:{start:0, end:20, exhausted:false}` *after* too... I feel like there is still something I'm missing... Or is an empty iterator just *always* going to show `exhausted:false`? – Teknein May 27 '23 at 15:57
  • 1
    @Teknein not sure what's happening there, might be that it's being copied and the debugger isn't keeping track of it. You can see it works if you print them out https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=815164fbbb74e78a1b586ae8db1c2011 Also this is actually 21 elements being filtered out. – drewtato May 27 '23 at 17:38
  • 1
    @Teknein I'd assume the debugger is showing a stale value since the `extras` variable is actually destroyed by passing it to `.extend()`, there's no value to display afterwards. You might see the behavior change if you pass it by mutable reference (i.e. `.extend(&mut extras)`) instead. – kmdreko May 27 '23 at 18:04