norman
norman

Reputation: 5566

Conditionally pair list items in python

I have a list like this:

[u'1.9', u'comment', u'1.11', u'1.5', u'another comment']

I want to split it into tuples such that number strings (for which isdigit(item[0]) is True) are paired with either the comment that comes immediately after them, or with an empty string if there is no comment (i.e., next item is another number string).

In other words:

[
  (u'1.9', u'comment'),
  (u'1.11', ''),
  (u'1.5', u'another comment'),
]

What is the cleanest way to go about this, especially since the list could be odd or even in length?

Upvotes: 1

Views: 231

Answers (4)

liborm
liborm

Reputation: 2724

I'd go with something like this (pairwise comes from itertools cookbook):

input = [u'1.9', u'comment', u'1.11', u'1.5', u'another comment']

from itertools import tee, izip
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

import re
v_re = re.compile('\d\.\d+')

[(a, b) if not v_re.match(b) else (a, '') for a, b in pairwise(input + [u'']) if v_re.match(a)]
# [(u'1.9', u'comment'), (u'1.11', ''), (u'1.5', u'another comment')]

It should work both for single element in input and for input ending with version string.

Upvotes: 0

eval
eval

Reputation: 1239

Firstly, "isdigit" can only seem test integer, so for simplicity I firstly assume the input is:

>>> lst = [u'9', u'comment', u'11', u'1001', u'another comment']

Then the solution is one liner:

>>> [(i, '') if s.isdigit() else (i, s) for i, s in zip(lst[:-1], lst[1:]) if i.isdigit()]
[(u'9', u'comment'), (u'11', ''), (u'1001', u'another comment')]

Explanation

zip generates all the pairs,

>>> lst = ['a', 'b', 'c', 'd'] 
>>> zip(lst[:-1], lst[1:]
[('a', 'b'), ('b', 'c'), ('c', 'd')]

Then use a following if statement to filter the pairs starts with an integer string.

>>> [(i, s) for i, s in zip(...) if i.isdigit()]

Finally, use if else statement to generate result, which is either (i, '') or (i, s)

BTW, this doesn't work when there's only one element in the lst. In this case you should handle specifically.

Upvotes: 0

Cody Bouche
Cody Bouche

Reputation: 955

Here is how to do it in a single list comprehension.

my_list = [u'1.9', u'comment', u'1.11', u'1.5', u'another comment']

result = [(x,('' if i + 1 >= len(my_list) or my_list[i + 1].replace('.','').isdigit() else my_list[i + 1])) for i, x in enumerate(my_list) if x and x.replace('.','').isdigit()]

Upvotes: 1

Martijn Pieters
Martijn Pieters

Reputation: 1122082

Your best bet is to use a generator function to do the pairing:

def number_paired(items):
    items = iter(items)
    number = next(items)
    while number is not None:
        comment = next(items, None)
        if comment is None or comment[0].isdigit():
            # really a number, or end of the iterable
            yield number, u''
            number = comment
            continue
        yield number, comment
        number = next(items)

You can then just iterate over the generator or produce a list from it with:

result = list(number_paired(items))

This also handles the case where you have a number at the end and no following comment:

>>> def number_paired(items):
...     items = iter(items)
...     number = next(items)
...     while number is not None:
...         comment = next(items, None)
...         if comment is None or comment[0].isdigit():
...             # really a number, or end of the iterable
...             yield number, u''
...             number = comment
...             continue
...         yield number, comment
...         number = next(items)
... 
>>> items = [u'1.9', u'comment', u'1.11', u'1.5', u'another comment']
>>> list(number_paired(items))
[(u'1.9', u'comment'), (u'1.11', u''), (u'1.5', u'another comment')]
>>> list(number_paired(items + [u'42']))
[(u'1.9', u'comment'), (u'1.11', u''), (u'1.5', u'another comment'), (u'42', u'')]

Upvotes: 2

Related Questions