Parsing text file lines into numbers using std::iter::Iterator map - file

I'm trying to read and parse a text file in Rust. Each line is a signed integer. I'm able to do it using for line in lines iteration but I'm unable to do it with a iter().map(|l| ...) one-liner. I'm getting a
expected `&core::result::Result<collections::string::String, std::io::error::Error>`,
found `core::result::Result<_, _>`
when I try to pattern match Ok(s) => match s.parse() but I'm unable to get to the bottom of what I am doing wrong. The whole example is below. The code on the bottom is the code that is producing the error.
Can anyone tell what I am doing wrong?
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::path::Path;
fn main() {
// Create a path to the desired file
let path = Path::new("input/numbers.txt");
let display = path.display();
// Open the path in read-only mode, returns `io::Result<File>`
let file = match File::open(&path) {
// The `description` method of `io::Error` returns a string that describes the error
Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
Ok(file) => file,
};
// Collect all lines into a vector
let reader = BufReader::new(file);
let lines: Vec<_> = reader.lines().collect();
// Works.
let mut nums = vec![];
for l in lines {
println!("{:?}", l);
let num = match l {
Ok(s) => match s.parse() {
Ok(i) => i,
Err(_) => 0
},
Err(_) => 0
};
nums.push(num);
}
// Doesn't work!
let nums: Vec<i64> = lines.iter().map(|l| match l {
Ok(s) => match s.parse() {
Ok(i) => i,
Err(_) => 0
},
Err(_) => 0
});
}

