Collecting entries from stdin into a Struct and parsing the values as f64 or u64 or i64 or .....etc

Hello again dear fellows from the Rust community.
In my last Topic (Sorting Vector of vectors of f64 - #8 by dzamlo) we talked about creating a vector of vectors from user input for using in a fractional knapsack algorithm. One of the suggestions was that instead of collecting the data as a vector and then pushing each entry into another vector, I should use structs to do this, since it would make the code cleaner.....etc.
Just to remind you, the user would input data in the following manner:
First line would contain the number of items (N) and the total capacity of the knapsack
N following lines would contain the Value and Weight of each item.
These would get parsed and collected into a vector. Now I did the same thing with structs, but there are some things I didn't quite get yet. Here is the code (the text continues after the code).

use std::io;
use std::str::FromStr;
use std::cmp::Ordering;

struct Items {
    value: f64 ,
    weight: f64,
    score: f64,
}

struct Capacity {
    nitems: f64 ,
    maxweight: f64,
}

impl Items {
    fn new() -> Items {
        let values = Items::read_values().unwrap();
        Items {value: values[0], weight: values[1], score: values[0]/values[1]}
    }

    fn read_values<T: FromStr>() -> Result<Vec<T>, T::Err> {
        let mut s = String::new();
        io::stdin().read_line(&mut s).expect("Could not read Item from stdin");
        s.trim().split_whitespace().map(|word| word.parse()).collect()

    }
}

impl Capacity {
    fn new() -> Capacity {
        let values = Capacity::read_values().unwrap();
        Capacity {nitems: values[0], maxweight: values[1]}
    }

    fn read_values<T: FromStr>() -> Result<Vec<T>, T::Err> {
        let mut s = String::new();
        io::stdin().read_line(&mut s).expect("Could not read Item from stdin");
        s.trim().split_whitespace().map(|word| word.parse()).collect()

    }
}

fn comp_f64(a: &f64, b: &f64) -> Ordering {
    if a < b {
        return Ordering::Less;
    } else if a > b {
        return Ordering::Greater;
    }
    Ordering::Equal
}

fn input_parser() -> (Vec<Items>, Capacity) {
    let capacity = Capacity::new();

    let mut items:Vec<Items> = Vec::with_capacity(capacity.nitems as usize);
    for _ in 0..items.capacity() {
        let mut values = Items::new();
        items.push(values);
    };
    items.sort_by(|a, b| comp_f64(&a.score,&b.score));
    (items, capacity)
}

fn fract_knapsack(items: Vec<Items>, capacity:Capacity) {
    let mut result = 0f64;
    let mut tweight = 0f64;
    let maxweight = capacity.maxweight;
    for item in items.iter().rev() {
        if tweight < maxweight && maxweight - tweight >= item.weight {
            result += item.value;
            tweight += item.weight;
        } else if maxweight - tweight < item.weight {
            result += item.value * ((maxweight - tweight) / item.weight);
            tweight += item.weight * ((maxweight - tweight) / item.weight);
        }
        if tweight == maxweight {
            break;
        }
    }
    println!("{:.4}", result);
}

fn main() {

    let (items, capacity) = input_parser();
    fract_knapsack(items, capacity);
}

Before I wrote this version, I tried writing an alternative version of the read_values function that would take the parsed values from stdin and create the struct without collecting into a vector first. My first very naive attempt was to replace the
s.trim().split_whitespace().map(|word| word.parse()).collect()
by
s.trim().split_whitespace().map(|word| word.parse()).collect::()
Rust told me that a collection of type Capacity cannot be built from an iterator over elements of type 'std::result::Result<_, _>'

My second attempt was to use another map function to map the values into the struct, but I didn't get very far with that either.
Do you guys think that's possible?

Thanks
Fabrex

Yeah, you can do this roughly as follows:

  1. Implement FromStr for Capacity
  2. Implement FromStr for Items
  3. Get a Lines iterator over the input.
  4. Peel off 1 line from the Iterator and parse() that into Capacity
  5. Consume the rest of the Iterator and parse/collect that into a Vec of Items.

Hmmm. I liked that you answered in guidelines.

I'll begin right away.

Thanks

I’m on mobile or else I’d flesh this out for you instead of a list :slight_smile: but try it yourself - better learning exercise.

No, really. I liked the guidelines better. Like you said. Better learning exercise.

Ps: Hope you don't start charging me for the lessons. :wink:

Ok. I didn't really do the entire ToDo list, but here is goes. I removed the knapsack part so that you guys can focus on the troubling part.

#![allow(unused_imports)]

use std::io;
use std::str::FromStr;
use std::cmp::Ordering;
use std::num::{ParseIntError, ParseFloatError};

struct Items {
    value: f64 ,
    weight: f64,
    score: f64,
}

impl Items {
    fn read_values() -> String {
        let mut s = String::new();
        io::stdin().read_line(&mut s).expect("Could not read line from stdin");
        s.to_owned()
    }
}

impl FromStr for Items {
    type Err = ParseFloatError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let input: Vec<&str> = s.split_whitespace().collect();
        let value = input[0].parse::<f64>()?;
        let weight = input[1].parse::<f64>()?;

        Ok(Items {value: value, weight: weight, score: value/weight})
    }
}

