Building a struct based on data in two other structs

I've 2 structs, one shows the INs of items, based on batch number, and another showing the OUTs without batch number, Assuming FIFO is in place ("first-in, first-out," the first items placed in inventory are the first sold. Thus, the inventory at the end of a year consists of the goods most recently placed in inventory.).

I modeled the issue in the diagrams below, and started coding as shown here, but stuck :frowning:

use std::collections::HashMap;

#[derive(Clone, Debug)]
struct INs<'a> {
    item: &'a str,
    date: &'a str,
    batch: &'a str,
    quantity: u64
}

#[derive(Clone, Debug)]
struct OUTs<'a> {
    item: &'a str,
    date: &'a str,
    quantity: u64
}

#[derive(Clone, Debug)]
struct BatchesOut<'a> {
    item: &'a str,
    batch: &'a str,
    date: &'a str,
    quantity: u64
}

fn main() {
        let mut ins: Vec<INs> = Vec::new();
        ins.push(INs { item: "A", date: "1/1/2020", batch: "A-1", quantity: 100});
        ins.push(INs { item: "A", date: "1/7/2020", batch: "A-2", quantity: 100});
        ins.push(INs { item: "A", date: "1/14/2020", batch: "A-3", quantity: 50});

        let mut outs: Vec<OUTs> = Vec::new();
        outs.push(OUTs { item: "A", date: "1/5/2020", quantity: 60});
        outs.push(OUTs { item: "A", date: "1/8/2020", quantity: 60});
        outs.push(OUTs { item: "A", date: "1/14/2020", quantity: 60});
        outs.push(OUTs { item: "A", date: "1/21/2020", quantity: 60});

       let mut batches_out: Vec<BatchesOut> = Vec::new();
       // Expected output
       // batches_out.push(BatchesOut { item: "A", batch: "A-1", date: "1/5/2020", quantity: 60});
       // batches_out.push(BatchesOut { item: "A", batch: "A-1", date: "1/8/2020", quantity: 40});
       // batches_out.push(BatchesOut { item: "A", batch: "A-2", date: "1/8/2020", quantity: 20});
       // batches_out.push(BatchesOut { item: "A", batch: "A-2", date: "1/14/2020", quantity: 60});
       // batches_out.push(BatchesOut { item: "A", batch: "A-2", date: "1/21/2020", quantity: 20});
       // batches_out.push(BatchesOut { item: "A", batch: "A-3", date: "1/21/2020", quantity: 40});
}

Graphical presentation:

How about something like this? It doesn't handle multiple item types, but those can be handled by creating an Inventory per item type.

1 Like

You are super, thanks 1000

Reading input and writing output from/to csv files make the code as below:

use std::collections::VecDeque;
use std::path::Path;
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
struct INs {
    item: String,
    date: String,
    batch: String,
    quantity: u64,
}

#[derive(Clone, Debug, Deserialize)]
struct OUTs {
    item: String,
    date: String,
    quantity: u64,
}

#[derive(Clone, Debug, PartialEq, Serialize)]
struct BatchesOut {
    item: String,
    batch: String,
    date: String,
    quantity: u64,
}

#[derive(Debug, Clone, Default, Serialize)]
struct Inventory {
    inventory: VecDeque<INs>,
}
impl<'a> Inventory {
    fn new() -> Self {
        Default::default()
    }
    fn push(&mut self, item: INs) {
        self.inventory.push_back(item);
    }
    fn process(&mut self, order: &mut OUTs) -> BatchesOut {
        let next_item = self.inventory.front_mut().unwrap();

        let num_items = std::cmp::min(next_item.quantity, order.quantity);

        let batch = BatchesOut {
            item: next_item.item.clone(),
            batch: next_item.batch.clone(),
            date: order.date.clone(), 
            quantity: num_items,
        };

        next_item.quantity -= num_items;
        if next_item.quantity == 0 {
            self.inventory.pop_front();
        }

        order.quantity -= num_items;

        batch
    }
    pub fn process_all(&mut self, outs: &[OUTs]) -> Vec<BatchesOut> {
        let mut batches = Vec::new();
        for out in outs {
            let mut out = out.clone();
            while out.quantity > 0 {
                batches.push(self.process(&mut out));
            }
        }
        batches
    }
}

