Andrius Andrius - 1 year ago 128
Python Question

Python creating tuple groups in list from another list

Let's say I have this data:

data = [1, 2, 3, -4, -5, 3, 2, 4, -2, 5, 6, -5, -1, 1]


I need it to be grouped in another list by tuples. One tuple consists of two lists. One for positive numbers, another for negative. And tuples should be created by checking what kind of number it is. Last negative number (I mean in a row that between negative numbers there were no positive ones) means, other numbers must go into another tuple and when it finds another last negative number, it should create another tuple.

So rules are these: All found numbers are being added into first tuple, when it finds negative number, it still adds it to that tuple, till it finds positive number (it means new tuple must be created).

I think it is easier to show, than to explain. After parsing
data
, the list should look like this:

l = [([1, 2, 3], [-4, -5]), ([3, 2, 4], [-2]), ([5, 6], [-5, -1]), ([1], [])]


I created a solution, but I wonder if it's quite optimal. Maybe it is possible to write a more elegant one (and I wonder about performance, is there some better way to write such parser with best possible performance:))?

def neighborhood(iterable):
iterator = iter(iterable)
prev = None
item = iterator.next() # throws StopIteration if empty.
for next in iterator:
yield (prev,item,next)
prev = item
item = next
yield (prev,item,None)

l = []
pos = []
neg = []
for prev, item, next in neighborhood(data):
if item > 0:
pos.append(item)
if not next:
l.append((pos, neg))
else:
neg.append(item)
if next > 0:
l.append((pos, neg))
pos = []
neg = []
elif not next:
l.append((pos, neg))

print l


P.S.
if not next
part I think can be used only once after main check.

Answer Source

I'd use itertools.groupby to make a list of consecutive tuples containing positive/negative lists first, and then group into consecutive pairs. This can still be done in one pass through the list by taking advantage of generators:

from itertools import groupby, zip_longest

x = (list(v) for k,v in groupby(data, lambda x: x < 0))
l = list(zip_longest(x, x, fillvalue=[]))

This gives l as:

[([1, 2, 3], [-4, -5]), ([3, 2, 4], [-2]), ([5, 6], [-5, -1]), ([1], [])]

A couple of notes on the code above:

  • The initial grouping into positive/negative values is handed to groupby which should be reasonably performant (it's compiled code).

  • The zipping-a-generator method for grouping into pairs is a reasonably common idiom in Python. It's guaranteed to work since zip guarantees than an iterable is consumed from left to right.

  • In Python 2, use izip_longest.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download