Reputation: 1481
I have a list of objects, each containing a flag
(boolean) and a value
(integer). The value is increasing for each list item. I want to convert the list into a new list with only the first and last value property (the interval) for every time the flag property alternates.
In other words, I want to convert this
[
{'flag': True, 'value': 0},
{'flag': True, 'value': 5},
{'flag': True, 'value': 10},
{'flag': False, 'value': 15},
{'flag': False, 'value': 20},
{'flag': False, 'value': 25},
{'flag': False, 'value': 30},
{'flag': False, 'value': 35},
{'flag': False, 'value': 40},
{'flag': True, 'value': 45},
{'flag': True, 'value': 50},
{'flag': True, 'value': 55},
{'flag': True, 'value': 60},
{'flag': False, 'value': 65},
{'flag': False, 'value': 70},
{'flag': False, 'value': 75},
{'flag': False, 'value': 80},
{'flag': False, 'value': 85},
{'flag': True, 'value': 90},
{'flag': True, 'value': 95}
]
into this (or something similar):
[
{'flag': True, 'values': (0, 10)},
{'flag': False, 'values': (15, 40)},
{'flag': True, 'values': (45, 60)},
{'flag': False, 'values': (65, 85)},
{'flag': True, 'values': (90, 95)},
]
What is a simple and elegant way of solving this in Python?
I hope the example data isn't too confusing. I've tried solving this both using a for-loop and using reduce, but neither solution seems right.
Thanks!
Upvotes: 2
Views: 1357
Reputation: 2468
Manual solution:
lista = [
{'flag': True, 'value': 0}, {'flag': True, 'value': 5}, {'flag': True, 'value': 10},
{'flag': False, 'value': 15}, {'flag': False, 'value': 20}, {'flag': False, 'value': 25},
{'flag': False, 'value': 30}, {'flag': False, 'value': 35}, {'flag': False, 'value': 40},
{'flag': True, 'value': 45}, {'flag': True, 'value': 50}, {'flag': True, 'value': 55},
{'flag': True, 'value': 60}, {'flag': False, 'value': 65}, {'flag': False, 'value': 70},
{'flag': False, 'value': 75}, {'flag': False, 'value': 80}, {'flag': False, 'value': 85},
{'flag': True, 'value': 90}, {'flag': True, 'value': 95}
]
change = False
output = []
p=[0]*2
flagPast = lista[0]['flag']
for index,item in enumerate(lista):
if(item['flag'] != flagPast):
cp=p[:]
output.append(zip(['flag','values'],[flagPast,cp]))
p[0]=item['value']
change = True
else:
change = False
if(not(change)):
p[1]=item['value']
flagPast = item['flag']
if(index==(len(lista)-1)):
p[1]=item['value']
cp=p[:]
output.append(zip(['flag','values'],[flagPast,cp]))
Upvotes: 0
Reputation: 18906
Pandas solution:
j = [
{'flag': True, 'value': 0}, {'flag': True, 'value': 5},
{'flag': True, 'value': 10}, {'flag': False, 'value': 15},
{'flag': False, 'value': 20}, {'flag': False, 'value': 25},
{'flag': False, 'value': 30}, {'flag': False, 'value': 35},
{'flag': False, 'value': 40}, {'flag': True, 'value': 45},
{'flag': True, 'value': 50}, {'flag': True, 'value': 55},
{'flag': True, 'value': 60}, {'flag': False, 'value': 65},
{'flag': False, 'value': 70}, {'flag': False, 'value': 75},
{'flag': False, 'value': 80}, {'flag': False, 'value': 85},
{'flag': True, 'value': 90}, {'flag': True, 'value': 95}]
import pandas as pd
df = pd.DataFrame(j)
df['group'] = df['flag'].ne(df['flag'].shift()).cumsum() # column to groupby
output = df.groupby("group").apply(lambda x: \
{'flag':x["flag"].tolist()[0],
'values':(x["value"].min(),x["value"].max())})
output.tolist()
Outputs:
[{'flag': True, 'values': (0, 10)},
{'flag': False, 'values': (15, 40)},
{'flag': True, 'values': (45, 60)},
{'flag': False, 'values': (65, 85)},
{'flag': True, 'values': (90, 95)}]
Dataframe looks like:
flag value group
0 True 0 1
1 True 5 1
2 True 10 1
3 False 15 2
4 False 20 2
5 False 25 2
6 False 30 2
7 False 35 2
8 False 40 2
9 True 45 3
...
Upvotes: 0
Reputation: 82889
You can use itertools.groupby
to group the items by flag
and then convert the groups to lists and get the values of the first and last elements of those lists. Here, lst
is your original list.
>>> [{"flag": key, "values" : (val[0]["value"], val[-1]["value"])}
... for key, val in ((key, list(val)) for key, val in itertools.groupby(lst, key=lambda d: d["flag"]))]
[{'flag': True, 'values': (0, 10)},
{'flag': False, 'values': (15, 40)},
{'flag': True, 'values': (45, 60)},
{'flag': False, 'values': (65, 85)},
{'flag': True, 'values': (90, 95)}]
Or similar, using an inner list comprehension to extract the value
keys first, as we have to use an inner generator expression to convert the group iterator to list anyway. The result is the same.
>>> [{"flag": key, "values" : (vals[0], vals[-1])}
... for key, vals in ((key, [g["value"] for g in grp])
... for key, grp in itertools.groupby(lst, key=lambda d: d["flag"]))]
But that's still far from being readable, so I'd suggest splitting this up into two lines:
>>> groups = ((key, [g["value"] for g in grp])
... for key, grp in itertools.groupby(lst, key=lambda d: d["flag"]))
>>> result = [{"flag": key, "values" : (vals[0], vals[-1])} for key, vals in groups]
Upvotes: 3