Reputation: 319
I want to create a data structure that would store and change the values as the data comes in. While I learned how to do async calls to a REST API, use serde_json and print the values out, I have a problem storing the data.
Here is the structure of the old, currently used, version of the array in PHP:
$data[$manufacturer][$model_id]['current_bid']=3400.50;
$data[$manufacturer][$model_id]['last_bid_time']=1649266798;
So let's say it's:
$data["volvo"]["V60"]['current_bid']=3400.50;
$data["volvo"]["V60"]['last_bid_time']=1649266798;
$data["volvo"]["V90"]['current_bid']=5200.50;
$data["volvo"]["V90"]['last_bid_time']=1649266799;
Model IDs are signed integers (u8
is fine, used a string in the above example for explanation purposes), current bids are f64
and last_bid_time
is a UNIX epoch. As I understand, the chrono:datetime
crate sends back an i64
through utc::now()
.
Bids of current cars can change as the data comes in, and there could be new models so I would like to add them to the volvo
part of this data structure. For each specific model, the bid is only going to be updated with the new price and the last_bid_time
.
How do I create such a variable? In PHP it's easy, in Rust it should be super performant and lightweight but I don't know how to do it.
As I understand, vectors are no good for this as they are ?limited at compile time? and hashmaps are not?
I've tried using hashmaps but I'm constantly getting an error that I should put the current_bid
and last_bid_time
as tuple which makes hashmap.insert
not work.
Should I use structs?
struct VehicleBid {
current_bid: f64,
last_bid_time: i64,
}
struct VehicleModel {
model_id: i8,
}
Struct Manufacturer {
name: String,
}
Struct Bids{
Manufacturer {
VehicleModel {
VehicleBid
}
}
}
I've come as far as this, but I don't know how to store data into this variable:
let mut data: HashMap<String, HashMap<u8, HashMap<f64, i64>>> = HashMap::new();
Upvotes: 0
Views: 227
Reputation: 3139
First: this usecase sounds like you need a database. That will manage things like concurrent connections, multi-part transactions, saving, and restoring. That being said, the simple read/replace functionality you described is absolutely possible using Rust HashMap
s.
The main issue with what you're showing is indirection. It is a better idea to use one map, where the key is (manufacture, id)
, as opposed to two maps, one that maps manufacturers to a list of ids, and then maps for each of those ids to vehicle. When you use just one map with the double key, it takes just one lookup to access and store values, instead of two.
The downside of doing it this way is that you cannot create a manufacturer that does not have cars. It also becomes harder (not impossible, just harder) to list all manufacturers, or list all the cars of a single manufacturer.
A big difference between Rust and PHP is data control and ownership. In Rust, you must prove to the compiler that everything you do it valid. In PHP, it's just assumed you know what you're doing.
Here is a functional example of what could work. However, this code is not thoroughly tested, and does not handle errors well. Specifically, Bids::get_entry
will panic
if you try to access a car that does not exist. Bids::handle_bid
, and Bids::print_current_bid
also will do this. The unwraps could be removed and handled intelligently. For example, Bids::entry
would be better to return Option<&BidInfo>
. The reason I did not do this is because the example I showed was already pretty long, and I wanted to keep the code as minimal as possible.
use std::collections::HashMap;
struct BidInfo {
/// The current bid
current_bid: f64,
/// The time of the last bid, as a unix epoch
last_bid_time: i64,
}
impl BidInfo {
/// Create a new vehicle
fn new() -> BidInfo {
BidInfo {
current_bid: 0.0,
last_bid_time: 0,
}
}
}
// To use CarInfo as the key of a HashMap, Rust needs to know how to:
// * hash it
// * test if it's equal to something else
//
// Using derive, we tell Rust to automatically generate the code to do this.
#[derive(PartialEq, Eq, Hash, Clone)]
struct CarInfo {
manufacturer: String,
model_id: u8,
}
impl CarInfo {
fn new(manufacturer: &str, model_id: u8) -> CarInfo {
CarInfo {
manufacturer: manufacturer.to_string(),
model_id: model_id,
}
}
}
// This type is the key
// vvvvvvv
struct Bids (HashMap<CarInfo, BidInfo>);
// This type is the value ^^^^^^^
impl Bids {
/// Create a new, empty, bids object
fn new() -> Bids {
Bids(HashMap::new())
}
/// Add a new car to the table
/// Assume that it's current bid info is empty
fn add_entry(&mut self, car: CarInfo) {
self.0.insert(car, BidInfo::new());
}
/// Get the current bid info for a given car
/// Note, we cannot modify this entry, we can just read it
/// If the car does not exist, this function throws an error
fn get_entry(&self, car: &CarInfo) -> &BidInfo {
self.0.get(car).unwrap()
}
/// Handle a new bid
fn handle_bid(&mut self, car: &CarInfo, bid: f64) {
// We use get_mut because we want to edit what we get back
// This gives us an Option<BidInfo>. If the car was in the map, it will
// contain the bid info, but if the car does not exist, it will be a
// special value, None
//
// This unwrap function assumes that it worked. If we try to unwrap a
// value that is None, we will get a runtime error
let mut entry = self.0.get_mut(car).unwrap();
entry.current_bid = bid;
entry.last_bid_time = entry.last_bid_time + 1; // This doesn't actually set timestamps, but you get the idea
}
/// Helper function that gives us information about the current bid of a car
fn print_current_bid(&self, car: &CarInfo) {
println!(
"Current bid of {} model {} is {}",
car.manufacturer,
car.model_id,
self.get_entry(car).current_bid
);
}
}
fn main() {
// For this example, we'll use two different manufacturers, each with 3
// cars
// Create empty map
let mut bids = Bids::new();
// Create a new car with manufacturer Volvo and model_id 0
bids.add_entry(CarInfo::new("Volvo", 0));
bids.add_entry(CarInfo::new("Volvo", 1));
bids.add_entry(CarInfo::new("Volvo", 32));
// We can also create entries this way
let bmw_car_0 = CarInfo::new("BMW", 0);
let bmw_car_1 = CarInfo::new("BMW", 1);
let bmw_car_3 = CarInfo::new("BMW", 3);
// We use clone here to create a copy of the car data, so we can keep the one we created
bids.add_entry(bmw_car_0.clone());
bids.add_entry(bmw_car_1.clone());
bids.add_entry(bmw_car_3.clone());
// Print out some state to verify everything is okay
bids.print_current_bid(&CarInfo::new("Volvo", 1));
// This method takes a &CarInfo instead of CarInfo. This means that instead
// of taking the object, it just borrows it for a bit. Therefore, we don't
// have to use clone this time
bids.print_current_bid(&bmw_car_1);
// This would cause an error, because the entry doesn't exist
//bids.print_current_bid(CarInfo::new("BMW", 33));
// Handle a few bids
bids.handle_bid(&CarInfo::new("Volvo", 1), 100.0);
bids.handle_bid(&bmw_car_1, 322.0);
// Print out the same info
bids.print_current_bid(&CarInfo::new("Volvo", 1));
bids.print_current_bid(&bmw_car_1);
}
Upvotes: 1