10

I program in python for a while and I've found that this language is very programmer friendly, so that maybe there is a technique that I don't know how to compose a list with conditional elements. The simplified example is:

# in pseudo code
add_two = True
my_list = [
  "one",
  "two" if add_two,
  "three",
]

Basically I'm looking for a handy way to create a list that contains some that are added under some specific condition.

Some alternatives that don't look such nice:

add_two = True

# option 1
my_list = []
my_list += ["one"]
my_list += ["two"] if add_two else []
my_list += ["three"]


# option 2
my_list = []
my_list += ["one"]
if add_two: my_list += ["two"]
my_list += ["three"]

Is there something that can simplify it? Cheers!

  • Any chance you can create a list of `True` and `False` for the values you want to include? Something like `[True, True, False, True]` to generate `['one', 'two', 'four']`? – Engineero Jan 21 '19 at 22:29
  • @Engineero but how are the bools swapped out for other values in any more of an efficient way? – roganjosh Jan 21 '19 at 22:30

8 Answers8

4

This approach uses a None sentinel value for values to remove, then filters them out at the end. If your data contains None already, you can create another sentinel object to use instead.

add_two = True
my_list = [
    "one",
    "two" if add_two else None,
    "three",
]

my_list = [e for e in my_list if e is not None]

print(my_list)
# ['one', 'two', 'three']
Ben
  • 5,952
  • 4
  • 33
  • 44
4

If you can create a list of bools representing what elements you want to keep from a candidate list, you can do this pretty succinctly. For example:

candidates = ['one', 'two', 'three', 'four', 'five']
include = [True, True, False, True, False]
result = [c for c, i in zip(candidates, include) if i]
print(result)
# ['one', 'two', 'four']

If you can use numpy, this gets even more succinct:

import numpy as np
candidates = np.array(['one', 'two', 'three', 'four', 'five'])
include = [True, True, False, True, False]
print(candidates[include])  # can use boolean indexing directly!
# ['one', 'two', 'four']

Finally, as suggested in a comment, you can use itertools.compress(). Note that this returns an iterator, so you have to unpack it.

from itertools import compress
print([v for v in compress(candidates, include)])
# ['one', 'two', 'four']
Engineero
  • 12,340
  • 5
  • 53
  • 75
  • 3
    This is what [`itertools.compress`](https://docs.python.org/3/library/itertools.html?highlight=itertools#itertools.compress) is used for :) `result = itertools.compress(candidates, include)` – Adam Smith Jan 21 '19 at 22:40
4

Another one line approach, which is more Pythonic in my opinion, would be:

add_two = True    
my_list = ['one'] + ['two'] * add_two + ['three']

In case of multiple conditionned elements, I think other alternatives, such as list of bools, should be used.

RobBlanchard
  • 855
  • 3
  • 17
  • 1
    Multiplication! Brilliant! Adding unpacking we get: `[1, *[2]*False, 3]` → `[1, 3]` and `[1, *[2]*True]` → `[1, 2, 3]`. Thank you! – Denis Sep 12 '22 at 13:15
3

In one line you can write:

my_list = ['one'] + (['two'] if add_two else []) + ['three']

Or use a list comprehension:

my_list = [x for x in ('one', 'two' if add_two else '', 'three') if x]

Or the functional way to remove Falsy values:

my_list = list(filter(None, ('one', 'two' if add_two else '', 'three')))
jpp
  • 159,742
  • 34
  • 281
  • 339
2

First I'd write a simple predicate function that determines whether or not a value should be included. Let's pretend that in this list of integers you only want to include those numbers >0.

def is_strictly_positive(x):
    return x > 0

Then you can have your whole list of numbers:

lst = [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7]

And filter them:

filter(is_strictly_positive, lst)  # 1 2 3 4 5 6 7

which creates a filter object -- a generator that produces the values you need once. If you need a whole list, you can either do:

new_lst = list(filter(is_strictly_positive, lst))  # [1, 2, 3, 4, 5, 6, 7]

or, idiomatically, use a list comprehension

new_lst = [x for x in lst if is_strictly_positive(x)]  # [1, 2, 3, 4, 5, 6, 7]

You can also use itertools.compress to produce a similar result to filter, but it's a little over-engineered in this simple case.

new_lst_gen = itertools.compress(lst, map(is_strictly_positive, lst))  # 1 2 3 4 5 6 7
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
1

The itertools module has many useful functions:

from itertools import compress, dropwhile, takewhile, filterfalse

condition1 = True
condition2 = False
condition3 = True
l = list(range(10))

print(list(compress(['a', 'b', 'c'], [condition1, condition2, condition3])))
# ['a', 'c']

print(list(dropwhile(lambda x: x > 5, l)))
# [5, 6, 7, 8, 9]

print(list(takewhile(lambda x: x < 5, l)))
# [0, 1, 2, 3, 4]

print(list(filterfalse(lambda x: x % 2, l))) # returns elements whose predicates == False
# [0, 2, 4, 6, 8]
Mykola Zotko
  • 15,583
  • 3
  • 71
  • 73
1

Had the same question right now (coming from dartlang / flutter where you can just write if in between the list of elements).

Came up with a solution which looks like that for python:

[
  'some_element',
  *[some_other for some_other in [some_other] if your_condition
]

you could make that more readable by something like this, though:

def maybe_include(element: Any, condition: Callable[[Any], bool) -> list[Any]:
  return list(element) if condition(element) else []  

# ...(somewhere else)...
[
  'some_element',
  *maybe_include(element, lambda element: your_condition),
  'more_static_elements', 
  ...
]

althought this is indeed a bit ugly, it works completely inplace and can be used fluently within declarative definitions.

Ric Hard
  • 599
  • 4
  • 12
0
my_list = [
  "one",
  *("two" for _i in range(1) if add_two),
  "three",
]

The idea is to use the list comprehension syntax to construct either a single-element list with item "two", if add_two is True, or an empty list, if add_two is False, and then unpack it.

victorx
  • 3,267
  • 2
  • 25
  • 35
  • Is the asterisk for multiplication? What is being multiplied, if so? Can you elaborate at all? It looks self-explanatory but I don't follow. – harperville Oct 13 '22 at 15:19
  • 1
    @harperville The asterisk is not for multiplication, but for [iterable unpacking](https://stackoverflow.com/q/53808024/2745495). And this is a good example why people should explain their answers, because it is not self-explanatory. Doing iterable unpacking on a 1-element iterable with `range(1)` is just so unnecessary. – Gino Mempin Oct 13 '22 at 23:28