fn main() {
    let mut ins = Inventory::new();
    let file_path_ins = Path::new("src/ins.csv");
    let mut rdr_ins = csv::ReaderBuilder::new()
        .has_headers(true)
        .delimiter(b',')
        .from_path(file_path_ins).unwrap();
    for result in rdr_ins.records() {
         let record: INs = result.unwrap().deserialize(None).unwrap();
          ins.push(INs { item: record.item, date: record.date,
                     batch: record.batch, quantity: record.quantity, });
    };


    let mut outs: Vec<OUTs> = Vec::new();
    let file_path_outs = Path::new("src/outs.csv");
    let mut rdr_outs = csv::ReaderBuilder::new()
        .has_headers(true)
        .from_path(file_path_outs).unwrap();
    for result in rdr_outs.records() {
         let record: OUTs = result.unwrap().deserialize(None).unwrap();
         outs.push(OUTs { item: record.item, date: record.date, quantity: record.quantity, });
    };

    let batches_out = ins.process_all(&outs);

    let file_path_batches_out = Path::new("src/batches_out.csv");
    let mut wtr = csv::Writer::from_path(file_path_batches_out).unwrap();

    batches_out.iter().for_each(|batch|
        wtr.serialize(BatchesOut {
                item: batch.item.to_string(),
                batch: batch.batch.to_string(),
                date: batch.date.to_string(),
                quantity: batch.quantity,
        }).unwrap()
    );
    wtr.flush().unwrap();
}

And cargo:

