Piotr Paczkowski
Piotr Paczkowski

Reputation: 163

How to compose a list with conditional elements

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!

Upvotes: 14

Views: 7037

Answers (12)

Axel Heider
Axel Heider

Reputation: 626

Adding to the answer in https://stackoverflow.com/a/74034817/831399, you can use this also, which might be a bit easer to understan:

add_two = True
my_list = [
  "one",
  *(["two"] if add_two else []),
  "three",
]

The * operator unpacks the element from the lists that is created in both cases.

Upvotes: 0

Mykola Zotko
Mykola Zotko

Reputation: 17804

The itertools module has many useful functions:

from itertools import compress, dropwhile, takewhile, filterfalse

l = list(range(10))

list(compress(['a', 'b', 'c'], [True, False, True]))
# ['a', 'c']

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

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

list(filterfalse(lambda x: x % 2, l)) # returns elements whose predicates == False
# [0, 2, 4, 6, 8]

Upvotes: 2

camball
camball

Reputation: 55

This answer applies strictly for lists of functions, but I implemented an explicit no-op function that's conditionally applied:

from collections.abc import Callable
from functools import reduce
from typing import Any  # use a more specific type for your use case

def no_op[T](x: T) -> T:  # where `T` ends up being `Any` in our case
    return x

condition = True

FUNCTIONS_TO_APPLY: list[Callable[[Any], Any]]] = [
    do_something,  # assumed to be defined elsewhere
    conditionally_apply if condition else no_op,
    do_something_else,
]

data_to_apply_functions_on: Any

output = reduce(
    lambda obj, func: func(obj),
    FUNCTIONS_TO_APPLY,
    data_to_apply_functions_on
)

Upvotes: -1

Catskul
Catskul

Reputation: 19240

There are already plenty of decent answers, but just in case someone wants one that is a bit less clever than the splat or multiplication tricks but still succinct:

messages = [ 
    msg for msg, test in [
        ("Text for condition 1", test1),
        ("Text for condition 2", test2),
    ] if test
]

Upvotes: 0

Ric Hard
Ric Hard

Reputation: 609

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.

Upvotes: 1

NSGoat
NSGoat

Reputation: 47

This can be achieved using boolean multiplication and list unpacking (expanded from @denis's comment):

*[value]*condition

The first * unpacks the value and the second * multiplies by the boolean condition to include or exclude it.

is_home_alone = False
is_filthy_animal = True

my_list = [
    *['--home-alone']*is_home_alone,
    *['--count-to-ten']*is_filthy_animal,
    *['one', 2., 10]*is_filthy_animal, # Multiple types and values
]

# returns: ['--count-to-ten', 'one', 2.0, 10]

Upvotes: 2

victorx
victorx

Reputation: 3559

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.

Upvotes: 0

RobBlanchard
RobBlanchard

Reputation: 905

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.

Upvotes: 5

Adam Smith
Adam Smith

Reputation: 54173

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

Upvotes: 1

Engineero
Engineero

Reputation: 12908

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']

Upvotes: 3

Ben
Ben

Reputation: 6348

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']

Upvotes: 4

jpp
jpp

Reputation: 164623

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')))

Upvotes: 7

Related Questions