Skip to content

How to work with dictionaries

This page explains how to create and work with dictionaries in q.

A dictionary is an association between a list of keys and a list of values. You can also think of it as key-value pairs, but q always stores it as a pair of lists.

Dictionary creation

A dictionary maps a list of keys to a corresponding list of values. The two lists must have the same count, and the key list must be a unique collection. k!v returns a dictionary, where k is the key and v is the value. Use key and value to decompose a dictionary into its key and value lists.

Example:

q

q)show d1:`Alice`Bob`Mike!30 25 43
Alice| 30
Bob  | 25
Mike | 43
q)key d1
`Alice`Bob`Mike
q)value d1
30 25 43
q)type d1
99h

Python

>>> d1 = dict(zip(['Alice', 'Bob', 'Mike'], [30, 25, 43]))
>>> d1
{'Alice': 30, 'Bob': 25, 'Mike': 43}

>>> d1.keys()
dict_keys(['Alice', 'Bob', 'Mike'])
>>> d1.values()
dict_values([30, 25, 43])
>>> type(d1)
<class 'dict'>

Note

Dictionary keys should be unique (no duplicates), but q raises no error if duplicates are present. However, operations on dictionaries with duplicate keys are undefined.

Both the keys and the values can be nested:

q)(`a`b; 1 2 3; "Alice")!3 cut til 8
`a`b   | 0 1 2
1 2 3  | 3 4 5
"Alice"| 6 7

If the keys are known to be unique, you can apply the u attribute to them, as below. The dictionary then functions as a hash table, and indexing is faster. The hash table uses storage and adds some overhead to the creation.

q)show d1:(`u#`Alice`Bob`Mike)!30 25 43
Alice| 30
Bob  | 25
Mike | 43
q)key d1
`u#`Alice`Bob`Mike

Dictionary literal syntax

kdb+ 4.1 introduced dictionary literal syntax. You can concisely define dictionaries if the keys are symbols. It presents the mapping in a more human-readable way:

q

q)([Alice: 30; Bob: 25; Mike: 43])
Alice| 30
Bob  | 25
Mike | 43

Python

>>> {'Alice': 30, 'Bob': 25, 'Mike': 43}
{'Alice': 30, 'Bob': 25, 'Mike': 43}

Literal syntax is particularly useful for singleton dictionaries:

q

q)enlist[`Alice]!enlist 30  / 4.0 syntax
Alice| 30
q)((), `Alice)!(), 30       / to save some typing
Alice| 30

q)([Alice:30])              / literal syntax
Alice| 30

Python

>>> {'Alice': 30}
{'Alice': 30}

Seeing keys and values next to each other is useful when creating larger dictionaries. Dictionary literal syntax works only with symbol keys, but you can use the over accumulator with ! for other types:

q

q)(!/) flip (("Alice"; 30); ("Bob"; 25); ("Mike"; 43))
"Alice"| 30
"Bob"  | 25
"Mike" | 43

Python

>>> dict([("Alice", 30), ("Bob", 25), ("Mike", 43)])
{'Alice': 30, 'Bob': 25, 'Mike': 43}

Empty dictionaries

You can create a general empty dictionary using general empty lists:

q)()!()     / general empty dictionary

In production, use typed dictionaries to prevent type promotion:

q)(`symbol$())!`float$()    / typed empty dictionary
q)([])~(`symbol$())!()      / empty dictionary with symbol keys

Projection from a dictionary literal syntax

Similar to list literal syntax (;..), omitting a value creates a projection:

q

q)d:([a:101; b:])    / missing values create projections
q)d 102
a| 101
b| 102

Python

>>> d = lambda x: {'a': 101, 'b':x}
>>> d(102)
{'a': 101, 'b': 102}
q)d each `AA`BB`CC  / create a list of dictionaries (table) via projection
a   b
------
101 AA
101 BB
101 CC

Implicit key names

Dictionary definition also makes it convenient to create a dictionary from variables.

q)Alice: 30
q)Bob: 25
q)([Alice; Bob])
Alice| 30
Bob  | 25

q assigns default key names when you omit them.

q)([0;1;2])
x | 0
x1| 1
x2| 2

Operations

Existence of a key

Use in to check the existence of a key.

q

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)`Alice in key d
1b
q)`John in key d
0b

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43}
>>> 'Alice' in d
True
>>> 'John' in d
False
>>>

Lookup and indexing

To find the output value corresponding to an input key, you look up the key. Lookup uses the same notation as indexing into a list:

q

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)d[`Bob]           / indexing
25
q)d `Bob            / brackets not necessary for indexing
25
q)d @ `Bob          / using 'index at'
25
q)d `Mike`Alice     / index by list
43 30
q)d `John           / indexing out of domain returns a null of the same type as the first value
0N

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43}
>>> d['Bob']
25




>>> [d[k] for k in ('Mike', 'Alice') if k in d]
[43, 30]

