Как вы обобщаете создание списка со многими переменными и условиями `if`?

Я создаю список следующим образом:

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

Как обобщить создание такого списка, чтобы его можно было легко расширить за счет большего числа переменных и последующих условий?

Ответы

Ответ 1

Форматирование строк

Почему бы не использовать здесь операцию modulo и не форматировать строку, например:

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

Здесь мы вычисляем x%4 и добавляем это к 'v 'в строке. Самое приятное, что мы можем легко изменить 4 на другое число.

Кортеж или индексирование списка

Если строка вывода не соответствует такой структуре, мы можем построить список или кортеж для хранения значений. Как:

# 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).

(по умолчанию) словарь

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

или в случае, если мы хотим получить "резервное" значение, значение, используемое при сбое поиска:

from collections import defaultdict

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

где здесь 1234 используется как запасное значение, а затем мы можем использовать:

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

Использование d[x%4] over d.get(x%4) данного d - это словарь, который может быть более полезен, если мы хотим, чтобы эти поисковые запросы не проходили незамеченными. В этом случае будет ошибка. Хотя ошибки, как правило, не являются хорошим знаком, может быть лучше поднять ошибку в случае неудачи поиска, чем добавить значение по умолчанию, поскольку это может быть признаком того, что что-то не работает правильно.

Ответ 2

Вот мои попытки создания общего решения. Во-первых, настройка -

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

Первые два варианта основаны на чистом-питоне, в то время как последние два используют числовые библиотеки (numpy и pandas).


Вариант 1

dict. get

Создать mapping ключей к значениям. В понимании списка запрос 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']

Здесь 'v3' - это значение по умолчанию, которое возвращается, когда результат x % 4 не существует в качестве ключа в mapping.

Это будет работать для любого произвольного множества условий и значений, а не только из условия, изложенного в вопросе (по модулю арифметики).


Вариант 2

collections. defaultdict

Аналогичное решение можно было бы использовать, используя 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']

Это похоже на вариант 1.


Вариант 3

numpy. char. add

Если вы используете numpy, вас может заинтересовать векторизованное решение, включающее в себя модульную арифметику и расширенное вещание -

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

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

Если вам нужен список в качестве конечного результата, вы можете вызвать r.tolist(). Обратите внимание, что это решение оптимизировано для вашего конкретного случая использования. Более общий подход будет достигнут с помощью numpy с помощью np.where/np.select.


Вариант 4

pd. Series. mod + pd. Series. radd

Аналогичное решение также будет работать с 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']

Ответ 3

В данном примере ясно, что мы можем "сжать" условия, которые приводят к конкретным решениям, которые были приведены здесь. В общем случае, однако, мы не можем предположить, что существует "трюк", чтобы быстро выписать все возможные условия в одной строке.

Я выписал бы все условия в функции:

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

Если вы просто используете операции сравнения, вы можете просто использовать collections.defaultdict, как показано в других ответах. Если условия более сложны, вам, вероятно, придется записывать всю функцию, как показано.

Теперь, когда вы перечислите понимание, вы можете просто сделать:

values = [conditions(x) for x in my_list_of_values]

Ответ 4

def condition(rule, out):
  return lambda x: out(x) if rule(x) else None

def rule1(x): return x%4 == 0
def out1(x): return 'v0'

def rule2(x): return x%4 == 1
def out2(x): return 'v1'

def rule3(x): return x%4 == 2
def out3(x): return 'v2'

lastrule = lambda x: True
lastout = lambda x: 'v3'

check1 = condition(rule1, out1)
check2 = condition(rule2, out2)
check3 = condition(rule3, out3)
check_last = condition(lastrule, lastout)

def tranform(*check_list):
  def trans_value(x):
    for trans in check_list:
      if trans(x) is not None:
        return trans(x)
  return trans_value

list_1=[4,5,6,7,8]      
print([tranform(check1, check2, check3, check_last)(x) for x in list_1])

Для длительных проверок сначала может быть проще сформировать список условий. Предположим, что условная формула и вывод - это как функции x, так и другие входные параметры. Нижеприведенный способ сохраняет некоторую типизацию, но сохраняет аддитивность для длительных проверок.

Чтобы достичь еще более общего метода (более сложные условия, несколько параметров), некоторые сложные процедуры могут быть полезны (что-то вроде both(greater, either(smaller, identity)), и вся программа должна быть снова реструктурирована, а это значит, что аддитивность программы еще не идеал, так как он пока недостаточно общий.

outconstants = ['v0', 'v1', 'v2', 'v3']
# for this specific example. In general, only outf is needed (see below)
n = len(outconstant)

outf = lambda out: lambda x: out
outs = [outf(out) for out in outconstants]
# define your own outf formula, if not output constant
# define multiple formulas and put into list, if different type of outputs are needed

rights = map(lambda constant: lambda x: constant, range(n-1)) 
lefts = [lambda x: x%4 for _ in range(n-1)]
# right and left formulas can be also defined separately and then put into list

def identity(a, b): return lambda x: a(x) == b(x) 
# define other rules if needed and form them into rules list with proper orders
# e.g., def greater(a, b): return lambda x: a(x) > b(x), ...


lastrule=lambda x: True

rules = list(map(identity, lefts, rights))
rules.append(lastrule)
# in complex case, each unique rule needs to be defined separately and put into list
# if new rule is needed, define it and append here before lastrule (additive)

def transform(rules, outs):
    def trans_value(x):
        for rule, out in zip(rules, outs):
            if rule(x):
                return out(x)
    return trans_value

list_1=[4,5,6,7,8]
print([transform(rules, outs)(x) for x in list_1])