Промежуточная переменная в понимании списка для одновременной фильтрации и преобразования - PullRequest
15 голосов
/ 04 ноября 2010

У меня есть список векторов (в Python), которые я хочу нормализовать, в то же время удаляя векторы, которые изначально имели небольшие нормы.

Список ввода, например,

a = [(1,1),(1,2),(2,2),(3,4)]

И мне нужно, чтобы вывод был (x*n, y*n) с n = (x**2+y**2)**-0.5

Если бы я, например, просто нуждался в нормах, это было бы легко с пониманием списка:

an = [ (x**2+y**2)**0.5 for x,y in a ]

Было бы также легко хранить, например, только нормализованный x, но я хочу иметь эту временную переменную "n" для использования в двух вычислениях, а затем выбросить ее.

Я не могу просто использовать лямбда-функцию, потому что мне также нужен n для фильтрации списка. Так какой же самый лучший способ?

Прямо сейчас я использую это понимание вложенного списка здесь (с выражением во внутреннем списке):

a = [(1,1),(1,2),(2,2),(3,4)]

[(x*n,y*n) for (n,x,y) in (( (x**2.+y**2.)**-0.5 ,x,y) for x,y in a) if n < 0.4]

# Out[14]: 
# [(0.70710678118654757, 0.70710678118654757),
#  (0.60000000000000009, 0.80000000000000004)]

Внутренний список генерирует кортежи с дополнительным значением (n), а затем я использую эти значения для расчетов и фильтрации. Это действительно лучший способ? Есть ли какие-то ужасные недостатки, о которых я должен знать?

Ответы [ 4 ]

11 голосов
/ 04 ноября 2010
Is this really the best way?

Ну, это работает эффективно, и если вы действительно, действительно хотите писать oneliners, то это лучшее, что вы можете сделать.

С другой стороны, простая 4-строчная функция сделала бы то же самое намного яснее:

def normfilter(vecs, min_norm):
    for x,y in vecs:
        n = (x**2.+y**2.)**-0.5
        if min_norm < n:
            yield (x*n,y*n)

normalized = list(normfilter(vectors, 0.4))

Кстати, в вашем коде или описании есть ошибка - вы говорите, что отфильтровываете короткие векторы, но ваш код делает наоборот: p

1 голос
/ 27 апреля 2019

Начиная с Python 3.8 и введением выражений присваивания (PEP 572) (оператор :=), можно использовать локальную переменную в пределах понимания списка, чтобы избежать многократного вызова то же выражение:

В нашем случае мы можем назвать оценку (x**2.+y**2.)**-.5 как переменную n, используя результат выражения для фильтрации списка, если n уступает 0.4; и, таким образом, повторно используйте n для получения сопоставленного значения:

# vectors = [(1, 1), (1, 2), (2, 2), (3, 4)]
[(x*n, y*n) for x, y in vectors if (n := (x**2.+y**2.)**-.5) < .4]
# [(0.7071067811865476, 0.7071067811865476), (0.6000000000000001, 0.8)]
1 голос
/ 04 ноября 2010

Похищение кода из unutbu, вот более крупный тест, включающий в себя цифровую версию и версию итератора Обратите внимание, что преобразование списка в numpy может занять некоторое время.

import numpy

# a = [(1,1),(1,2),(2,2),(3,4)]
a=[]
for k in range(1,10):
    for j in range(1,10):
        a.append( (float(k),float(j)) )

npa = numpy.array(a)

def two_lcs(a):
    an = [ ((x**2+y**2)**-0.5, x,y) for x,y in a ]
    an = [ (x*n,y*n) for n,x,y in an if n < 5.0 ]
    return an

def using_iterator(a):
    def normfilter(vecs, min_norm):
        for x,y in vecs:
            n = (x**2.+y**2.)**-0.5
            if n < min_norm:
                yield (x*n,y*n)

    return list(normfilter(a, 5.0))

def using_forloop(a):
    result=[]
    for x,y in a:
        n=(x**2+y**2)**-0.5
        if n<5.0:
            result.append((x*n,y*n))
    return result

def using_lc(a):    
    return [(x*n,y*n)
            for (n,x,y) in (( (x**2.+y**2.)**-0.5 ,x,y) for x,y in a) if n < 5.0]


def using_numpy(npa):
    n = (npa[:,0]**2+npa[:,1]**2)**-0.5
    where = n<5.0
    npa = npa[where]
    n = n[where]
    npa[:,0]=npa[:,0]*n
    npa[:,1]=npa[:,1]*n
    return( npa )

и результат ...

nlw@pathfinder:~$ python -mtimeit -s'import test' 'test.two_lcs(test.a)'
10000 loops, best of 3: 65.8 usec per loop
nlw@pathfinder:~$ python -mtimeit -s'import test' 'test.using_lc(test.a)'
10000 loops, best of 3: 65.6 usec per loop
nlw@pathfinder:~$ python -mtimeit -s'import test' 'test.using_forloop(test.a)'
10000 loops, best of 3: 64.1 usec per loop
nlw@pathfinder:~$ python -mtimeit -s'import test' 'test.using_iterator(test.a)'
10000 loops, best of 3: 59.6 usec per loop
nlw@pathfinder:~$ python -mtimeit -s'import test' 'test.using_numpy(test.npa)'
10000 loops, best of 3: 48.7 usec per loop
1 голос
/ 04 ноября 2010

Это предполагает, что использование forloop может быть самым быстрым способом. Обязательно проверьте результаты timeit на своем компьютере, так как эти результаты могут различаться в зависимости от ряда факторов (аппаратное обеспечение, ОС, версия Python, длина a и т. Д.).

a = [(1,1),(1,2),(2,2),(3,4)]

def two_lcs(a):
    an = [ ((x**2+y**2)**0.5, x,y) for x,y in a ]
    an = [ (x*n,y*n) for n,x,y in an if n < 0.4 ]
    return an

def using_forloop(a):
    result=[]
    for x,y in a:
        n=(x**2+y**2)**0.5
        if n<0.4:
            result.append((x*n,y*n))
    return result

def using_lc(a):    
    return [(x*n,y*n)
            for (n,x,y) in (( (x**2.+y**2.)**-0.5 ,x,y) for x,y in a) if n < 0.4]

дает следующие результаты:

% python -mtimeit -s'import test' 'test.using_forloop(test.a)'
100000 loops, best of 3: 3.29 usec per loop
% python -mtimeit -s'import test' 'test.two_lcs(test.a)'
100000 loops, best of 3: 4.52 usec per loop
% python -mtimeit -s'import test' 'test.using_lc(test.a)'
100000 loops, best of 3: 6.97 usec per loop
...