Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you generalise the creation of a list with many variables and conditions of `if`?

I create a list as follows:

['v0' if x%4==0 else 'v1' if x%4==1 else 'v2' if x%4==2 else 'v3' for x in list_1]

How to generalize the creation of such a list, so that it can be easily expanded by a larger number of variables and subsequent conditions?

like image 687
Tomasz Przemski Avatar asked Jan 07 '18 18:01

Tomasz Przemski


3 Answers

String formatting

Why not use a modulo operation here, and do string formatting, like:

['v{}'.format(x%4) for x in list_1]

Here we thus calculate x%4, and append this to 'v' in the string. The nice thing is that we can easily change 4, to another number.

Tuple or list indexing

In case the output string do not follow such structure, we can construct a list or tuple to hold the values. Like:

# in case the values do not follow a certain structure
vals = ('v0', 'v1', 'v2', 'v3')
[vals[x%4] for x in list_1]

By indexing it that way, it is clear what values will map on what index. This works good, given the result of the operation - here x%4 - maps to a n (with a reasonable small n).

(Default)dictionary

In case the operation does not map on a n, but still on a finite number of hashable items, we can use a dictionary. For instance:

d = {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'}

or in case we want a "fallback" value, a value that is used given the lookup fails:

from collections import defaultdict

d = defaultdict(lambda: 1234, {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'})

where here 1234 is used as a fallback value, and then we can use:

[d[x%4] for x in list_1]

Using d[x%4] over d.get(x%4) given d is a dictionary can be more useful if we want to prevent that lookups that fail pass unnoticed. It will in that case error. Although errors are typically not a good sign, it can be better to raise an error in case the lookup fails than add a default value, since it can be a symptom that something is not working correctly.

like image 102
Willem Van Onsem Avatar answered Nov 14 '22 08:11

Willem Van Onsem


Here are my attempts at a generic solution. First, the setup -

list_1 = [1, 2, 4, 5, 10, 4, 3]

The first two options are pure-python based, while the last two use numerical libraries (numpy and pandas).


dict.get

Generate a mapping of keys to values. In the list comprehension, query dict.get -

mapping = {0 : 'v0', 1 : 'v1', 2 : 'v2'}
r = [mapping.get(x % 4, 'v3') for x in list_1]

r
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']

Here, 'v3' is the default value that is returned when the result of x % 4 does not exist as a key in mapping.

This would work for any arbitrary set of conditions and values, not just the condition outlined in the question (modulo arithmetic).


collections.defaultdict

A similar solution would be possible using a defaultdict -

from collections import defaultdict

mapping = defaultdict(lambda: 'v3', {0: 'v0', 1: 'v1', 2: 'v2', 3: 'v3'})
r = [mapping[x % 4] for x in list_1]

r
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']

This works similar to Option 1.


numpy.char.add

If you use numpy, then you might be interested in a vectorised solution involving modulo arithmetic and broadcasted addition -

r = np.char.add('v', (np.array(list_1) % 4).astype('<U8'))

r
array(['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3'],
      dtype='<U9')

If you require a list as the final result, you can call r.tolist(). Note that this solution is optimised for your particular use case. A more generic approach would be achieved with numpy using np.where/np.select.


pd.Series.mod + pd.Series.radd

A similar solution would also work with pandas mod + radd -

r = pd.Series(list_1).mod(4).astype(str).radd('v')
r

0    v1
1    v2
2    v0
3    v1
4    v2
5    v0
6    v3
dtype: object

r.tolist()
['v1', 'v2', 'v0', 'v1', 'v2', 'v0', 'v3']
like image 20
cs95 Avatar answered Nov 14 '22 09:11

cs95


In the given example, it's clear that we can "compress" the conditions, which lead to the specific solutions that were given here. In the general case though, we can't assume that there is some "trick" to quickly write out all possible conditions in a single line.

I'd write out all the conditions in a function:

def conditions(x):
    if x == <option a>:
        return <result a>
    elif x == <option b>:
        return <result b>
    .
    .
    .
    else:
        return <default option>

If you're just using compare operations, you can just use a collections.defaultdict, as shown in the other responses. If the conditions are more complex, then you'd probably have to write out the whole function as shown.

Now for you list comprehension, you can just do:

values = [conditions(x) for x in my_list_of_values]
like image 2
Jonathan Mosenkis Avatar answered Nov 14 '22 10:11

Jonathan Mosenkis