map, filter, reduce, apply โ the core four.
There are ~500 ZefOps in the box. You don't need to memorize them all. You need to learn the shapes โ because once you know the shape, the docs tell you the rest.
Every ZefOp has a shape:
For example: map(f) takes a list, returns a list.
reduce(f) takes a list, returns one value.
filter(p) takes a list, returns a shorter list.
Match shapes and the pipe just works.
[1, 2, 3] | map(add(1)) | collect # [2, 3, 4]
['a', 'b'] | map(to_upper_case) | collect # ['A', 'B']
[1, 4, 9] | map(str) | collect # ['1', '4', '9']
Classic functional map. One thing in โ one thing out, preserving the shape of the collection.
Remember Z? It's a placeholder that makes predicates and small
transforms look like math:
[1, 2, 3, 4] | map(Z * Z) | collect # [1, 4, 9, 16]
[1, 2, 3] | map(Z + 10) | collect # [11, 12, 13]
[{'x':1}, {'x':5}] | map(Z['x']) | collect # [1, 5]
apply is the singular sibling of map:
5 | apply(Z * Z) | collect # 25
'hello' | apply(to_upper_case) | collect # 'HELLO'
mapYou have a list and want to transform each element.
applyYou have a single value and want to run a function on it.
Pass a dict of functions to fan out:
5 | apply({
'plus_one': add(1),
'doubled': multiply(2),
'squared': Z * Z,
}) | collect
# {'plus_one': 6, 'doubled': 10, 'squared': 25}
Or a list:
5 | apply([add(1), multiply(2), Z * Z]) | collect
# [6, 10, 25]
map combined with apply({...}) gives you "for each
thing, compute several summaries":
numbers = [10, 20, 30]
numbers | map(apply({
'val': Z,
'sqr': Z * Z,
'bin': bin,
})) | collect
# [{'val':10,'sqr':100,'bin':'0b1010'},
# {'val':20,'sqr':400,'bin':'0b10100'}, ...]
[1, 2, 3, 4, 5] | filter(Z > 2) | collect # [3, 4, 5]
[{'age': 20}, {'age': 15}] | filter(F.age >= 18) | collect
# [{'age': 20}]
# filter by type
[1, 'a', 2.5, 'b'] | filter(String) | collect # ['a', 'b']
Filter accepts a predicate. The predicate can be:
Z > 2)F.age >= 18)String, Int)[1, 2, 3, 4] | reduce(add) | collect # 10
[2, 3, 4] | reduce(multiply) | collect # 24
['a', 'b', 'c'] | reduce(add) | collect # 'abc'
# with an initial value:
[1, 2, 3] | reduce(add, 100) | collect # 106
[] | reduce(add, 0) | collect # 0 (safe for empty)
[1, 2, 3, 4] | scan(add) | collect
# [1, 3, 6, 10] โ running total
[1, 2, 3] | scan(add, 100) | collect
# [100, 101, 103, 106]
Super useful for running totals, cumulative counts, moving windows.
Often you want to grab one thing from a collection:
[1, 2, 3] | first | collect # 1
[1, 2, 3] | last | collect # 3
[1, 2, 3] | nth(1) | collect # 2 (0-indexed)
[1, 2, 3] | length | collect # 3
[3, 1, 2] | sort | collect # [1, 2, 3]
[1, 1, 2] | unique | collect # [1, 2]
[[1,2],[3]] | flatten | collect # [1, 2, 3]
F operatorF.field is shorthand for "grab the field attribute/key":
person = {'name': 'Alice', 'age': 30}
person | F.name | collect # 'Alice'
# chain F.s for deep fields
data | F.user | F.address | F.city | collect
You can use F.xxx inside other ops:
people = [{'name':'a','age':20}, {'name':'b','age':15}]
people | filter(F.age >= 18) | map(F.name) | collect
# ['a']
Let's combine what we have. Given a list of orders, find the top 3 customers by total spend.
orders = [
{'customer': 'A', 'amount': 100},
{'customer': 'B', 'amount': 50},
{'customer': 'A', 'amount': 200},
{'customer': 'C', 'amount': 75},
{'customer': 'B', 'amount': 300},
{'customer': 'A', 'amount': 25},
]
top3 = (
orders
| group_by(F.customer) # {'A':[...], 'B':[...], 'C':[...]}
| items # [('A',[...]), ('B',[...]), ('C',[...])]
| map(apply_functions([
identity, # keep customer name
map(F.amount) | reduce(add), # sum amounts
])) # [('A', 325), ('B', 350), ('C', 75)]
| sort_by(nth(1), descending=True)
| take(3)
| collect
)
# => [('B', 350), ('A', 325), ('C', 75)]
Given scores = [72, 85, 90, 68, 74, 91, 88], write a pipeline that returns the average of the passing scores (โฅ 70).
scores | filter(Z >= 70) | apply({
'sum': reduce(add),
'n': length,
}) | apply(Z['sum'] / Z['n']) | collect
# => 83.333...
| op | input โ output | one-liner |
|---|---|---|
map(f) | [a] โ [f(a)] | transform each |
apply(f) | a โ f(a) | transform one |
filter(p) | [a] โ [a where p(a)] | keep matching |
reduce(f) | [a] โ a | fold |
scan(f) | [a] โ [a] | fold with history |
first / last / nth(i) | [a] โ a | pick one |
length | [a] โ Int | count |
sort / sort_by(k) | [a] โ [a] | order |
unique | [a] โ [a] | dedupe |
flatten | [[a]] โ [a] | one level |
take(n) / drop(n) | [a] โ [a] | slice |
group_by(k) | [a] โ {k: [a]} | bucket |
reverse | [a] โ [a] | flip |
F.x | dict โ value | project field |
log | a โ a | print & pass through (debug!) |
logInsert | log anywhere in a pipeline to see the value at that point:
data | op1 | log | op2 | log | op3 | collect
Prints ๐ชต: <value> at each log and passes the value through unchanged. Best debugger Zef has.
Next up: the fancier ops โ match, rearrange, and the full power of Z. โ