q vs python: Dictionaries

(Series) A side-by-side comparison of q/kdb+ and python for qbies (q newbies).

Cover image

This cheat sheet is a list of code snippets that do the same thing in q and python -- something I wished existed when I first came to q/KDB+ from python. Note that in both languages, the code shown is sometimes not the most elegant or succinct; that's on purpose because I wanted to show the same constructs in both languages. For a comprehensive intro to q, check out Q for Mortals.

Note q code snippets are edited to have line breaks for readability (my opinion; ymmv). Multiline statements do not work at the prompt, but they do work in scripts. See docs.

Versions: q (3.6), python (3.7)

Basics*

q)d:(`a`b`c)!1 2 3

q)d:(
    ((enlist `a)!(enlist 1)),
    ((enlist `b)!(enlist 2)),
    ((enlist `c)!(enlist 3))
    )

q)d
a| 1
b| 2
c| 3

q)d`a
1

q)d`x
0N
>>> d = dict(a=1, b=2, c=3)

>>> d = {
...     'a': 1,
...     'b': 2,
...     'c': 3,
...     }

>>> d
{'a': 1, 'b': 2, 'c': 3}



>>> d['a']
1

>>> d['x']
KeyError: 'd'

Note the last d`x does not return an exception; 0N is a null long.

Get / Look Up*

q)d`a
q)d `a
q)d[`a]
q)d@`a
1

q)d`a`b
q)d `a`b
q)d[`a`b]
q)d@`a`b
1 2
>>> d['a']
1




>>> [d[k] for k in [1, 2]]
[1, 2]

Find / Reverse Look Up*

q)e:(`j`k`l`m)!(1 2 2 3)

q)e
j| 1
k| 2
l| 2
m| 3


q)e?2  // returns first match
`k

q)e?3 2
`m`k
>>> e = dict(zip(list('jklm'), [1, 2, 2, 3]))

>>> e
{'j': 1, 'k': 2, 'l': 2, 'm': 3}




>>> rev_e = {v: k for k, v in list(e.items())}
>>> rev_e[2]
'l'

>>> [rev_e[k] for k in [3, 2]]
['m', 'l']

On python side, do reversed(list(e.items())) to achieve same result as q.

Subset*

Get a smaller dictionary.

q)`j`m#e
j| 1
m| 3

q)(enlist `k)#e
k| 2
>>> {k: e[k] for k in 'jm'}
{'j': 1, 'm': 3}


>>> {k: e[k] for k in 'k'}
{'k': 2}

Set*

q)e[`x]:42

q)e
l| 2
m| 3
x| 42
>>> e['x'] = 42

>>> e
{'l': 2, 'm': 3, 'x': 42}

Delete*

q)e:`j`k`l`m!(1 2 2 3)

q)e:`j`k _ e
q)e
l| 2
m| 3

q)e:`l _ e
q)e
m| 3
>>> e = dict(zip(list('jklm'), [1, 2, 2, 3]))

>>> [e.pop(item) for item in 'jk']
>>> e
{'l': 2, 'm': 3}


>>> del e['j']
>>> e
{'l': 2, 'm': 3}

If your dictionary is keyed by numbers, you're out of luck.

q)e:(0 1)!`j`k
q)e
0| j
1| k

q)e:1 _ e  // deletes one item
q)e
1| k
>>> e = {0: 'j', 1: 'k'}
>>> e
{0: 'j', 1: 'k'}


>>> del e[1]  # deletes the key 1
>>> e
{0: 'j'}

Join Dictionaries*

Since python 3.9, d1 | d2 also works.

q)d1:(enlist `x)!(enlist 1)
q)d2:(enlist `y)!(enlist 2)
q)d1,d2
x| 1
y| 2

q)d3:(enlist `x)!(enlist 1)
q)d4:(enlist `x)!(enlist 2)
q)d3,d4
x| 2
>>> d1 = dict(x=1)
>>> d2 = dict(y=2)
>>> {**d1, **d2}
{'x': 1, 'y': 2}


>>> d3 = dict(x=1)
>>> d4 = dict(x=2)
>>> {**d1, **d2}
{'x': 2}

Dictionary from table*

q)t:([]k:`x`y; v:1 2)
q)exec k!v from t
x| 1
y| 2

// Note duplicated key
q)t:([]k:`x`y`y; v:1 2 3)
q)exec k!v from t
x| 1
y| 2
y| 3
>>> df = pd.DataFrame(dict(k=list('xy'), v=[1, 2]))
>>> dict(zip(df.k, df.v))
{'x':1, 'y':2}


# Last key wins
>>> df = pd.DataFrame(dict(k=list('xyy'), v=[1, 2, 3]))
>>> dict(zip(df.k, df.v))
{'x':1, 'y':3}

Gotchas*

Duplicated keys*

q)d:(`j`j)!1 2
q)d
j| 1
j| 2


q)d`j  // only the first
1
>>> {'j': 1, 'j': 2}  # last one wins
{'j': 2}

>>> dict([('j', 1), ('j', 2)])  # last one wins
{'j': 2}

>>> dict(j=1, j=2)
SyntaxError: keyword argument repeated

q dictionaries have types*

Except when they don't.

q)longDict:(`j`k`l)!1 2 3

q)longDict[`m]:4.2  / not allowed
'type

q)mixedDict:(`j`k`l)!(1; 0b; `s)
q)mixedDict[`m]:4.2  / allowed
q)mixedDict
j| 1
k| 0b
l| `s
m| 4.2
>>> d = dict(j=1, k=2, l=3)

>>> d['m'] = 4.2

>>> d
{'j': 1, 'k': 2, 'l': 3, 'm': 4.2}

Missing keys*

The last example (boolean) is arguably the most dangerous, because there are no nulls for the boolean type.

q)d:(`j`k`l)!1 2 3
q)d`notHere
0N

q)d:(`j`k`l)!`a`b`c
q)d`notHere
`

q)d:(`j`k`l)!101b
q)d`notHere
0b
>>> d['not_here']
KeyError