henkimon
henkimon

Reputation: 1481

Convert a list of values to a list of intervals in Python

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

Answers (3)

farbiondriven
farbiondriven

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

Anton vBR
Anton vBR

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

tobias_k
tobias_k

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

Related Questions