This program takes as input a sentence " Add name to dept", passes the string into a function that splits the string by whitespace into a vector that then is inserted into a hashmap that is suppose to retain the values of name and dept, which it does for 1 input. On the second input only the first word "Add" is printed. Are there any glaring missteps that may cause this odd output?
use std::io;
use std::collections::HashMap;
fn main() {
let mut input = String::new();
let mut db: HashMap<String,String> = HashMap::new();
loop {
println!("'Add <name> to <department>'");
io::stdin().read_line(&mut input).expect("Not input");
add_to_hashmap(&input, &mut db);
}
}
fn add_to_hashmap(input: &String, db: &mut HashMap<String,String>){
let v: Vec<&str> = input.split(" ").collect();
db.insert(v[1].to_string(),v[3].to_string());
for (name, dept) in db{
println!("{}, {}", name, dept);
}
}
Investigation
To diagnose this I added a dbg! call to check the value of input each time add_to_hashmap is called.
let v: Vec<&str> = dbg!(input).split(" ").collect();
The first time it prints:
'Add <name> to <department>'
Add john to math
[src/main.rs:13] input = "Add john to math\n"
john, math
The second time:
'Add <name> to <department>'
Add bill to science
[src/main.rs:13] input = "Add john to math\nAdd bill to science\n"
john, math
Diagnosis
input isn't being cleared. read_line doesn't erase the input buffer; it just appends to it.
In the documentation you can see that the example code clears the buffer after each call:
use std::io::{self, BufRead};
let mut cursor = io::Cursor::new(b"foo\nbar");
let mut buf = String::new();
// cursor is at 'f'
let num_bytes = cursor.read_line(&mut buf)
.expect("reading from cursor won't fail");
assert_eq!(num_bytes, 4);
assert_eq!(buf, "foo\n");
buf.clear();
// cursor is at 'b'
let num_bytes = cursor.read_line(&mut buf)
.expect("reading from cursor won't fail");
assert_eq!(num_bytes, 3);
assert_eq!(buf, "bar");
buf.clear();
// cursor is at EOF
let num_bytes = cursor.read_line(&mut buf)
.expect("reading from cursor won't fail");
assert_eq!(num_bytes, 0);
assert_eq!(buf, "");
Related
I am attempting to relearn data-science in rust.
I have a Vec<String> that includes a delimiter "|" and a new line "!end".
What I'd like to end up with is Vec<Vec<String>> that can be put into a 2D ND array.
I have this python Code:
file = open('somefile.dat')
lst = []
for line in file:
lst += [line.split('|')]
df = pd.DataFrame(lst)
SAMV2FinalDataFrame = pd.DataFrame(lst,columns=column_names)
And i've recreated it here in rust:
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
let file = File::open(filename).expect("no such file");
let buf = BufReader::new(file);
buf.lines()
.map(|l| l.expect("Could not parse line"))
.collect()
}
fn main() {
let lines = lines_from_file(".dat");
let mut new_arr = vec![];
//Here i get a lines immitable borrow
for line in lines{
new_arr.push([*line.split("!end")]);
}
// here i get expeected closure found str
let x = lines.split("!end");
let array = Array::from(lines)
what i have: ['1','1','1','end!','2','2','2','!end']
What i need: [['1','1','1'],['2','2','2']]
Edit: also why when i turbo fish does it make it disappear on Stack Overflow?
I think part of the issue you ran into was due how you worked with arrays. For example, Vec::push will only add a single element so you would want to use Vec::extend instead. I also ran into a few cases of empty strings due to splitting by "!end" would leave trailing '|' on the ends of substrings. The errors were quite strange, I am not completely sure where the closure came from.
let lines = vec!["1|1|1|!end|2|2|2|!end".to_string()];
let mut new_arr = Vec::new();
// Iterate over &lines so we don't consume lines and it can be used again later
for line in &lines {
new_arr.extend(line.split("!end")
// Remove trailing empty string
.filter(|x| !x.is_empty())
// Convert each &str into a Vec<String>
.map(|x| {
x.split('|')
// Remove empty strings from ends split (Ex split: "|2|2|2|")
.filter(|x| !x.is_empty())
// Convert &str into owned String
.map(|x| x.to_string())
// Turn iterator into Vec<String>
.collect::<Vec<_>>()
}));
}
println!("{:?}", new_arr);
I also came up with this other version which should handle your use case better. The earlier approach dropped all empty strings, while this one should preserve them while correctly handling the "!end".
use std::io::{self, BufRead, BufReader, Read, Cursor};
fn split_data<R: Read>(buffer: &mut R) -> io::Result<Vec<Vec<String>>> {
let mut sections = Vec::new();
let mut current_section = Vec::new();
for line in BufReader::new(buffer).lines() {
for item in line?.split('|') {
if item != "!end" {
current_section.push(item.to_string());
} else {
sections.push(current_section);
current_section = Vec::new();
}
}
}
Ok(sections)
}
In this example, I used Read for easier testing, but it will also work with a file.
let sample_input = b"1|1|1|!end|2|2|2|!end";
println!("{:?}", split_data(&mut Cursor::new(sample_input)));
// Output: Ok([["1", "1", "1"], ["2", "2", "2"]])
// You can also use a file instead
let mut file = File::new("somefile.dat");
let solution: Vec<Vec<String>> = split_data(&mut file).unwrap();
playground link
I'd like to find a keyword in a text file or in a variable. I want to get user input and paste it into a text file.
my code:
use std::fs;
fn main() {
let mut user_name = String::new();
println!("Hello what is your name: ");
std::io::stdin().read_line(&mut name).unwrap();
fs::write("files/user_name.txt", user_name).unwrap();
let mut user_wish = String::new();
println!("What is your wish: ");
std::io::stdin().read_line(&mut user_wish).unwrap();
fs::write("files/user_wish.txt", user_wish).unwrap();
}
I don't know how to find keywords like my and name in the text file user_wish.txt so that I can list user_name.
use std::fs;
static KEYWORDS: &'static [&'static str] = &["my", "name"];
fn main() {
let mut user_name = String::new();
println!("Hello what is your name: ");
std::io::stdin().read_line(&mut user_name).unwrap();
// ---
// fs::write<P, C>(path: P, contents: C)
// -- Note contents takes ownership of `C` / `user_clone`
// that memory will be consumed after it finishes
// so we give it clone(), so we can still access `user_name`
fs::write("files/user_name.txt", user_name.clone()).unwrap();
for kword in KEYWORDS {
if user_name.contains(kword) {
println!("contains keyword");
}
}
let mut user_wish = String::new();
println!("What is your wish: ");
std::io::stdin().read_line(&mut user_wish).unwrap();
fs::write("files/user_wish.txt", user_wish).unwrap();
}
I want to input a vector of vectors of u32 using the std::io library.
Here is what I did and it obviously doesn't work because the parse method can't turn a String into a Vec:
fn main() {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let input: Vec<Vec<u32> = match input.trim().parse() {
Ok(num) => num,
Err(_) => vec![vec![0]],
};
}
If you have another way to make it work I'll be happy to try it :)
For an input of the stile:
1 2 3 4
5 6 7 8
You could use something like the following example. Notice the use of BufRead
use std::io::BufRead;
fn read_input() -> std::io::Result<Vec<Vec<u64>>> {
let stdin = std::io::stdin();
let mut lines = stdin.lock().lines();
let mut res = Vec::new();
while let Some(line) = lines.next() {
let line = line?;
let items = line
.split(" ")
.map(|n| n.parse().expect("an u64 parseable number"))
.collect();
res.push(items);
}
Ok(res)
}
Playground
My input data is structured as follows:
label_1
value_1
label_2
value_2
...
And my end goal is to read that data into a HashMap
My current working approach is to put even and odd lines in two separate vectors and then read from both vectors to add to Hashmap.
use std::io;
use std::io::prelude::*;
use std::collections::HashMap;
fn main() {
let mut labels: Vec<String> = Vec::new();
let mut values: Vec<String> = Vec::new();
let stdin = io::stdin();
/* Read lines piped from stdin*/
for (i, line) in stdin.lock().lines().enumerate() {
if i % 2 == 0 {
/* store labels (even lines) in labels vector */
labels.push(line.unwrap());
} else {
/* Store values (odd lines) in values vector */
values.push(line.unwrap());
}
}
println!("number of labels: {}", labels.len());
println!("number of values: {}", values.len());
/* Zip labels and values into one iterator */
let double_iter = labels.iter().zip(values.iter());
/* insert (label: value) pairs into hashmap */
let mut records: HashMap<&String, &String> = HashMap::new();
for (label, value) in double_iter {
records.insert(label, value);
}
}
I would like ask how to achieve this result without going though an intermediary step with vectors ?
You can use .tuples() from the itertools crate:
use itertools::Itertools;
use std::io::{stdin, BufRead};
fn main() {
for (label, value) in stdin().lock().lines().tuples() {
println!("{}: {}", label.unwrap(), value.unwrap());
}
}
See also:
This answer on "Are there equivalents to slice::chunks/windows for iterators to loop over pairs, triplets etc?"
You can manually advance an iterator with .next()
use std::io;
use std::io::prelude::*;
use std::collections::HashMap;
fn main() {
let stdin = io::stdin();
let mut lines = stdin.lock().lines();
let mut records = HashMap::new();
while let Some(label) = lines.next() {
let value = lines.next().expect("No value for label");
records.insert(label.unwrap(), value.unwrap());
}
}
Playground
How about:
fn main() {
let lines = vec![1,2,3,4,5,6];
let mut records = std::collections::HashMap::new();
for i in (0..lines.len()).step_by(2) {
// make sure the `i+1` is existed
println!("{}{}", lines[i], lines[i + 1]);
records.insert(lines[i], lines[i + 1]);
}
}
I'm quite new in rust and I have no idea how to workaround.
The code is:
let input = File::open(file_path)?;
let buffered = BufReader::new(input);
let line_count = buffered.lines().count();
for (nr, line) in buffered.lines().enumerate() {
print!("\rLOADED: [{}/{}]", nr, line_count);
// do something...
}
The error I've got:
let buffered = BufReader::new(input);
-------- move occurs because `buffered` has type `std::io::BufReader<std::fs::File>`, which does not implement the `Copy` trait
let line_count = buffered.lines().count();
-------- value moved here
for (nr, line) in buffered.lines().enumerate() {
^^^^^^^^ value used here after move
Please help, I'm stuck on this.
Calling BufReader::new(file_handle); consumes the file_handle and calling buffered.lines() consumes buffered. There might be a more efficient, clever, or elegant way to do this but you can open and iterate over the file twice if you want to first get the full line count:
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn main() -> io::Result<()> {
let file_path = "input.txt";
let input = File::open(file_path)?;
let buffered = BufReader::new(input);
let line_count = buffered.lines().count();
let input = File::open(file_path)?;
let buffered = BufReader::new(input);
for (nr, line) in buffered.lines().enumerate() {
println!("LOADED: [{}/{}]", nr, line_count);
}
Ok(())
}