Let's look at the complete error message, which points to the error for us:
<anon>:5:9: 5:14 error: mismatched types:
expected `&core::result::Result<&str, ()>`,
found `core::result::Result<_, _>`
(expected &-ptr,
found enum `core::result::Result`) [E0308]
<anon>:5 Ok(s) => match s.parse() {
^~~~~
The compiler is expecting a &Result, but found a Result, and the issue is with the Ok(s) pattern. The type of l is a reference to a Result because you are using iter - which returns an iterator of references to the items in the vector.
The shortest fix is to add a & to the pattern match for the closure variable:
fn main() {
let lines: Vec<Result<_, ()>> = vec![Ok("1"), Ok("3"), Ok("5")];
// HERE V
let nums: Vec<i64> = lines.iter().map(|&l| match l {
Ok(s) => match s.parse() {
Ok(i) => i,
Err(_) => 0
},
Err(_) => 0
}).collect();
println!("{:?}", nums)
}
I also had to add collect to go back to a Vec.
The other change you could make would be to consume the input vector using into_iter and then iterate on each value in the vector:
// HERE V~~~~
let nums: Vec<i64> = lines.into_iter().map(|l| match l {
And for good measure, you could use ok, and_then, and unwrap_or to say the same thing a bit more succinctly:
let nums: Vec<i64> = lines.into_iter().map(|l| {
l.ok().and_then(|s| s.parse().ok()).unwrap_or(0)
}).collect();

Related

Is it possible to "combine" similar types in rust match assignment? [duplicate]

This question already has an answer here:
Multiple return types in rust [duplicate]
(1 answer)
Closed 10 months ago.
I'm trying to implement a function in rust that iterates either on the StdIn Lines or a BuffReader Lines.
Both types have lines.next() method. After that, the code is just String manipulation that works.
I originally made it worked (with repetitive and implicitly-typed branches) but the two branches had similar code. This gave me the impression I can DRY/refactor my code better.
The problem is (I think) that my two match branches, in main, do not return exactly the same type. Is it possible to make them "stick together" because I'm only using one common method? (It reminds me of Python duck typing)
Here's my refacto try:
use clap::Parser;
use exitcode;
use std::any::Any;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Lines, Write};
fn colorist(lines: &mut dyn Any, mut writer: impl std::io::Write) {
while let Some(line_wrap) = lines.next() {
match line_wrap {
Ok(line) => {
// ...
let buffer = "Some value base on line";
match writeln!(writer, "{}", buffer){
Ok(()) => (),
Err(e) => {eprintln!("{:?}", e);}
};
},
Err(e) => {eprintln!("{:?}", e);}
}
}
}
fn main() {
let args = Args::parse(); // have one '--file' arg being a filepath or is a empty str ("")
let mut writer = io::stdout();
let mut lines: Lines<dyn Any> = match args.file.is_empty() { // either read from stdin or read the file
false => {
let file = File::open(args.file).unwrap();
let reader = BufReader::new(file);
reader.lines() // type `std::io::Lines<BufReader<File>>`
},
true => {
let stdin = io::stdin();
stdin.lock().lines() // type `std::io::Lines<StdinLock<'_>>`
}
};
colorist(&mut lines, &mut writer);
}
This is a toy project, and I'm still learning rust on my free time. I might have some bad designs there and here.
As #PitaJ & #Chayim-friedman suggested, Either solved my problem.
I ended up with:
use either::*;
fn colorist(lines: &mut Either<Lines<BufReader<File>>, Lines<StdinLock>>, mut writer: impl std::io::Write) {...}
fn main() {
let args = Args::parse();
let mut writer = io::stdout();
let stdin = io::stdin();
let mut lines = match args.file.is_empty(){
false => {
let file = File::open(args.file).unwrap();
let reader = BufReader::new(file);
either::Left(reader.lines())
},
true => {
either::Right(stdin.lock().lines())
}
};
colorist(&mut lines, &mut writer);
match write!(writer, "{}", RESET){
_ => (),
};
let _ = writer.flush();
std::process::exit(exitcode::OK);
}

Rust return leveldb Database instance

I want to use the leveldb database in Rust. Everything works fine if I have all the code in one function, but I want to split the code up and have different functions for creating an entry and reading from the db. The easiest method I thought of, was to return the database instance I created in create_database and then submit it as a parameter to the function. The Problem is that Rust doesn't allow me to use Database as a type.
This works:
use std::{env, fs};
use leveldb::database::Database;
use leveldb::iterator::Iterable;
use leveldb::kv::KV;
use leveldb::options::{Options, WriteOptions, ReadOptions};
pub fn create_database() {
let mut dir = env::current_dir().unwrap();
dir.push("demo");
let path_buf = dir.clone();
fs::create_dir_all(dir).unwrap();
let path = path_buf.as_path();
let mut options = Options::new();
options.create_if_missing = true;
// Create Database
let database = match Database::open(path, options) {
Ok(db) => {db},
Err(e) => {panic!("Failed to open database: {:?}", e)}
};
// Read from database
let read_opts = ReadOptions::new();
let res = database.get(read_opts, 1);
match res {
Ok(data) => {
assert!(data.is_some());
assert_eq!(data, Some(vec![1]));
}
Err(e) => {panic!("Failed to read from database: {:?}", e)}
};
let read_opts = ReadOptions::new();
let mut iter = database.iter(read_opts);
let entry = iter.next();
assert_eq!(
entry,
Some((1, vec![1]))
);
// Write to database
let write_ops = WriteOptions::new();
match database.put(write_ops, 1, &[1]) {
Ok(_) => {()},
Err(e) => {panic!("Failed to write to database: {:?}", e)}
};
}
But this doesn't:
use std::{env, fs};
use leveldb::database::Database;
use leveldb::iterator::Iterable;
use leveldb::kv::KV;
use leveldb::options::{Options, WriteOptions, ReadOptions};
pub fn create_database() -> Database {
let mut dir = env::current_dir().unwrap();
dir.push("demo");
let path_buf = dir.clone();
fs::create_dir_all(dir).unwrap();
let path = path_buf.as_path();
let mut options = Options::new();
options.create_if_missing = true;
// Create Database
let database = match Database::open(path, options) {
Ok(db) => {db},
Err(e) => {panic!("Failed to open database: {:?}", e)}
};
return database;
}
pub fn get(database: Database) {
// Read from database
let read_opts = ReadOptions::new();
let res = database.get(read_opts, 1);
match res {
Ok(data) => {
assert!(data.is_some());
assert_eq!(data, Some(vec![1]));
}
Err(e) => {panic!("Failed to read from database: {:?}", e)}
};
let read_opts = ReadOptions::new();
let mut iter = database.iter(read_opts);
let entry = iter.next();
assert_eq!(
entry,
Some((1, vec![1]))
);
}
pub fn put(database: Database) {
// Write to database
let write_ops = WriteOptions::new();
match database.put(write_ops, 1, &[1]) {
Ok(_) => {()},
Err(e) => {panic!("Failed to write to database: {:?}", e)}
};
}
If I execute this program I get this error:
error[E0107]: wrong number of type arguments: expected 1, found 0
--> src/db/db.rs:8:29
|
8 | pub fn create_database() -> Database {
| ^^^^^^^^ expected 1 type argument
error[E0107]: wrong number of type arguments: expected 1, found 0
--> src/db/db.rs:27:22
|
27 | pub fn get(database: Database) {
| ^^^^^^^^ expected 1 type argument
error[E0107]: wrong number of type arguments: expected 1, found 0
--> src/db/db.rs:48:22
|
48 | pub fn put(database: Database) {
| ^^^^^^^^ expected 1 type argument
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0107`.
error: could not compile `gcoin`
To learn more, run the command again with --verbose.
I have tried looking for examples to use this database but didn't find anything that matched. In the source code I found out that Database has a generic type, but couldn't find a way to fix my problem.
Solved! Thanks Locke and kmdreko

Assigning Array of objects from const to array

So im trying to populate an Array to then pass a prop to another Component.
const arrayList = (): ArrayList[] => {
let remaining: number = 0
let stateArrayList: String[] = []
for (let i = 0; remaining > i; i++) {
stateArrayList.push("Blah")
}
return (arrayList)
Results in the following error
Type '() => String[]' is missing the following properties from type 'String[]': pop, push, concat, join, and 27 more. TS2322
I have looked around online using the TS2322 issue code, but cant seem to find a resolution.
Any help would be appreciated
You're returning arrayList (the function) but I think you wanted actually to return stateArrayList (the array).
Like this :
const arrayList = (): ArrayList[] => {
let remaining: number = 0
let stateArrayList: String[] = []
for (let i = 0; remaining > i; i++) {
stateArrayList.push("Blah")
}
return stateArrayList
}

ES6: Merge two arrays into an array of objects

I have two arrays that I want to merge together to one array of objects...
The first array is of dates (strings):
let metrodates = [
"2008-01",
"2008-02",
"2008-03",..ect
];
The second array is of numbers:
let figures = [
0,
0.555,
0.293,..ect
]
I want to merge them to make an object like this (so the array items match up by their similar index):
let metrodata = [
{data: 0, date: "2008-01"},
{data: 0.555, date: "2008-02"},
{data: 0.293, date: "2008-03"},..ect
];
So far I do this like so: I create an empty array and then loop through one of the first two arrays to get the index number (the first two arrays are the same length)... But is there an easier way (in ES6)?
let metrodata = [];
for(let index in metrodates){
metrodata.push({data: figures[index], date: metrodates[index]});
}
The easiest way is probably to use map and the index provided to the callback
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = metrodates.map((date,i) => ({date, data: figures[i]}));
console.log(output);
Another option is to make a generic zip function which collates your two input arrays into a single array. This is usually called a "zip" because it interlaces the inputs like teeth on a zipper.
const zip = ([x,...xs], [y,...ys]) => {
if (x === undefined || y === undefined)
return [];
else
return [[x,y], ...zip(xs, ys)];
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = zip(metrodates, figures).map(([date, data]) => ({date, data}));
console.log(output);
Another option is to make a generic map function which accepts more than one source array. The mapping function will receive one value from each source list. See Racket's map procedure for more examples of its use.
This answer might seem the most complicated but it is also the most versatile because it accepts any number of source array inputs.
const isEmpty = xs => xs.length === 0;
const head = ([x,...xs]) => x;
const tail = ([x,...xs]) => xs;
const map = (f, ...xxs) => {
let loop = (acc, xxs) => {
if (xxs.some(isEmpty))
return acc;
else
return loop([...acc, f(...xxs.map(head))], xxs.map(tail));
};
return loop([], xxs);
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = map(
(date, data) => ({date, data}),
metrodates,
figures
);
console.log(output);
If you use lodash, you can use zipWith + ES6 shorthand propery names + ES6 Arrow functions for a one-liner, otherwise see #noami's answer.
const metrodata = _.zipWith(figures, metrodates, (data, date)=> ({ data, date }));

Rust: Lifetime of String from file [duplicate]

This question already has answers here:
Return local String as a slice (&str)
(7 answers)
Closed 7 years ago.
I'm trying to read in some external GLSL code into Rust. The reading works properly, but I run into a lifetime issue in the final expression (in the Ok(_) branch)
error: s does not live long enough
fn read_shader_code(string_path: &str) -> &str {
let path = Path::new(string_path);
let display = path.display();
let mut file = match File::open(&path) {
Err(why) => panic!("Couldn't open {}: {}", display, Error::description(&why)),
Ok(file) => file,
};
let mut s = String::new();
match file.read_to_string(&mut s) {
Err(why) => panic!("couldn't read {}: {}", display, Error::description(&why)),
Ok(_) => &s,
}
}
The string bound to "s" will be deallocated once the function ends ("s" goes out of scope), so you cannot return a reference to its contents outside the function.
The best way is to return the string itself:
fn read_shader_code(string_path: &str) -> String {
let path = Path::new(string_path);
let display = path.display();
let mut file = match File::open(&path) {
Err(why) => panic!("Couldn't open {}: {}", display, Error::description(&why)),
Ok(file) => file,
};
let mut s = String::new();
match file.read_to_string(&mut s) {
Err(why) => panic!("couldn't read {}: {}", display, Error::description(&why)),
Ok(_) => s,
}
}

Resources