Многомерное динамическое хранилище данных

Я хочу создать структуру данных, которая будет хранить и изменять значения по мере поступления данных. Хотя я научился выполнять асинхронные вызовы REST API, использовать serde_json и распечатывать значения, у меня возникла проблема с хранением данных.

Вот структура старой, используемой в настоящее время версии массива в PHP:

$data[$manufacturer][$model_id]['current_bid']=3400.50;
$data[$manufacturer][$model_id]['last_bid_time']=1649266798;

Итак, скажем, это:

$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;

Идентификаторы модели представляют собой целые числа со знаком (u8 подходит, в приведенном выше примере использовалась строка для пояснения), текущие ставки — это f64, а last_bid_time — это эпоха UNIX. Насколько я понимаю, ящик chrono:datetime отправляет обратно i64 через utc::now().

Ставки на текущие автомобили могут меняться по мере поступления данных, и могут появиться новые модели, поэтому я хотел бы добавить их в часть volvo этой структуры данных. Для каждой конкретной модели ставка будет обновляться только с новой ценой и last_bid_time.

Как создать такую ​​переменную? В PHP это просто, в Rust это должно быть сверхпроизводительно и легковесно, но я не знаю, как это сделать.

Насколько я понимаю, векторы для этого не годятся, так как они «ограничены во время компиляции»? а хэшмапы нет?

Я пытался использовать хэш-карты, но постоянно получаю сообщение об ошибке, что я должен поместить current_bid и last_bid_time как кортеж, из-за чего hashmap.insert не работает.

Должен ли я использовать структуры?

struct VehicleBid {
    current_bid: f64,
    last_bid_time: i64,
}

struct VehicleModel {
    model_id: i8,
}

Struct Manufacturer {
    name: String,
}

Struct Bids{
    Manufacturer {
        VehicleModel {
            VehicleBid
        }
    }
}

Я дошел до этого, но не знаю, как хранить данные в этой переменной:

let mut data: HashMap<String, HashMap<u8, HashMap<f64, i64>>> = HashMap::new();

Показанный вами код Rust синтаксически недействителен. Вы можете перечитать глава о структурах в Язык программирования Rust.

Shepmaster 06.04.2022 20:02

Похоже, на ваш вопрос могут ответить ответы Как эффективно искать и вставлять в HashMap?. Если нет, пожалуйста, редактировать свой вопрос, чтобы объяснить различия. В противном случае мы можем пометить этот вопрос как уже отвеченный.

Shepmaster 06.04.2022 21:38
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
2
44
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых: этот вариант использования звучит так, будто вам нужна база данных. Это позволит управлять такими вещами, как одновременные соединения, многоэтапные транзакции, сохранение и восстановление. При этом простая функциональность чтения/замены, которую вы описали, абсолютно возможна с использованием Rust HashMaps.

Основная проблема с тем, что вы показываете, - это косвенность. Лучше использовать одну карту, где ключ — (manufacture, id), а не две карты, одна из которых сопоставляет производителей со списком идентификаторов, а затем сопоставляет каждый из этих идентификаторов с транспортным средством. Когда вы используете только одну карту с двойным ключом, для доступа и сохранения значений требуется всего один поиск вместо двух.

Недостатком этого способа является то, что вы не можете создать производителя, у которого нет автомобилей. Также становится сложнее (не невозможно, а просто сложнее) составить список всех производителей или всех автомобилей одного производителя.

Большая разница между Rust и PHP заключается в контроле данных и владении ими. В Rust вы должны доказать компилятору, что все, что вы делаете, действительно. В PHP предполагается, что вы знаете, что делаете.

Вот функциональный пример того, что может работать. Однако этот код не был тщательно протестирован и плохо обрабатывает ошибки. В частности, Bids::get_entry будет panic, если вы попытаетесь получить доступ к несуществующему автомобилю. Bids::handle_bid и Bids::print_current_bid также сделают это. Развертки могут быть удалены и обработаны разумно. Например, Bids::entry лучше вернуть Option<&BidInfo>. Причина, по которой я этого не сделал, заключается в том, что пример, который я показал, уже был довольно длинным, и я хотел, чтобы код был как можно меньше.

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);
}

@Shepmaster, честно говоря, я в основном имел в виду свободное использование unwrap и общее отсутствие кода, обрабатывающего несуществующие записи. Будет отредактирован вопрос, чтобы предоставить больше информации

Carson 06.04.2022 22:07

Я имею в виду, что вы можете специально указать, что им не следует копировать: «не делайте xxx(), потому что ууу». Тогда, по крайней мере, это не сбивает с толку.

Shepmaster 06.04.2022 22:13

Большое спасибо, @Carson. Особенно ко всем комментариям. Столько АГА! и несколько? но почему?.

user3019799 06.04.2022 22:27

user3019799 заходите в чат! Буду рад попытаться помочь ответить на любые вопросы. @Shepmaster не уверен в прецеденте, должен ли я создать комнату для нас двоих или просто скинуть ссылку на комнату ржавчины?

Carson 06.04.2022 22:43

думаю и то и другое нормально

Shepmaster 06.04.2022 22:43

@user3019799 Присоединяйтесь к этому чату, я постараюсь ответить на любые ваши вопросы: chat.stackoverflow.com/rooms/243666/rust-hashmap-help-sessio‌​n

Carson 06.04.2022 22:45

Другие вопросы по теме