struct Capacity {
    nitems: f64,
    maxweight: f64,
}

impl Capacity {
    fn read_values<T: FromStr>() -> String {
        let mut s = String::new();
        io::stdin().read_line(&mut s).expect("Could not read line from stdin");
        s.to_owned()
    }
}

impl FromStr for Capacity {
    type Err = ParseFloatError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let input: Vec<&str> = s.split_whitespace().collect();
        let nitems = input[0].parse::<f64>()?;
        let maxweight = input[1].parse::<f64>()?;

        Ok(Capacity {nitems: nitems, maxweight: maxweight})
    }
}

fn input_parser() -> (Vec<Items>, Capacity) {
    let capacity = Capacity::from_str(&Capacity::read_values::<f64>()).expect("Error obtaining values from String");

    let mut items:Vec<Items> = Vec::with_capacity(capacity.nitems as usize);
    for _ in 0..items.capacity() {
        let mut values = Items::from_str(&Items::read_values()).expect("Error obtaining values from String");
        items.push(values);
    };
    items.sort_by(|a, b| comp_f64(&a.score,&b.score));
    (items, capacity)
}


fn comp_f64(a: &f64, b: &f64) -> Ordering {
    if a < b {
        return Ordering::Less;
    } else if a > b {
        return Ordering::Greater;
    }
    Ordering::Equal}

fn main() {
    let (item,capacity) = input_parser();
}

The code works, but I still have some doubts.
One of them would be:
Originally I intended in defining the Capacity struct with nitems as a usize value.
I just could not figure out how to do that, mainly because I had to define the type Err within the impl FromStr for Capacity. If I use a usize value for nitems and a f64 for maxweight, the Err type should be ParseIntError and ParseFloatError respectively. I thought about a match statement but I didn't really figure out how.

Other than that, any comments on the code would be nice.
Thank you
Fabrício

Ps: Wrote this in a hurry because my ride was already going (sorry for any confusion or typos).

Define your own error type (enum makes sense here) and return that as a wrapper over the int/float error.

Hi there. I've update the code to not use vectors in order to capture stdin. So the final implementation of FromStr became this:

impl FromStr for Capacity {
    type Err = ParseFloatError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut words = s.split_whitespace();
        let nitems = words.next().unwrap().parse()?;
        let maxweight = words.next().unwrap().parse()?;
        Ok(Capacity {nitems: nitems, maxweight: maxweight})
    }
}

Sorry I took so long to reply, but I had no idea how I would implement the type Err for both f64 and i64, so, I went to study a bit. I searched some hints on the Programming-Rust book and the Rust Book, but, in the end, I was finally defeated by my own lack of knowledge. I just couldn't figure out how to do this.
I know that you can wrap an existing struct into an enum. Something like

struct Capacity {
    nitems: usize,
    maxweight: f64,
}

enum ErrorType {
    IntCapacity(Capacity),
    FloatCapacity(Capacity),
}

But I'm not sure how this would help me, if it would help me at all.

Could you give me some more pointers?

Thanks
Fabrex

Here is an example.