How would you iterate over an array of 3 strings? - rust

How would you iterate over the following array in Rust?
const ARRAY: [&'static str;3] = ["red", "blue","green"];
I know in a language like say Lua, you would do:
for i, v in ipairs(ARRAY) do
print(i, v)
end
And I mention that because I saw something similar syntactically in Rust in this post:
How to print both the index and value for every element in a Vec?
But I am still trying to wrap my mind around vectors and other Rust concepts as it relates to arrays.

Thanks to #BallpointBen guidance via comment, I was able to get a working for loop:
fn main() {
const ARRAY: [&'static str;3] = ["red", "blue","green"];
for (count, v) in ARRAY.into_iter().enumerate() {
println!("{} {}", count, v);
}
}
I still do not understand why there is two sets of curly braces as opposed to three, but this is working.

Related

Is Rust hash map storing the same key twice?

I am going through the Rust language book, and was going through the hash map section.
At the end of the section there is a quiz, which asks what would be the output of the following code.
use std::collections::HashMap;
fn main() {
let mut h: HashMap<char, Vec<usize>> = HashMap::new();
for (i, c) in "hello!".chars().enumerate() {
h.entry(c).or_insert(Vec::new()).push(i);
}
let mut sum = 0;
for i in h.get(&'l').unwrap() {
sum += *i;
}
println!("{}", sum);
}
The answer is 5, and I confirmed by executing the same. The explanation given is following
This program stores a vector of indexes for each occurrence of a given letter into a hashmap. Then it sums all the indexes for the letter 'l', which occurs at indexes 2 and 3 in the string "hello!".
Now when I look at section adding a new key and updating value based on old one, it seems Rust only allows unique key for a hash map as per definition.
However, when I run the above program with an extra print statement in the for loop
for i in h.get(&'l').unwrap() {
println!("{}", i);
sum += *i;
}
It seems there are two entries for the character l. As per my understanding, when iterating over string hello! when the first for loop encounters the second l it should update and overwrite the index value of l from 2 to 3, instead of making another entry with key l and value 3.
So the question is, is Rust allowing duplicate keys? Somehow this is not making sense.

Split string in Rust, treating consecutive delimiters as one

How do I split a string in Rust such that contiguous delimiters are collapsed into one? For example:
"1 2 3".splitX(" ")
should yield this Vec: ["1", "2", "3"] (when collected from the Split object, or any other intermediate object there may be). This example is for whitespace but we should be able to extend this for other delimiters too.
I believe we can use .filter() to remove empty items after using .split(), but it would be cleaner if it could be done as part of the original .split() directly. I obviously searched this thoroughly and am surprised I can't find the answer anywhere.
I know for whitespace we already have split_whitespace() and split_ascii_whitespace(), but I am looking for a solution that works for a general delimiter string.
The standard solution is to use split then filter:
let output: Vec<&str> = input
.split(pattern)
.filter(|s| !s.is_empty())
.collect();
This is fast and clear.
You can also use a regular expression to avoid the filter step:
let output: Vec<&str> = regex::Regex::new(" +").unwrap()
.split(input)
.collect();
If it's in a function which will be called several times, you can avoid repeating the Regex compilation with lazy_regex:
let output: Vec<&str> = lazy_regex::regex!(" +")
.split(input)
.collect();
IMO, by far the cleanest way is to write .split(" ").filter(|s| !s.is_empty()). It works for all separators and the intent is obvious from reading the code.
If that's too "ugly", you could perhaps pull it into a trait:
trait SplitNonEmpty {
// you might want to define your own struct for the return type
fn split_non_empty<'a, P>(&self, p: P) where P: Pattern<'a> -> ...;
}
impl SplitNonEmpty for &str {
// ...
}
If it's very important that this function returns a Split, you might need to refactor your code to use traits more; do you really care that it was created by splitting a string, or do you care that you can iterate over it? If so, maybe that function should take a impl IntoIterator<&'a str>?
As stated by others, split and filter or with regex is better here. But there is one pattern which can be used flat_map. Though in this context it doesn't add much value.
fn main() {
let output: Vec<&str> = "1 2 3"
.split(" ")
.flat_map(|x| if !x.is_empty() { Some(x) } else { None })
.collect();
println!("{:#?}", output)
}
You can use this pattern, say, if you want to parse these strings as numbers and ignore error values.
fn main() {
let output: Vec<i32> = "1 2 3"
.split(" ")
.flat_map(|x| x.parse())
.collect();
println!("{:#?}", output)
}
All flat_map cares is closure to return something which implements IntoIterator

Flatten vector of enums in Rust

I am trying to flatten a vector of Enum in Rust, but I am having some issues:
enum Foo {
A(i32),
B(i32, i32),
}
fn main() {
let vf = vec![Foo::A(1), Foo::A(2), Foo::B(3, 4)];
let vi: Vec<i32> = vf
.iter()
.map(|f| match f {
Foo::A(i) => [i].into_iter(),
Foo::B(i, j) => [i, j].into_iter(),
})
.collect(); // this does not compile
// I want vi = [1, 2, 3, 4]. vf must still be valid
}
I could just use a regular for loop and insert elements into an existing vector, but that would be no fun. I'd like to know if there is a more idiomatic Rust way of doing it.
Here's a way to do it that produces an iterator (rather than necessarily a vector, as the fold() based solution does).
use std::iter::once;
enum Foo {
A(i32),
B(i32, i32),
}
fn main() {
let vf = vec![Foo::A(1), Foo::A(2), Foo::B(3, 4)];
let vi: Vec<i32> = vf
.iter()
.flat_map(|f| {
match f {
&Foo::A(i) => once(i).chain(None),
&Foo::B(i, j) => once(i).chain(Some(j)),
}
})
.collect();
dbg!(vi);
}
This does essentially the same thing that you were attempting, but in a way which will succeed. Here are the parts I changed, in the order they appear in the code:
I used .flat_map() instead of .map(). flat_map accepts a function which returns an iterator and produces the elements of that iterator ("flattening") whereas .map() would have just given the iterator.
I used & in the match patterns. This is because, since you are using .iter() on the vector (which is appropriate for your requirement “vf must still be valid”), you have references to enums, and pattern matching on a reference to an enum will normally give you references to its elements, but we almost certainly want to handle the i32s by value instead. There are several other things I could have done, such as using the * dereference operator on the values instead, but this is concise and tidy.
You tried to .into_iter() an array. Unfortunately, in current Rust this does not do what you want and you can't actually return that iterator, for somewhat awkward reasons (which will be fixed in an upcoming Rust version). And then, if it did mean what you wanted, then you'd get an error because the two match arms have unequal types — one is an iterator over [i32; 1] and the other is an iterator over [i32; 2].
Instead, you need to build two possible iterators which are clearly of the same type. There are lots of ways to do this, and the way I picked was to use Iterator::chain to combine once(i), an iterator that returns the single element i, with an Option<i32> (which implements IntoIterator) that contains the second element j if it exists.
Notice that in the first match arm I wrote the seemingly useless expression .chain(None); this is so that the two arms have the same type. Another way to write the same thing, which is arguably clearer since it doesn't duplicate code that has to be identical, is:
let (i, opt_j) = match f {
&Foo::A(i) => (i, None),
&Foo::B(i, j) => (i, Some(j)),
};
once(i).chain(opt_j)
In either case, the iterator's type is std::iter::Chain<std::iter::Once<i32>, std::option::IntoIter<i32>> — you don't need to know this exactly, just notice that there must be a type which handles both the A(i) and the B(i, j) cases.
First of all, you need to change the i32 references to owned values by e.g. dereferencing them. Then you can circumvent proxying through inlined arrays by using fold():
enum Foo {
A(i32),
B(i32, i32),
}
fn main() {
let vf = vec![Foo::A(1), Foo::A(2), Foo::B(3, 4)];
let vi: Vec<i32> = vf
.iter()
.fold(Vec::new(), |mut acc, f| {
match f {
Foo::A(i) => acc.push(*i),
Foo::B(i, j) => {
acc.push(*i);
acc.push(*j);
}
}
acc
});
}

Is there a more idiomatic way to initialize an array with random numbers than a for loop?

Is there an idiomatic way of initialising arrays in Rust. I'm creating an array of random numbers and was wondering if there is a more idiomatic way then just doing a for loop. My current code works fine, but seems more like C than proper Rust:
let mut my_array: [u64; 8] = [0; 8];
for i in 0..my_array.len() {
my_array[i] = some_function();
}
Various sized arrays can be directly randomly generated:
use rand; // 0.7.3
fn main() {
let my_array: [u64; 8] = rand::random();
println!("{:?}", my_array);
}
Currently, this only works for arrays of size from 0 to 32 (inclusive). Beyond that, you will want to see related questions:
How can I initialize an array using a function?
What is the proper way to initialize a fixed length array?
The other solution is nice and short, but does not apply to the case where you need to initialize an array of random numbers in a specific range. So, here's an answer that addresses that case.
use rand::{thread_rng, Rng};
fn main() {
let a = [(); 8].map(|_| thread_rng().gen_range(0.0..1.0));
println!("The array of random float numbers between 0.0 and 1.0 is: {:?}", a);
}
I would be happy to know if there's a better (shorter and more efficient) solution than this one.

Rust String concatenation [duplicate]

This question already has answers here:
How do I concatenate strings?
(9 answers)
Closed 7 years ago.
I started programming with Rust this week and I am having a lot of problems understanding how Strings work.
Right now, I am trying to do a simple program that prints a list of players appending their order(for learning purposes only).
let res : String = pl.name.chars().enumerate().fold(String::new(),|res,(i,ch)| -> String {
res+=format!("{} {}\n",i.to_string(),ch.to_string());
});
println!("{}", res);
This is my idea, I know I could just use a for loop but the objective is to understand the different Iterator functions.
So, my problem is that the String concatenation does not work.
Compiling prueba2 v0.1.0 (file:///home/pancho111203/projects/prueba2)
src/main.rs:27:13: 27:16 error: binary assignment operation `+=` cannot be applied to types `collections::string::String` and `collections::string::String` [E0368]
src/main.rs:27 res+=format!("{} {}\n",i.to_string(),ch.to_string());
^~~
error: aborting due to previous error
Could not compile `prueba2`.
I tried using &str but it is not possible to create them from i and ch values.
First, in Rust x += y is not overloadable, so += operator won't work for anything except basic numeric types. However, even if it worked for strings, it would be equivalent to x = x + y, like in the following:
res = res + format!("{} {}\n",i.to_string(),ch.to_string())
Even if this were allowed by the type system (it is not because String + String "overload" is not defined in Rust), this is still not how fold() operates. You want this:
res + &format!("{} {}\n", i, ch)
or, as a compilable example,
fn main(){
let x = "hello";
let res : String = x.chars().enumerate().fold(String::new(), |res, (i, ch)| {
res + &format!("{} {}\n", i, ch)
});
println!("{}", res);
}
When you perform a fold, you don't reassign the accumulator variable, you need to return the new value for it to be used on the next iteration, and this is exactly what res + format!(...) do.
Note that I've removed to_string() invocations because they are completely unnecessary - in fact, x.to_string() is equivalent to format!("{}", x), so you only perform unnecessary allocations here.
Additionally, I'm taking format!() result by reference: &format!(...). This is necessary because + "overload" for strings is defined for String + &str pair of types, so you need to convert from String (the result of format!()) to &str, and this can be done simply by using & here (because of deref coercion).
In fact, the following would be more efficient:
use std::fmt::Write;
fn main(){
let x = "hello";
let res: String = x.chars().enumerate().fold(String::new(), |mut res, (i, ch)| {
write!(&mut res, "{} {}\n", i, ch).unwrap();
res
});
println!("{}", res);
}
which could be written more idiomatically as
use std::fmt::Write;
fn main(){
let x = "hello";
let mut res = String::new();
for (i, ch) in x.chars().enumerate() {
write!(&mut res, "{} {}\n", i, ch).unwrap();
}
println!("{}", res);
}
(try it on playpen)
This way no extra allocations (i.e. new strings from format!()) are created. We just fill the string with the new data, very similar, for example, to how StringBuilder in Java works. use std::fmt::Write here is needed to allow calling write!() on &mut String.
I would also suggest reading the chapter on strings in the official Rust book (and the book as a whole if you're new to Rust). It explains what String and &str are, how they are different and how to work with them efficiently.

Resources