I'm porting some old PHP script to Go in order to achieve better performance. However, the old PHP is full of multidimensional arrays. Some excerpt from the codebase:
while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
$someData[$row['column_a']][$row['column_b']] = $row;
}
// ... more queries and stuff
if (isset($moreData['id']) && isset($anotherData['id']) && $someData[$anotherData['id']][$moreData['id']]) {
echo $someData[$anotherData['id']][$moreData['id']];
}
Awful, i know, but i can't change the logic. I made the whole script perform much better by compiling with phc, but moving to a procedural, statically-typed language seems like a better move. How can i replicate those data structures efficiently with Go or Rust? It needs to be fault-tolerant when it comes to checking indexes, there's a lot of issets all around the script to check if the identifiers exist in the data structures.
In Go, this would be represented as a map
. The syntax is map[key]value
. So for example to store a multidimensional map of [string, string] -> int
it would be map[string]map[string]int
. If you know your indices are integers and densely packed, then you'd want to use slices. Those are simpler and look like [][]type
.
As for the checking for a key existing, use this syntax, where m is a map:
if val, ok := m[key1][key2]; ok {
///Do something with val
}
Remember that to add a key to a multidimensional map you'd have to make sure the inner map is allocated before adding to it.
if _, ok := m[key]; !ok {
m[key] = make(map[string]int)
}
m[key1][key2] = value
Obviously you'd want to wrap this up in a type with methods or a few simple functions.
PHP arrays are actually associative arrays, also known as maps or dictionaries. Rust uses the name Map in its standard library. The Map
trait provides an interface for a variety of implementations. In particular, this trait defines a contains_key
method, which you can use to check if the map contains a particular key (instead of writing isset($array[$key])
, you write map.contains_key(key)
).
Map
has two type parameters: K
is the type of the map's keys (i.e. the values you use as an index) and V
is the type of the map's values.
If you need your map to contain keys and/or values of various types, you'll need to use the Any
trait. For example, if the keys are strings and the values are of various types, you could use HashMap<String, Box<Any>>
(Box
is necessary because trait objects are unsized; see this answer for more information). Check out the documentation for AnyRefExt
and AnyMutRefExt
to see how to work with an Any
value.
However, if the possible types are relatively limited, it might be easier for you to define your own trait and use that trait instead of Any
so that you can implement operations on those types without having to cast explicitly everywhere you need to use the values (plus, you can add types by adding an impl
without having to change all the places that use values).