Indexed assignment

You can edit the value of any key:

q

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)d[`Alice]: 40     / update
q)d
Alice| 40
Bob  | 25
Mike | 43
q)d[`John]: 30     / insert
q)d
Alice| 40
Bob  | 25
Mike | 43
John | 30
q)d[`Alice`Bob]: 50
q)d
Alice| 50
Bob  | 50
Mike | 43
John | 30
q)d[`Alice`Bob]: 60 70
q)d
Alice| 60
Bob  | 70
Mike | 43
John | 30

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43}
>>> d['Alice'] = 40
>>> d
{'Alice': 40, 'Bob': 25, 'Mike': 43}


>>> d.update(dict.fromkeys(['Alice', 'Bob'], 50))
>>> d
{'Alice': 50, 'Bob': 50, 'Mike': 43}


>>> d.update({'Alice': 60, 'Bob': 70})
>>> d
{'Alice': 60, 'Bob': 70, 'Mike': 43}

You can also use general amend at, if the dictionary name is provided as a symbol:

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)@[`d;`Alice;:;50]
q)d
Alice| 50
Bob  | 25
Mike | 43

Subdictionaries

You can extract subdictionaries with the take operator. The inverse operator is cut:

q

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)`Mike`Alice#d         / subdictionary
Mike | 43
Alice| 30
q)(enlist `Alice)#d
Alice| 30
q)`Mike`Alice cut d     / underscore is also accepted
Bob| 25

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43}
>>> {k: d[k] for k in ('Mike', 'Alice') if k in d}
{'Mike': 43, 'Alice': 30}
>>> {'Alice': d['Alice']}
{'Alice': 30}
>>> {k: v for k, v in d.items() if k not in ('Mike', 'Alice')}
{'Bob': 25}

Arithmetic operations

Dictionaries are a generalization of lists: the keys are predefined values rather than sequential integers. Most operators that work on lists also work on dictionaries.

Unary and binary arithmetic operations apply element-wise:

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)2*d
Alice| 60
Bob  | 50
Mike | 86
q)d + ([Alice: 10; Bob: 5; John: 30])
Alice| 40
Bob  | 30
Mike | 43
John | 30
q)neg d
Alice| -30
Bob  | -25
Mike | -43
q)sums d
Alice| 30
Bob  | 55
Mike | 98
q)asc d
Bob  | 25
Alice| 30
Mike | 43
q)iasc d
`Bob`Alice`Mike
q)f: {2*x+1}
q)f d
Alice| 62
Bob  | 52
Mike | 88

Aggregations, some set functions, and some other functions only consider the values:

q

q)d:([Alice: 30; Bob: 25; Mike: 43])
q)count d
3
q)sum d
98
q)d except 25
30 43

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43}
>>> len(d)
3
>>> sum(d.values())
98
>>> [value for key, value in d.items() if value != 25]
[30, 43]

Recall that the keys are ordered, so you can reverse a dictionary, or take or drop its first or last elements:

q

q)reverse ([Alice: 30; Bob: 25; Mike: 43])
Mike | 43
Bob  | 25
Alice| 30
q)2 sublist ([Alice: 30; Bob: 25; Mike: 43])
Alice| 30
Bob  | 25
q)-2 _ ([Alice: 30; Bob: 25; Mike: 43])     / drop elements
Alice| 30

Python

>>> dict(reversed({'Alice': 30, 'Bob': 25, 'Mike': 43}.items()))
{'Mike': 43, 'Bob': 25, 'Alice': 30}


>>> dict(list({'Alice': 30, 'Bob': 25, 'Mike': 43}.items())[:2])
{'Alice': 30, 'Bob': 25}

>>> dict(list({'Alice': 30, 'Bob': 25, 'Mike': 43}.items())[:-2])
{'Alice': 30}

Iteration

Explicit iteration is rarer in q than in other languages, because many operators and functions already iterate for you. Where iteration does earn its place is with a function that is not atomic — one with a side effect, or one that needs to see a whole value at a time. each applies such a function to the values:

q

q)d:([Alice: 30; Bob: 25; Mike: 43])
q){show "the value is ", string x} each d;
"the value is 30"
"the value is 25"
"the value is 43"

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43}
>>> for v in d.values():
...     print("the value is", v)
the value is 30
the value is 25
the value is 43

Do not reach for each when the operator already iterates

An atomic function applies through a dictionary on its own, and doing it directly is both shorter and faster. Write 2*d, not {x*2} each d.

When the function is not atomic, each returns a dictionary with the keys preserved. count is a common case, since it must see each value whole:

