Changing several elements in a list using list comprehension

Viewed 110

An example:

example = [None, b'1', b'2', b'3', b'4', b'1', None, None, b'8', b'56', b'66', b'77', b'1', None]

I need to change values in this list in the following way: bytes should be converted to int, None to 0 and every 6th and 7th element to a bool. The expected output:

expected_output = [0, 1, 2, 3, 4, True, False, 0, 8, 56, 66, 77, True, False]

My attempt:

[int(value) if value else bool(value) if index in (5,6) else 0 for index, value in enumerate(example)]

The result:

[0, 1, 2, 3, 4, True, False, 0, 8, 56, 66, 77, 0, 0]

I know there is a problem in this part of my code:

[int(value) if value else bool(value) if index in (5,6) else 0 for index, value in enumerate(example)]
                                       ^
                                       |
                                       |

How can I change every 6th and 7th element to a bool?

3 Answers

You need to check whether the index modulo 7 (the number of elements in each group) is 5 or 6. But you also need to do that first since b'1' also passes the if value test:

[bool(value) if index % 7 in (5,6) else int(value) if value else 0 for index, value in enumerate(example)]

Output:

[0, 1, 2, 3, 4, True, False, 0, 8, 56, 66, 77, True, False]

The more logic you add to this list comprehension the more unreadable it will become. I'd suggest you to go with a normal if/elif statement here:

for ix, i in enumerate(example):
    if ix % 7 in (5,6):
        example[ix] = bool(i)
    elif not i:
        example[ix] = 0
    elif isinstance(i, bytes):
        example[ix] = int(i)

print(example)
# [0, 1, 2, 3, 4, True, False, 0, 8, 56, 66, 77, True, False]

Basic

You can implement rotation with %(modulo) operator.

example = [
    None, b'1', b'2', b'3', b'4', b'1', None,
    None, b'8', b'56', b'66', b'77', b'1', None,
]

results = [
    bool(value) if index % 7 in {5, 6} else
    int(value) if value else 0
    for index, value in enumerate(example)
]
print(results)

output:

[0, 1, 2, 3, 4, True, False, 0, 8, 56, 66, 77, True, False]

Pythonic

But, there is some more pythonic way:

import itertools

example = [
    None, b'1', b'2', b'3', b'4', b'1', None,
    None, b'8', b'56', b'66', b'77', b'1', None,
]
converters = itertools.cycle(itertools.chain(
    itertools.repeat(lambda x: int(x) if x else 0, 5),
    itertools.repeat(bool, 2),
))

results = [
    converter(value) for converter, value in zip(converters, example)
]
print(results)

output: same with above.

Explanation

  • itertools.cycle: Make a cyclic iterator.
  • itertools.chain: Concatenate iterables.
  • itertools.repeat: Make iterator with repeating by specified number.
  • So converters = ... part means: lambda x: int(x) if x else 0 fucntion with five times, bool with two times, and repeat this cycle!
  • You can add multiple rules for each position without complicated if-else.
Related