Package: rel2tree
rel2treeConvert your list of data intoJSONserializable structure.MotivationLet's suppose you have a set of data given as a list of dicts:importjson[{"name":"Jane","city":"New York","sales":23},{"name":"Joe","city":"New York","sales":11},{"name":"Jane","city":"Chicago","sales":21},{"name":"Jane","city":"New York","sales":4},{"name":"Joe","city":"New York","sales":13},{"name":"Joe","city":"Chicago","sales":31},{"name":"Jane","city":"New York","sales":7},]You may want a nice summary, something like this:[{"name":"Jane","cities":[{"city":"New York","sales":34},{"city":"Chicago","sales":21}],"sum":55},{"name":"Joe","cities":[{"city":"New York","sales":24},{"city":"Chicago","sales":31}],"sum":55}]This can be done relatively easily by iterating over the data
set and building the final structure.summary={}forrecordindata:this_person=summary.setdefault(record["name"],{"name":record["name"],"cities":{},"sum":0,})this_person_cities=this_person["cities"].setdefault(record["city"],{"city":record["city"],"sum":0,})this_person_cities["sum"]+=record["sales"]this_person["sum"]+=record["sum"]summary=list(summary.values())forpersoninsummary:person["cities"]=list(person["cities"].values())print(json.dumps(summary))Although the above code works well, but it has some problems.Not declarative: by looking at the code it is not trivial to tell the final data
structure.Error-prone.The complexity grows with more complex business logic
or by adding an additional level.Not reusable.Let's see how you do it withrel2tree:fromrel2treeimportf# NOQAsummary=f.groupby(lambdax:x["name"],f.dict({"name":f.groupkey(),"cities":f.groupby(lambdax:x["city"],f.dict({"city":f.groupkey(),"sum":f.map(lambdax:x["sales"]).t(sum)})),"sum":f.map(lambdax:x["sales"]).t(sum)}))print(json.dumps(summary(data)))Tutorialmap,sort,filter,distinctThe only object one can import fromrel2treeisf, which is of typeFso we will call it anFobject.fis callable, but - on it's own does nothing:print(f(2))# 2Let's say we have a list of numbers (numbers) and we want
to duplicate all of it's elements. This can be done in many ways:using a list comprehension:out=[2*xforxinnumbers]using map:out=map(lambdax:2*x,numbers)defining a function (for reusability)importfunctoolsdup=functools.partial(map,lambdax:2*x)out=dup(numbers)Using anfit looks like this:numbers=range(15)dup=f.map(lambdax:2*x)out=dup(numbers)This simply made our third approach a little more terse.Now what if our task is to add 1 to each element after
duplication? Can we reuse ourdupfunction? As
the result off.maphas the same type asf, we can
use map again:dupplus1=dup.map(lambdax:x+1)f.sort(fnc)sorts our list based on the value offncapplied to the items (just as thekeyargument of python's)sorted.f.filter(fnc)keeps only thoseiitems, wherefnc(i)is ture(ish). These methods also returnFs
(internally the type offisF) so they are chainable.
TheFbelow first duplicates, then filters out big
numbers and finally sorts them. (f.sort, without a function sorts the elements.)f.map(lambdax:2*x).filter(lambdax:x<10).sort()dictBack to ournumbers, but with the desired output of{"even":[0,2,4,6,8,10,12,14],"odd":[1,3,5,7,9,11,13]}We can combine the dict method to achive this:summary=f.dict({"even":f.filter(lambdax:(x%2==0)),"odd":f.filter(lambdax:(x%2==1)),})If the dictionary values areFobjects, those objects will be called with
the input list to form the final values, otherwise the values will be left as is.groupbyTo generalize the above example, we can group our numbers based on the remainder
devided by, say, 3:summary=f.groupby(lambdax:x%3)# [[0, 3, 6, 9, 12], [1, 4, 7, 10, 13], [2, 5, 8, 11, 14]]To make it more informative, the desired output should be:[{"remainder":0,"numbers":[0,3,6,9,12]},{"remainder":1,"numbers":[1,4,7,10,13]},{"remainder":2,"numbers":[2,5,8,11,14]}]This can be done by usinggroupkey:summary=f.groupby(lambdax:x%3,f.dict({"remainder":f.groupkey(),"numbers":f}))f.groupkey(level=0)gives the deepest level group key, whilef.groupkey(1)is the one level above group key in case of nestedgroupby's.