28

I want to represent a table of data in the memory like the following:

     | USD | EUR |
-----+-----+-----+
John | 100 | 50  |
-----+-----+-----+
Tom  | 300 | 200 |
-----+-----+-----+
Nick | 200 | 0   |
-----+-----+-----+

There is a known set of people, each of them owns some currency.

And I have the following enums:

enum Person {
    John,
    Tom,
    Nick
}

enum Currency {
    USD,
    EUR
}

I'd like to encode this data as 2D array, and it would be cool to be able to index array elements not by usize but by enum. E.g.:

data[Person::John][Currency::USD] = 100;

Is it possible to do with arrays and enums in Rust? Or is there any other data structure that would serve for this?

I am aware of HashMap, but it's not exactly what I want because:

  • HashMap works on the heap (what makes it much slower than regular stack allocated array)

  • HashMap gives me no guarantee that item exist. E.g. every time I want to get something I have to unwrap it and handle None case, what is not very handy in comparison with usage of normal array.

This is different from How do I match enum values with an integer? because I am not interested in converting enum to usize; I just want a handy way to access array/map items by enum.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sergey Potapov
  • 3,819
  • 3
  • 27
  • 46
  • Why don't you use Person and Currency as Trait and implement those traits on John, Tom, Nick and USD, EUR? – Boiethios Jul 04 '17 at 09:01
  • It sounds like you want an associative array from a name-currency pair to a value. Have you tried something? A `HashMap`? – E_net4 Jul 04 '17 at 09:07
  • > Why don't you use Person and Currency as Trait and implement those traits on John, Tom, Nick and USD, EUR? I am not sure I got this idea, but anyway I want USD and EUR enums, cause in other places of my application I need them as enums. > It sounds like you want an associative array from a name-currency pair to a value. Have you tried something? Thanks for reply. I am aware of HashMap but it's not exactly what I need. HashMap works on heap. – Sergey Potapov Jul 04 '17 at 09:16
  • Possible duplicate of [How do I match enum values with an integer?](https://stackoverflow.com/questions/28028854/how-do-i-match-enum-values-with-an-integer) – the8472 Jul 04 '17 at 09:18

4 Answers4

20

ljedrz provided a good solution. Another way to approach the problem is to use existing crate enum-map.

Add the following to your Cargo.toml:

[dependencies]
enum-map = "*"
enum-map-derive = "*"

Then, in src/main.rs:

extern crate enum_map;
#[macro_use] extern crate enum_map_derive;

#[derive(Debug, EnumMap)]
enum Person { John, Tom, Nick }

#[derive(Debug, EnumMap)]
enum Currency { USD, EUR }

use enum_map::EnumMap;
use Person::*;
use Currency::*;

fn main() {
    // Create 2D EnumMap populated with f64::default(), which is 0.0
    let mut table : EnumMap<Person, EnumMap<Currency, f64>> = EnumMap::default();

    table[John][EUR] = 15.25;

    println!("table = {:?}", table);
    println!("table[John][EUR] = {:?}", table[John][EUR]);
}

The output:

table = EnumMap { array: [EnumMap { array: [0, 15.25] }, EnumMap { array: [0, 0] }, EnumMap { array: [0, 0] }] }
table[John][EUR] = 15.25
Sergey Potapov
  • 3,819
  • 3
  • 27
  • 46
9

If you need this to be implemented using arrays, this isn't as straightforward as it might seem.

In order to be able to contain both of these pieces of information in an array (to be able to index by them), you first need to combine them in a single type, e.g. in a struct:

struct Money([(Currency, usize); 2]);

struct PersonFinances {
    person: Person,
    money: Money
}

Then, if you want to be able to index the table, you will need to wrap it in your own type so that you can implement the Index trait for it:

use std::ops::Index;

struct Table([PersonFinances; 3]);

impl Index<(Person, Currency)> for Table {
    type Output = usize;

    fn index(&self, idx: (Person, Currency)) -> &Self::Output {
        &self
        .0
        .iter()
        .find(|&pf| pf.person == idx.0) // find the given Person
        .expect("given Person not found!")
        .money
        .0
        .iter()
        .find(|&m| m.0 == idx.1)  // find the given Currency
        .expect("given Currency not found!")
        .1
    }
}

Then you can index the Table by a Person, Currency pair:

table[(Tom, EUR)]

Rust playground link to the whole code

ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • 1
    Very inefficient. The whole point of an index is for constant (or near constant) access time. It's fine for the example, but not good for a real situation. – Hutch Moore Aug 31 '22 at 22:44
1

This isn't nearly so hard as it's made out to be. I appreciate that you said that you are "not interested in converting enum to usize", but it may be that people mis-interpreted you to mean that you want to avoid that at all costs. All you have to do here is:

data[Person::John as usize][Currency::USD as usize] = 100;

As long as data really is a 2D array, everything will compile without warnings or errors, and will work as expected.

Actually, you might need to declare Person with the added "= 0" on the first element, like this:

enum Person { John = 0, Dave, Dan, Will }

I like to use this cheat:

    enum Person { John = 0, Dave, SIZE }
    enum Currency { USD = 0, CAD, SIZE }
    let mut my2d: [[f32; Person::SIZE as usize]; Currency::SIZE as usize] = [[0., 0.], [0., 0.]];
    my2d[Person::John as usize][Currency::USD as usize] = 5.4;
    println!("$$: {}", my2d[Person::John as usize][Currency::USD as usize]);
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Michael
  • 11
  • 2
0

You want a HashMap:

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
enum Person {
    John,
    Tom,
    Nick
}

#[derive(PartialEq, Eq, Hash)]
enum Currency {
    USD,
    EUR
}

type Table = HashMap<Person, HashMap<Currency, f32>>;

fn main() {
    let mut table = Table::new();
    let mut currency = HashMap::<Currency, f32>::new();

    currency.insert(Currency::USD, 100_f32);
    table.insert(Person::John, currency);

    println!("{}", table[&Person::John][&Currency::USD]);
}
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • 4
    Hi, thanks for the reply. I am aware of HashMap, but it's not exactly what I want because: - HashMap works on heap (what makes it much slower than regular stack allocated array) - HashMap gives me no guarantee that item exist. E.g. every time I want to `get` something I have to unwrap it and handle `None` case, what is not very handy in comparison with normal array. – Sergey Potapov Jul 04 '17 at 09:11
  • @SergeyPotapov So, as said in the other answer, you must implement your own type. – Boiethios Jul 04 '17 at 09:18