I want something like this in Rust but I don't understand the compile errors:
fn main() {
let apple: String = String::from("apple");
let accle: String = String::from("accle");
let apple_vec: Vec<char> = apple.chars().collect();
let accle_vec: Vec<char> = accle.chars().collect();
let counter = 0;
for j in accle_vec {
for i in apple_vec {
// if i == j{
counter++;
// }
}
}
println!("counter is {}", counter);
}
I want to compare the characters of two arrays, one by one, and count every time there is a mismatch.
There are several things going on here, so let's break this down.
First error we hit is:
error: expected expression, found `+`
--> src/main.rs:12:21
|
12 | counter++;
| ^ expected expression
error: could not compile `playground` due to previous error
This means that this is invalid syntax. That is because rust does not have ++, instead we can use counter += 1 or counter = counter + 1. I'll use the first.
Making this change, we get a few errors, but concentrating on the counter, we see:
error[E0384]: cannot assign twice to immutable variable `counter`
--> src/main.rs:12:13
|
8 | let counter = 0;
| -------
| |
| first assignment to `counter`
| help: consider making this binding mutable: `mut counter`
...
12 | counter += 1;
| ^^^^^^^^^^^^ cannot assign twice to immutable variable
Some errors have detailed explanations: E0382, E0384.
For more information about an error, try `rustc --explain E0382`.
The advice we get is sound - the counter is declared as immutable and we are trying to mutate it. We should declare it as mutable. So let mut counter = 0
Lastly, we get the following error:
error[E0382]: use of moved value: `apple_vec`
--> src/main.rs:10:18
|
5 | let apple_vec: Vec<char> = apple.chars().collect();
| --------- move occurs because `apple_vec` has type `Vec<char>`, which does not implement the `Copy` trait
...
10 | for i in apple_vec {
| ^^^^^^^^^
| |
| `apple_vec` moved due to this implicit call to `.into_iter()`, in previous iteration of loop
| help: consider borrowing to avoid moving into the for loop: `&apple_vec`
|
note: this function takes ownership of the receiver `self`, which moves `apple_vec`
This is because iterating over the inner vector in this way would drain it on the first pass and on the second pass of the outer loop inner iteration would be impossible. In order to prevent this, you can borrow the vec for the iteration instead like for i in &apple_vec
Putting this all together would yield the following code:
fn main() {
let apple: String = String::from("apple");
let accle: String = String::from("accle");
let apple_vec: Vec<char> = apple.chars().collect();
let accle_vec: Vec<char> = accle.chars().collect();
let mut counter = 0;
for j in &accle_vec {
for i in &apple_vec {
if i == j {
counter += 1;
}
}
}
println!("counter is {}", counter);
}
This is how I would write the code, in a more functional manner:
use std::collections::HashSet;
fn main() {
let apple: String = String::from("appleb");
let accle: String = String::from("acclea");
// this goes through both strings at the same time
// eg. a==a p!=c p!=c
// I think maybe this was your goal
// It'll find matches where the same character is in the same position in
// both strings
// Iterate through the characters of the first string
let count1 = apple.chars()
// zip in the char from the second string
.zip(accle.chars())
// Now you have the char from the first and the second strings
// .. and you're still iterating.
// Filter so that you only keep entries where both chars are the same
.filter(|(a, b)| a == b)
// Count the output
.count();
// This is like having nested loops, where it'll compare
// the first 'a' to every letter in the second word
// eg. 'a' == 'a', 'a' != 'c'
// Using the above values, it returns one extra because there are two
// matches for 'a' in "acclea"
// This is what your original code was doing I think
// iterate through the chars of the first string
let count2 = apple.chars()
// For every char from the first string, iterate through
// *all* the chars of the second string
.flat_map(|a| accle.chars()
// Instead of just returning the char from the second string
// Return a tuple containing the char from the first string and
// the second
.map(move |b| (a, b)))
// Only accept instances where both chars are the same
.filter(|(a, b)| a == b)
// Count them
.count();
// To just see if a char from "apple" is in "accle" (anyhere)
// I would just write
let count3 = apple.chars()
// Only accept a char from "apple" if it can also be found in
// "accle"
.filter(|a| accle.chars().any(|b| *a == b))
.count();
// If speed was important and words were long, you could get all the chars
// from "accle" and put them in a HashSet
let set: HashSet<char> = accle.chars().collect();
let count4 = apple.chars()
.filter(|a| set.contains(a))
.count();
println!("Count1 is {}", count1);
println!("Count2 is {}", count2);
println!("Count3 is {}", count3);
println!("Count4 is {}", count4);
}
Playground link
Related
I'm trying to peek at the char in-front of my current location whilst iterating over a &str.
let myStr = "12345";
let mut iter = myStr.chars().peekable();
for c in iter {
let current: char = c;
let next: char = *iter.peek().unwrap_or(&'∅');
}
I will be passing this char into a method down the line. However, even this MRE produces a borrow after move error that I'm not sure how to get past.
error[E0382]: borrow of moved value: `iter`
--> src/lib.rs:7:27
|
4 | let mut iter = myStr.chars().peekable();
| -------- move occurs because `iter` has type `Peekable<Chars<'_>>`, which does not implement the `Copy` trait
5 | for c in iter {
| ---- `iter` moved due to this implicit call to `.into_iter()`
6 | let current: char = c;
7 | let next: char = *iter.peek().unwrap_or(&'∅');
| ^^^^^^^^^^^ value borrowed here after move
|
note: this function takes ownership of the receiver `self`, which moves `iter`
--> /home/james/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:267:18
|
267 | fn into_iter(self) -> Self::IntoIter;
Any idea what's going on here? I've tried various combinations of referencing and dereferencing but nothing I've tried seems to work.
The iterator is moved into the for loop. You cannot manually manipulate an iterator inside a for loop. However, the for loop can be replaced by while let:
while let Some(c) = iter.next() {
let current: char = c;
let next: char = *iter.peek().unwrap_or(&'∅');
}
Playground.
If you can work with slices, it will get much easier with windows():
let slice = ['1', '2', '3', '4', '5'];
let iter = slice.windows(2);
for arr in iter {
let current = arr[0];
let next = arr[1];
}
Playground
I have the following function. It is given a file. It should return a random line from the file as a string.
fn get_word(word_list: File) -> String {
let reader = BufReader::new(word_list);
let lines = reader.lines();
let word_count = lines.count();
let y: usize = thread_rng().gen_range(0, word_count - 1);
let element = lines.nth(y);
match element {
Some(x) => println!("Result: {}", x.unwrap()),
None => println!("Error with nth"),
}
let word = String::new(""); // Once the error is gone. I would create the string.
return word;
}
But I keep getting this error:
93 | let lines = reader.lines();
| ----- move occurs because `lines` has type `std::io::Lines<BufReader<File>>`, which does not implement the `Copy` trait
94 | let word_count = lines.count();
| ------- `lines` moved due to this method call
...
99 | let element = lines.nth(y);
| ^^^^^^^^^^^^ value borrowed here after move
|
I am new to Rust and have been learning by try and error. I don't know how to access the data after I have called the count function. If there is another method to accomplish what I want, I would gladly welcome it.
The .count() method consumes the iterator. From the documentation
Consumes the iterator, counting the number of iterations and returning it.
This method will call next repeatedly until None is encountered, returning the number of times it saw Some. Note that next has to be called at least once even if the iterator does not have any elements.
In other words, it reads the file content and discards it. If you want to get the Nth line, then you have to re-read the file using another iterator instance.
If your file is small, you can save the read lines in a vector:
let lines = reader.lines().collect::<Vec<String>>();
Then the length of the vector is the number of lines and you can avoid re-reading the file, but if it's a large file you may end-up crashing with "out of memory" error. In that case you should re-read the file content, or use a better strategy such as indexing where the new lines are, so you can jump straight to the new line, without having to re-read a lot of data.
The value returned by lines is an iterator, which reads the file sequentially. To count the number of lines, the iterator is consumed: self is taken by value; ownership is transferred into the count() function. So you can't rewind and then request the nth line.
The easiest solution is to read all the lines into a vector:
let lines = reader.lines().collect::<Vec<String>>();
let word_count = lines.len();
let y: usize = thread_rng().gen_range(0, word_count - 1);
let word = lines[y].clone();
return word;
Notice the clone call: you can't simply write return lines[y]; because you'd be borrowing the string from the vector, but the vector is destroyed as soon as the function returns. By returning a clone of the string, this is avoided.
(to_owned or even to_string would also work. You can also avoid a copy by using swap_remove; I'm not sure there is a more elegant way to move one element from a vector and discard the rest.)
Note that counting the lines and then selecting one of them requires you to either rewind the iterator and go through it twice (once to count and once to select), or to store everything in memory first (e.g. with .collect::<Vec<_>>). Selecting a random line from the list can however be done in a single pass by randomly choosing on each line whether to keep the currently selected line or replacing it with the latest read line:
fn get_word(word_list: File) -> String {
let reader = BufReader::new(word_list);
let lines = reader.lines();
let mut selected = lines.next().unwrap();
let mut count = 0;
for l in lines {
count += 1;
if thread_rng().gen_range (0, count) == 0 {
selected = l;
}
}
match selected {
Ok(x) => return x,
Err(_) => {
print!("Error get_word");
return String::new();
}
}
}
Or of course the simplest way is to just use choose:
fn get_word(word_list: File) -> String {
use rand::seq::IteratorRandom;
let reader = BufReader::new(word_list);
match reader.lines.choose (thread_rng()) {
Some (Ok (x)) => return x,
_ => {
print!("Error get_word");
return String::new();
}
}
}
In order to solve this problem I used the solution given of using .collect::<Vec<String>> but the whole solution needs a little more work. At least in my case.
First: .lines returns a Iterator of type Result<std::string::String, std::io::Error>.
Second: To access the value of this vector I have to borrow it with &.
Here the working function:
fn get_word(word_list: File) -> String {
let reader = BufReader::new(word_list);
let lines = reader.lines().collect::<Vec<_>>();
let word_count = lines.len();
let y: usize = thread_rng().gen_range(0, word_count - 1);
match &lines[y] {
Ok(x) => return x.to_string(),
Err(_) => {
print!("Error get_word");
return String::new();
}
}
}
In the below code, I understand that a borrow has been done by the call of zipped.filter. What I don't understand is how to fix it, if I want to use zipped again later.
I'm new to Rust, so if there are other problems or strange misuse of idioms in this code, I'm interested in that as well, but primarily about how to make the borrow work twice here.
Code:
fn main() {
let data : Vec<String> = vec!["abc".to_string(), "def".to_string(), "bbc".to_string()];
for s1 in &data {
for s2 in &data {
let zipped = s2.chars().zip(s1.chars());
// Did a diff of the two strings have only one character different?
if zipped.filter(|(a,b)| a != b).count() == 1 {
let newStr = zipped.filter(|(a,b)| a == b).map(|(a,_)| a).collect::<String>();
println!("String without the different character: {}", newStr);
}
}
}
}
Error:
error[E0382]: use of moved value: `zipped`
--> a.rs:10:30
|
6 | let zipped = s2.chars().zip(s1.chars());
| ------ move occurs because `zipped` has type `Zip<Chars<'_>, Chars<'_>>`, which does not implement the `Copy` trait
...
9 | if zipped.filter(|(a,b)| a != b).count() == 1 {
| ---------------------- `zipped` moved due to this method call
10 | let newStr = zipped.filter(|(a,b)| a == b).map(|(a,_)| a).collect::<String>();
| ^^^^^^ value used here after move
|
note: this function consumes the receiver `self` by taking ownership of it, which moves `zipped`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
You can always zipped.clone(), which clones the iterator so that you move the clone when you first call to filter(...).count(). The original zipped remains untouched and you'll move it by the second filter(...).collect() .
Note that it doesn't clone any data because iterators are lazy, copying an iterator means to copy its logic (so it copies logic of .chars(), .zip() etc which is just a bunch of function pointers, not the data).
fn main() {
let data : Vec<String> = vec!["abc".to_string(), "def".to_string(), "bbc".to_string()];
for s1 in &data {
for s2 in &data {
let zipped = s2.chars().zip(s1.chars());
// << THE CHANGE IS HERE
if zipped.clone().filter(|(a,b)| a != b).count() == 1 {
let newStr = zipped.filter(|(a,b)| a == b).map(|(a,_)| a).collect::<String>();
println!("String without the different character: {}", newStr);
}
}
}
}
Output:
String without the different character: bc
String without the different character: bc
I am trying to modify a mutable sums: Vec<i64> while iterating over it. The loop code is as follows:
for (j, &mut sum) in sums.iter_mut().enumerate() {
if !(j == i) {
sum += n;
}
}
And here is the error I get:
error[E0384]: re-assignment of immutable variable `sum`
--> mini_max_sum.rs:27:17
|
25 | for (j, &mut sum) in sums.iter_mut().enumerate() {
| --- first assignment to `sum`
26 | if !(j == i) {
27 | sum += n;
| ^^^^^^^^ re-assignment of immutable variable
This seems totally arcane to me. Rust lets me mutably borrow sum from sums, but the compiler prevents me from actually modifying it. Omitting .enumerate() does not even alter the resulting error code.
I would like to know how to fix the loop.
I don't know why you decided to add &mut to the pattern for the loop variable, but that's the problem. You need to take the mutable reference directly and then dereference it when you increment it:
fn main() {
let mut sums = vec![1, 2, 3];
let i = 0;
let n = 0;
for (j, sum) in sums.iter_mut().enumerate() {
if j != i {
*sum += n;
}
}
}
With &mut in the pattern, you are actually destructuring the variable and removing the mutable reference. If you print the type of your sum variable, you'll see that it's an i64.
I've got a piece of code which is supposed to check if two sentences are "too similar", as defined by a heuristic made clearest by the code.
fn too_similar(thing1: &String, thing2: &String) -> bool {
let split1 = thing1.split_whitespace();
let split2 = thing2.split_whitespace();
let mut matches = 0;
for s1 in split1 {
for s2 in split2 {
if s1.eq(s2) {
matches = matches + 1;
break;
}
}
}
let longer_length =
if thing1.len() > thing2.len() {
thing1.len()
} else {
thing2.len()
};
matches > longer_length / 2
}
However, I'm getting the following compilation error:
error[E0382]: use of moved value: `split2`
--> src/main.rs:7:19
|
7 | for s2 in split2 {
| ^^^^^^ value moved here in previous iteration of loop
|
= note: move occurs because `split2` has type `std::str::SplitWhitespace<'_>`, which does not implement the `Copy` trait
I'm not sure why split2 is getting moved in the first place, but what's the Rust way of writing this function?
split2 is getting moved because iterating with for consumes the iterator and since the type does not implement Copy, Rust isn't copying it implicitly.
You can fix this by creating a new iterator inside the first for:
let split1 = thing1.split_whitespace();
let mut matches = 0;
for s1 in split1 {
for s2 in thing2.split_whitespace() {
if s1.eq(s2) {
matches = matches + 1;
break;
}
}
}
...
You can also rewrite the matches counting loop using some higher order functions available in the Iterator trait:
let matches = thing1.split_whitespace()
.flat_map(|c1| thing2.split_whitespace().filter(move |&c2| c1 == c2))
.count();
longer_length can also be written as:
let longer_length = std::cmp::max(thing1.len(), thing2.len());
There are possibly some better ways to do the word comparison.
If the phrases are long, then iterating over thing2's words for every word in thing1 is not very efficient. If you don't have to worry about words which appear more than once, then HashSet may help, and boils the iteration down to something like:
let words1: HashSet<&str> = thing1.split_whitespace().collect();
let words2: HashSet<&str> = thing2.split_whitespace().collect();
let matches = words1.intersection(&words2).count();
If you do care about repeated words you probably need a HashMap, and something like:
let mut words_hash1: HashMap<&str, usize> = HashMap::new();
for word in thing1.split_whitespace() {
*words_hash1.entry(word).or_insert(0) += 1;
}
let matches2: usize = thing2.split_whitespace()
.map(|s| words_hash1.get(s).cloned().unwrap_or(0))
.sum();