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


#1

Hello again dear fellows from the Rust community.
In my last Topic (Sorting Vector of vectors of f64) 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


#2

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.

#3

Hmmm. I liked that you answered in guidelines.

I’ll begin right away.

Thanks


#4

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.


#5

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:


#6

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).


#7

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


#8

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


#9

Here is an example.