[package]
name = "aging"
version = "0.1.0"
authors = ["Hasan Yousef <myemail@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
csv = "1.1.3"
serde = { version = "1.0.106", features = ["derive"] }

Tried to handle it per item by:

#[derive(Debug, Clone, Default, Serialize)]
struct Inventory {
    inventory: VecDeque<INs>,
}
impl Inventory {
    fn process(&mut self, order: &mut OUTs) -> BatchesOut {
// replacing:
     let next_item = self.inventory.front_mut().unwrap();

// by:
     let mut moved_batch = self.inventory.into_iter()
            .filter(|item| *item.item == order.item).collect::<VecDeque<INs>>();
     let next_item = moved_batch.front_mut().unwrap();
    }

    pub fn process_all(&mut self, outs: &[OUTs]) -> Vec<BatchesOut> {
            while out.quantity > 0 {
                batches.push(self.process(&mut out));
            }
}

But got an error as:

error[E0507]: cannot move out of `self.inventory` which is behind a mutable reference
  --> src\main.rs:41:31
   |
41 |         let mut moved_batch = self.inventory.into_iter()
   |                               ^^^^^^^^^^^^^^ move occurs because `self.inventory` has type `std::collections::VecDeque<INs>`, which does not implement the `Copy` trait

Each Inventory should only contain items of the correct item type. I do not recommend trying to store them in the same inventory.

mmm, not getting your point. I'm trying to solve a real business problem we are having, with 2K SKU/item, did not get how you want to create separate Inventory for each item?

I tried the below:

let mut items = BTreeSet::new(); // Unique list of the items
let mut ins: BTreeMap<String, Vec<Inventory>> = BTreeMap::new();  // saving ins for each item as Vec
let mut outs: BTreeMap<String, Vec<OUTs>> = BTreeMap::new();  // saving outs for each item as vec
// Reading ins and build the unique items list
    let file_path_ins = Path::new("src/ins.csv");
    let mut rdr_ins = csv::ReaderBuilder::new()
        .has_headers(true)
        .delimiter(b',')
        .from_path(file_path_ins).unwrap();
    for result in rdr_ins.records() {
        let record: INs = result.unwrap().deserialize(None).unwrap();
        let mut buf = VecDeque::new();
        buf.push_back(INs { item: record.item.clone(), date: record.date,
                     batch: record.batch, quantity: record.quantity, });
        ins.entry(record.item.clone())
            .or_insert_with(Vec::new)
            .push(Inventory{ inventory: buf});
         items.insert(record.item);
    };
// Read outs
    let file_path_outs = Path::new("src/outs.csv");
    let mut rdr_outs = csv::ReaderBuilder::new()
        .has_headers(true)
        .from_path(file_path_outs).unwrap();
    for result in rdr_outs.records() {
         let record: OUTs = result.unwrap().deserialize(None).unwrap();
         outs.entry(record.item.clone())
            .or_insert_with(Vec::new)
            .push(OUTs { item: record.item, date: record.date, quantity: record.quantity});
    };

// All above is executing correctly
// Now running the calculations related to each item:
// In this block i'm getting an error:
    for item in items {
        let in1 =ins.get(item.as_str()).unwrap();
        let out1 = outs.get(item.as_str()).unwrap();
        let batches_out = in1.process_all(&out1);
    }

Error I got is:

error[E0599]: no method named `process_all` found for reference `&std::vec::Vec<Inventory>` in the current scope
   --> src\main.rs:106:31
    |
106 |         let batches_out = in1.process_all(&out1);
    |                               ^^^^^^^^^^^ method not found in `&std::vec::Vec<Inventory>`

Full code as of now is:

use std::collections::{VecDeque, HashMap, BTreeSet, BTreeMap};
use std::path::Path;
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
struct INs {
    item: String,
    date: String,
    batch: String,
    quantity: u64,
}

#[derive(Clone, Debug, Deserialize)]
struct OUTs {
    item: String,
    date: String,
    quantity: u64,
}

#[derive(Clone, Debug, PartialEq, Serialize)]
struct BatchesOut {
    item: String,
    batch: String,
    date: String,
    quantity: u64,
}

#[derive(Debug, Clone, Default, Serialize)]
struct Inventory {
    inventory: VecDeque<INs>,
}
impl Inventory {
    fn new() -> Self {
        Default::default()
    }
    fn push(&mut self, item: INs) {
        self.inventory.push_back(item);
    }
    fn process(&mut self, order: &mut OUTs) -> BatchesOut {
        let next_item = self.inventory.front_mut().unwrap();
        let num_items = std::cmp::min(next_item.quantity, order.quantity);

        let batch = BatchesOut {
            item: next_item.item.clone(),
            batch: next_item.batch.clone(),
            date: order.date.clone(),
            quantity: num_items,
        };

        next_item.quantity -= num_items;
        if next_item.quantity == 0 {
           self.inventory.pop_front();
        }
        order.quantity -= num_items;

        batch
    }
    pub fn process_all(&mut self, outs: &[OUTs]) -> Vec<BatchesOut> {
        let mut batches = Vec::new();
        for out in outs {
            let mut out = out.clone();
            while out.quantity > 0 {
                batches.push(self.process(&mut out));
            }
        }
        batches
    }
}

fn main() {
    let mut ins: BTreeMap<String, Vec<Inventory>> = BTreeMap::new();
    let mut items = BTreeSet::new();
    let file_path_ins = Path::new("src/ins.csv");
    let mut rdr_ins = csv::ReaderBuilder::new()
        .has_headers(true)
        .delimiter(b',')
        .from_path(file_path_ins).unwrap();
    for result in rdr_ins.records() {
        let record: INs = result.unwrap().deserialize(None).unwrap();
        let mut buf = VecDeque::new();
        buf.push_back(INs { item: record.item.clone(), date: record.date,
                     batch: record.batch, quantity: record.quantity, });
        ins.entry(record.item.clone())
            .or_insert_with(Vec::new)
            .push(Inventory{ inventory: buf});
         items.insert(record.item);
    };

    let mut outs: BTreeMap<String, Vec<OUTs>> = BTreeMap::new();
    let file_path_outs = Path::new("src/outs.csv");
    let mut rdr_outs = csv::ReaderBuilder::new()
        .has_headers(true)
        .from_path(file_path_outs).unwrap();
    for result in rdr_outs.records() {
         let record: OUTs = result.unwrap().deserialize(None).unwrap();
         outs.entry(record.item.clone())
            .or_insert_with(Vec::new)
            .push(OUTs { item: record.item, date: record.date, quantity: record.quantity});
    };

    for item in items {
        let in1 =ins.get(item.as_str()).unwrap();
        let out1 = outs.get(item.as_str()).unwrap();
        let batches_out = in1.process_all(&out1);

        let file_path_batches_out = Path::new("src/batches_out.csv");
        let mut wtr = csv::Writer::from_path(file_path_batches_out).unwrap();

        batches_out.iter().for_each(|batch|
            wtr.serialize(BatchesOut {
                    item: batch.item.to_string(),
                    batch: batch.batch.to_string(),
                    date: batch.date.to_string(),
                    quantity: batch.quantity,
            }).unwrap()
        );
        wtr.flush().unwrap();
    }
}

Cargo is:

[dependencies]
csv = "1.1.3"
serde = { version = "1.0.106", features = ["derive"] }

One inventory can store all of the items of a certain type. You need just one per item type. playground

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.