These are something I use quite a lot in python like so:
from collections import defaultdict
nested = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
nested[key1][key2][key3] = [1, 2, 3]
Is something similar possible in Nim?
I have tried the following but getting key error when trying to use it.
import std/tables
var nested = initTable[string, Table[string, Table[string, seq[string]]]]()
the problem you're having probably that you're trying to do exactly this in Nim:
nested[key1][key2][key3] = [1, 2, 3]
right?
The thing is that if you're writing this:
table[key] = value
you're calling this function: https://nim-lang.org/docs/tables.html#[]=,TableRef[A,B],A,sinkB which as the documentation says creates a new entry with this key if there isn't one before. It's equivalent to
`[]=`(table, key, value)
but if you're writing
table[key1][key2] = value
is the same as
`[]=`(`[]`(table, key1), key2, value)
for the first level of table you're first calling this function: https://nim-lang.org/docs/tables.html#%5B%5D%2CTableRef%5BA%2CB%5D%2CA
which throws a key error when there's no entry with that key.
Though honestly in most cases you're probably better off with a table like this: Table[(string, string, string), seq[string]]
"Though honestly in most cases you're probably better off with a table like this: Table[(string, string, string), seq[string]]"
This is possible but its not ideal for iteration or lookup in the resulting object though. Id have to to write a bunch more functions just to iterate the keys at each level.
A way to do this would be:
import std/tables
var nested: Table[string, Table[string, Table[string, seq[string]]]]
nested.mgetOrDefault(key1, initTable[string, Table[string, seq[string]]]()).mgetOrDefault(key2, initTable[string, seq[string]]())[key3] = @[1, 2, 3]
# sugar:
template `{}`[A, B](tab: Table[A, B], key: A): var B =
tab.mgetOrDefault(key, default(A))
nested{key1}{key2}{key3} = @[1, 2, 3]
mgetOrPut accesses a key, if the key doesn't exist then it is set to the value you give it, and returns the address to the value of that key. You can then modify the value in this address, either by setting a key inside it, or by calling mgetOrPut again. Docs
default(T) where T is a type returns the default value for that type. So default(Table[string, seq[string]]) would be equal to initTable[string, seq[string]](). Docs
Curly sugar is inessential, you could easily rename it to mgetOrDefault and use it as a regular proc
Still understandable if you still don't want to do it this way, it's just that there's not always an analogue for mgetOrPut in other languages, and I thought it could be an easy thing to miss.
I appreciate the detailed replies, I will spend some time to try and understand the code you have posted.
But for Nim its not a good look when c++ makes something more simple.
#include <map>
#include <string>
#include <vector>
using namespace std;
typedef map<string, map<string, map<string, vector<string>>>> NestedMap;
int main()
{
NestedMap nested;
nested["key1"]["key2"]["key3"].push_back("string");
}
the downside to this is that in C++ this code:
++
#include <stdio.h>
#include <map>
int main()
{
std::map<int, int> test;
test[42];
printf("test %d\n", test.size());
}
prints 1. Eventhough there's no visible assignment at any point a new table entry is created.N00b here. I tried creating a distinct DefaultTable type but I cannot borrow mgetOrPut. Is this code supposed to compile? What am I missing?
https://play.nim-lang.org/#ix=3DxL
import tables
type
DefaultTable[A, B] = distinct Table[A, B]
proc mgetOrPut[A, B](t: var DefaultTable[A, B]; key: A; val: B): var B {.borrow.}
proc `[]`[A, B](tab: var DefaultTable[A, B], key: A): var B {.inline.} =
tab.mgetOrPut(key, default(B))
var nested:DefaultTable[string,DefaultTable[string,DefaultTable[string,seq[string]]]]
nested["key1"]["key2"]["key3"] = @["1", "2", "3"]
echo nested["key1"]["key2"]["key3"]
Outputs:
/usercode/in.nim(6, 1) Error: no symbol to borrow from found
One of the things I like about Nim is how it uses terms properly.
I also considered to use text instead of string and chain instead of (linked) list. ;-)
I'd still go with the one @shirleyquirk posted. That version will only do it for tables, and not arbitrary values, saving you from the issue that the C++ code had. Compare this:
proc `[]`[A, B, C](t: var Table[A, Table[B, C]], key: A): var Table[B, C] =
t.mgetOrPut(key, initTable[B, C]())
var nested: Table[string, Table[string, Table[string, int]]]
nested["this"]["works"]["fine"] = 42
#echo nested["this"]["doesn't"]["work"] # Will throw an exception about the key not being found
To this, which has similar behaviour to the C++ version which silently inserts keys:
var nested: Table[string, Table[string, Table[string, int]]]
nested["this"]["works"]["fine"] = 42
discard nested["now"]["this"]["works"] # Adds a default int to the key
echo nested["now"]["this"].len # Now there is something in the "this" table
echo nested["now"]["this"].hasKey("works") # And the works key have been silenly added
echo nested["now"]["this"]["works"] # With the default value of an int, which is 0
The first version will still shadow-add table entries to the subtables if you do something like echo nested["a"]["couple"]["keys"], but you could always also create a procedure like:
proc hasKey[A, B, C](t: Table[A, Table[B, C], k: A): bool = t.hasKey(k) and t[k].len != 0
To make hasKey behave a bit more like "has key with useful data".