Replacing vector pushing with nonmutable code

Right now I have this code to find the variables of a number:

fn main() {
  let num: i32 = 10;
  let mut v: Vec<i32> = Vec::new();
   while i <= num {
         i += 1;
         if num % i == 0 {
             v.push(i);
         };
     }
     println!("{}", v)
//v.iter().sum()
}

While this works, I don't like how I create a mutable vector v and then populate it with numbers. Is there a way to maybe
a) make the mutating less visible by creating a vector from passed arguments (like Vec::from(i))
or b) create a vector from the previous numbers, and then discard the previous numbers, replacing .push with a series of creating and discarding vectors.

I briefly considered using collect, but you can't collect into a vector without declaring it first.

Any help is really appreciated!

How about (1..=num).filter(|i| num % i == 0).collect()?

1 Like

Ah, a .filter solution. Thanks for the suggestion! I'll look at the docs right now.

What is the unary equals (=num) doing?

I'm not exactly sure what you're trying to accomplish or avoid, but here are a few approaches that you may find helpful.

You can see other iterator methods in the documentation, and there's a chapter in the book as well.

3 Likes

.. excludes the right-hand value, but ..= includes it. For example, the range 1..10 will see values 1, 2, ..., 9. But the range 1..=10 will see values 1, 2, ..., 10.

3 Likes

I see, that's range syntax. Thanks so much for helping!

Sure there is. First of all, if you posted well-formatted code that compiles, it would be even easier to help you out. There is play.rust-lang.org which is commonly used to post a link for running an example online next to some code. It also has an option for automatic code formatting. Assuming your iteration starts with i = 0, your code, if fixed, would look like this:

fn main() {
    let num: i32 = 10;
    let mut v: Vec<i32> = Vec::new();
    let mut i = 0;
    while i <= num {
        i += 1;
        if num % i == 0 {
            v.push(i);
        };
    }
    println!("{:?}", v);
    //v.iter().sum()
}

(playground link)

The fact that num+1 is still considered (due to the increment of i happening at the beginning of the loop) is a bit weird, so I’ll assume you meant all values from 1 to num to be considered for i (it shouldn’t affect the result anyways since num % num+1 == 0 only is true for num == 0.

So on your main question: Yes it is possible to avoid“mutable code” or how I would phrase it “imperative code” by using a the functional/declaritive style programming that Rust’s Iterator API offers.

What your code does is:

  • consider the values i from 1 to num
  • of those only consider the ones such that num % i == 0
  • put all of them into a Vec

The corresponding operations of iterators you need for these steps are

The final code could look like this:

fn main() {
    let num: i32 = 10;
    let v: Vec<i32> = (1..=num).filter(|&i| num % i == 0).collect();
    println!("{:?}", v);
    //v.iter().sum()
}

(playground link)

If you, as your comment seems to suggest, plan to just need the sum of all these numbers then storing them in a Vec is a totally unnecessary intermediate step that will just cost extra time and memory for the allocation. Instead you can directly call sum on the iterator that got .collect()ed into the Vec in the code above:

fn main() {
    let num: i32 = 10;
    let iterator = (1..=num).filter(|&i| num % i == 0);
    let sum: i32 = iterator.sum();
    // or just:
    // `let sum: i32 = (1..num).filter(|&i| num % i == 0).sum();`
    
    println!("{}", sum);
}

(playground link)


Edit: Damn you guys with your quick short replies being all fast and stuff xDD

10 Likes

Thanks to you too! Sorry about the non-compiling code... I'm too used to javascript and declaring i within the loop.

Hm, I encountered a strange error (playground]. Why are type annotations mandatory in this case?

Furthermore, .sum() seems to only work on the Filter returned by .filter(), and throws strange errors when trying to collect it into a vector and summing it (filter(...).collect().iter().sum() doesn't work). How come?

You can collect() into different data structures than just a Vec, if the data structure implements the necessary traits. Sometimes the compiler can infer what type you meant from the rest of your code, but in this case the compiler wasn't able to, so you have to tell it. Here's an example of collecting a Vec and a HashSet.

1 Like

The errors I got when I put that in the playground were again about not being able to figure out which collect() to call. It suggested another way to annotate which type you meant which is colloquially called the "turbofish", ::<...>.

Here's what it looked like after I applied the suggestion. And you can see that you can then continue on to .sum() the result. I've used .into_iter() instead of .iter() so that the Vec is completely consumed and the iterator returns i32, instead of returning references into the vector (&i32).

However... there is no point in collecting the Vec, or any other collection, just to immediately consume and iterate over the contents. (If you're just playing around and getting a feel for things though, that's completely understandable.)

1 Like

One more hint for using .collect() inside of more complex expressions you can use the turbofish syntax (don’t you dare skipping the link) to specify the generic type argument and thus the return type of collect. Note that collect has the signature

fn collect<B>(self) -> B
where
    B: FromIterator<Self::Item>, 

in the documentation, (self is referring to the Iterator trait), and here B is a type argument.

Damn, I’m too slow again... nevermind, I’ll skip the concrete usage example and refer you to @quinedot’s answer above.

5 Likes

@steffahn @quinedot thanks to both of you! I didn't know that the vector had to be explicitly turned into an iterator, or what the turbofish was.

Haha yes, that's exactly what I'm doing - I realize that explicitly converting into a vector may be unnecessary memory load, but I'd still like to understand why.

As an aside, it's really a lot more productive and quicker for you as well as others if you start by reading the documentation for the APIs you are using before asking about every single detail and compiler error you come across. For example, I'm sure you could tell from the signature of collect why it needs the type annotation – because it doesn't always return a Vec, it returns whatever type you tell it to collect into. Hence, logically, it can't just guess one type.

1 Like

that's probably true... it is a little hard to understand <<>>'a&::_<%B_ at first, but I probably should spend some more time with the doc.

1 Like

If the question is about collect, then I do not find it strange that newcomers ask questions about it, even if they've read the documentation.

7 Likes

Sadly, a lot of documentation is very difficult. Although javascript seems very easy to me now, when Iw as first reading things like the MDN docs, the morass of things like (callback, [,Arr](num) was impossible. I really hope Rust gets a simple learning site like W3Schools very soon!