q)show kids:([Alice: enlist `Bill; Bob: `$(); Mike: `Maggie`Jack`Ellie])
Alice| ,`Bill
Bob  | `symbol$()
Mike | `Maggie`Jack`Ellie
q)count each kids
Alice| 1
Bob  | 0
Mike | 3

Iterate over one side on its own by taking it as a plain list with key or value. The result is then a list, not a dictionary:

q

q)key d
`Alice`Bob`Mike
q)value d
30 25 43
q){x+1} each value d
31 26 44

Python

>>> list(d.keys())
['Alice', 'Bob', 'Mike']
>>> list(d.values())
[30, 25, 43]
>>> [v+1 for v in d.values()]
[31, 26, 44]

To visit each key together with its value, apply a binary function with the each-both iterator ('):

q

q){string[x], ": ", string y}'[key d; value d] / or (key d) {...}' value d
"Alice: 30"
"Bob: 25"
"Mike: 43"

Python

>>> [f"{k}: {v}" for k, v in d.items()]
['Alice: 30', 'Bob: 25', 'Mike: 43']

Filtering a dictionary is a lookup rather than a loop. where returns the keys that satisfy a condition, and taking those keys with # gives back a dictionary:

q

q)where d > 28
`Alice`Mike
q)(where d > 28) # d
Alice| 30
Mike | 43

Python

>>> [k for k, v in d.items() if v > 28]
['Alice', 'Mike']
>>> {k: v for k, v in d.items() if v > 28}
{'Alice': 30, 'Mike': 43}

Reverse lookup

Use find for a reverse lookup on a dictionary:

q

q)d:([Alice: 30; Bob: 25; Mike: 43; John: 30])
q)d?25          / find
`Bob
q)d?40          / value not found works as for lists
`               / returns a null of the same type as the first key
q)d?30          / only returns the first match
`Alice

Python

>>> d = {'Alice': 30, 'Bob': 25, 'Mike': 43, 'John': 30}
>>> next(k for k, v in d.items() if v == 25)
'Bob'


>>> next(k for k, v in d.items() if v == 30)
'Alice'

You can also use vector operation = and keyword where:

q

q)where d=25
,`Bob
q)where d=40
`symbol$()
q)where d=30
`Alice`John

Python

>>> [k for k, v in d.items() if v == 25]
['Bob']
>>> [k for k, v in d.items() if v == 40]
[]
>>> [k for k, v in d.items() if v == 30]
['Alice', 'John']

Joins

The Join operator (,) merges two dictionaries.

Join on dictionaries has upsert semantics for common keys. If a key exists in both dictionaries, the value from the right operand overwrites the value from the left operand. New keys are appended to the end.

Example:

q

q)([Alice: 30; Bob: 25; Mike: 43]), ([Alice: 10; Bob: 5; John: 30])
Alice| 10
Bob  | 5
Mike | 43
John | 30

Python

>>> {'Alice': 30, 'Bob': 25, 'Mike': 43} | {'Alice': 10, 'Bob': 5, 'John': 30}
{'Alice': 10, 'Bob': 5, 'Mike': 43, 'John': 30}

Null replacement with Coalesce (^)

The coalesce operator ^ uses upsert semantics to merge two dictionaries, with the right operand prevailing over the left on common keys. The difference from , is that null values in the right operand do not prevail over the left.

q)left: ([Alice: 30; Bob: 0N; Mike: 43])
q)right: ([Alice: 10; Bob: 5; Mike: 0N])
q)left ^ right
Alice| 10
Bob  | 5
Mike | 43

Column dictionaries

A dictionary whose value items are all same-length lists is a column dictionary. Column dictionaries are the foundation for tables.

Example:

q)show friends:([name:`Alice`Bob`Mike; dob: 1982.09.15 1984.07.05 1990.11.16; sex: "mfm"])
name| Alice      Bob        Mike
dob | 1982.09.15 1984.07.05 1990.11.16
sex | m          f          m
q)count each friends
name| 3
dob | 3
sex | 3

A column dictionary looks like a matrix, but you index by a key:

q)friends[`name; 1]
`Bob
q)friends . (`name;1)
`Bob

Flip a column dictionary and the result is a table:

q)flip friends
name  dob        sex
--------------------
Alice 1982.09.15 m
Bob   1984.07.05 f
Mike  1990.11.16 m

Step dictionaries

A step dictionary is a dictionary with the sorted attribute applied. When a key is not present, it returns the value associated with the nearest preceding key instead of a null value.

Note

The sorted attribute needs to be applied to the keys of the dictionary, as well as to the dictionary as a whole for it to function as a step dictionary. If the keys are unsorted, q throws 's-fail when you try to create a step dictionary.

An as-of join is an example of a step dictionary: it joins the prevailing value to the time field in the join.

Example:

q)d:(00:00:00; 04:00:00; 09:00:00)!`closed`preopen`open
q)ds:`s#d
q)d 06:00:00
`
q)ds 06:00:00
`preopen

Next steps

  • See Q for Mortals §5. Dictionaries for additional detail on dictionaries.
  • See tables, which are lists of dictionaries.