Código útil que usa reduzir ()? [fechadas]


123

Alguém aqui tem algum código útil que usa a função reduzir () em python? Existe algum código que não seja o habitual + e * que vemos nos exemplos?

Consulte Destino da redução () no Python 3000 por GvR


1
from functools import reducepermite que o mesmo código para trabalhar em ambos Python 2 e 3.
jfs

Respostas:


66

Os outros usos que encontrei além de + e * foram com e e ou, mas agora temos anye allsubstituímos esses casos.

foldle foldraparecem muito no Scheme ...

Aqui estão alguns usos fofos:

Achatar uma lista

Objetivo: transformar [[1, 2, 3], [4, 5], [6, 7, 8]]em [1, 2, 3, 4, 5, 6, 7, 8].

reduce(list.__add__, [[1, 2, 3], [4, 5], [6, 7, 8]], [])

Lista de dígitos para um número

Objetivo: transformar [1, 2, 3, 4, 5, 6, 7, 8]em 12345678.

Caminho feio e lento:

int("".join(map(str, [1,2,3,4,5,6,7,8])))

Maneira bonita reduce:

reduce(lambda a,d: 10*a+d, [1,2,3,4,5,6,7,8], 0)

23
Para achatamento uma lista, eu prefiro lista (itertools.chain (* nested_list))
Roberto Bonvallet

13
soma ([[1, 2, 3], [4, 5], [6, 7, 8]], [])
Gordon Wrigley

3
Também é útil para operações bit a bit. E se você quiser usar o bit a bit ou vários números, por exemplo, se precisar converter sinalizadores de uma lista em uma máscara de bit?
Antimony

6
Fazendo alguns benchmarks, a maneira 'feia' é mais rápida para grandes listas. timeit.repeat('int("".join(map(str, digit_list)))', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)leva ~ 0,09 segundos enquanto timeit.repeat('reduce(lambda a,d: 10*a+d, digit_list)', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)leva 0,36 segundos (cerca de 4 vezes mais lento). Basicamente, a multiplicação por 10 se torna cara quando a lista fica grande, enquanto int str e concatenação permanecem baratas.
dr jimbob

3
Concedido, sim, para listas pequenas (tamanho 10), o método de redução é 1,3 vezes mais rápido. No entanto, mesmo nesse caso, evitar reduzir e executar um loop simples é ainda mais rápido, timeit.repeat('convert_digit_list_to_int(digit_list)', setup = 'digit_list = [d%10 for d in xrange(1,10)]\ndef convert_digit_list_to_int(digits):\n i = 0\n for d in digits:\n i = 10*i + d\n return i', number=100000)leva 0,06 s, timeit.repeat('reduce(lambda a,d: 10*a+d, digit_list)', setup = 'digit_list = list(d%10 for d in xrange(1,10))', number=100000)leva 0,12 s e a conversão de dígitos para o método str leva 0,16 s.
dr jimbob

51

reduce()pode ser usado para encontrar o mínimo múltiplo comum para 3 ou mais números :

#!/usr/bin/env python
from fractions import gcd
from functools import reduce

def lcm(*args):
    return reduce(lambda a,b: a * b // gcd(a, b), args)

Exemplo:

>>> lcm(100, 23, 98)
112700
>>> lcm(*range(1, 20))
232792560

1
O que há lcmna segunda linha?
beardc 24/05

1
@BirdJaguarIV: siga o link na resposta. lcm()retorna múltiplo menos comum de dois números.
JFS

39

reduce()pode ser usado para resolver nomes pontilhados (onde eval()é inseguro demais para usar):

>>> import __main__
>>> reduce(getattr, "os.path.abspath".split('.'), __main__)
<function abspath at 0x009AB530>


12

Eu acho que reduzir é um comando bobo. Conseqüentemente:

reduce(lambda hold,next:hold+chr(((ord(next.upper())-65)+13)%26+65),'znlorabggbbhfrshy','')

1
Eu também gosto da ironia aqui
Roman

11

O uso reduceque encontrei no meu código envolveu a situação em que eu tinha alguma estrutura de classe para expressão lógica e precisava converter uma lista desses objetos de expressão em uma conjunção das expressões. Eu já tinha a função make_andde criar uma conjunção com duas expressões, então escrevi reduce(make_and,l). (Eu sabia que a lista não estava vazia; caso contrário, teria sido algo parecido reduce(make_and,l,make_true).)

Essa é exatamente a razão pela qual (alguns) programadores funcionais gostam reduce(ou dobram funções, como normalmente são chamadas). Muitas vezes há já muitas funções binárias como +, *, min, max, concatenação e, no meu caso, make_ande make_or. Tendo umareduce torna trivial elevar essas operações para listas (ou árvores ou o que você tiver, para funções de dobra em geral).

Obviamente, se certas instanciações (como sum) são frequentemente usadas, você não deseja continuar escrevendo reduce. No entanto, em vez de definir o sumloop for some, você pode defini-lo com a mesma facilidade reduce.

Legibilidade, como mencionado por outros, é realmente um problema. Você poderia argumentar, no entanto, que a única razão pela qual as pessoas acham reducemenos "claras" é porque não é uma função que muitas pessoas conheçam e / ou usem.


para se proteger da lista vazia, você pode explorar o comportamento de curto-circuito do andoperador: L and reduce(make_and, L)se retornar a lista vazia for apropriado neste caso
jfs

9

Composição da função : se você já possui uma lista de funções que deseja aplicar em sucessão, como:

color = lambda x: x.replace('brown', 'blue')
speed = lambda x: x.replace('quick', 'slow')
work = lambda x: x.replace('lazy', 'industrious')
fs = [str.lower, color, speed, work, str.title]

Em seguida, você pode aplicá-los todos consecutivamente com:

>>> call = lambda s, func: func(s)
>>> s = "The Quick Brown Fox Jumps Over the Lazy Dog"
>>> reduce(call, fs, s)
'The Slow Blue Fox Jumps Over The Industrious Dog'

Nesse caso, o encadeamento de métodos pode ser mais legível. Mas às vezes isso não é possível, e esse tipo de composição pode ser mais legível e sustentável do que um f1(f2(f3(f4(x))))tipo de sintaxe.


1
Uma vantagem é que você pode alterar a lista de funções a serem aplicadas no código.
hakanc


7

@Blair Conrad: Você também pode implementar seu glob / reduzir usando sum, assim:

files = sum([glob.glob(f) for f in args], [])

Isso é menos detalhado do que qualquer um dos dois exemplos, é perfeitamente pitônico e ainda é apenas uma linha de código.

Então, para responder à pergunta original, eu pessoalmente tento evitar o uso de reduzir porque isso nunca é realmente necessário e acho que é menos claro do que outras abordagens. No entanto, algumas pessoas se acostumam a reduzir e preferem listar compreensões (especialmente programadores Haskell). Mas se você ainda não está pensando em um problema em termos de redução, provavelmente não precisa se preocupar em usá-lo.


2
Ambos sume reducelevam ao comportamento quadrático. Isso pode ser feito em tempo linear: files = chain.from_iterable(imap(iglob, args)). Embora provavelmente não importe nesse caso, devido ao tempo que leva para a glob () acessar um disco.
JFS

6

reduce pode ser usado para suportar pesquisas de atributos encadeados:

reduce(getattr, ('request', 'user', 'email'), self)

Claro, isso é equivalente a

self.request.user.email

mas é útil quando seu código precisa aceitar uma lista arbitrária de atributos.

(Atributos encadeados de comprimento arbitrário são comuns ao lidar com modelos do Django.)


4

reduceé útil quando você precisa encontrar a união ou interseção de uma sequência de setobjetos semelhantes.

>>> reduce(operator.or_, ({1}, {1, 2}, {1, 3}))  # union
{1, 2, 3}
>>> reduce(operator.and_, ({1}, {1, 2}, {1, 3}))  # intersection
{1}

(Além dos sets reais , um exemplo desses são os objetos Q do Django .)

Por outro lado, se você está lidando com bools, deve usar anye all:

>>> any((True, False, True))
True


3

Como estou escrevendo uma função de composição para um idioma, construo a função composta usando reduzir junto com meu operador de aplicação.

Em poucas palavras, escrever compõe uma lista de funções para compor em uma única função. Se eu tiver uma operação complexa aplicada em etapas, quero reunir tudo da seguinte maneira:

complexop = compose(stage4, stage3, stage2, stage1)

Dessa forma, posso aplicá-lo a uma expressão assim:

complexop(expression)

E eu quero que seja equivalente a:

stage4(stage3(stage2(stage1(expression))))

Agora, para construir meus objetos internos, quero que ele diga:

Lambda([Symbol('x')], Apply(stage4, Apply(stage3, Apply(stage2, Apply(stage1, Symbol('x'))))))

(A classe Lambda cria uma função definida pelo usuário e o Apply cria um aplicativo de função.)

Agora, infelizmente, reduza dobras da maneira errada, então acabei usando, aproximadamente:

reduce(lambda x,y: Apply(y, x), reversed(args + [Symbol('x')]))

Para descobrir o que reduz produz, tente estes no REPL:

reduce(lambda x, y: (x, y), range(1, 11))
reduce(lambda x, y: (y, x), reversed(range(1, 11)))

Eu usei compose = lambda *func: lambda arg: reduce(lambda x, f: f(x), reversed(funcs), arg)para gerar todas as combinações possíveis de funções para teste de desempenho.
JFS

3

reduzir pode ser usado para obter a lista com o enésimo elemento máximo

reduce(lambda x,y: x if x[2] > y[2] else y,[[1,2,3,4],[5,2,5,7],[1,6,0,2]])

retornaria [5, 2, 5, 7], pois é a lista com o máximo do terceiro elemento +


max (lst, key = lambda x: x [2])
aoeu256 16/10

3

Reduzir não se limita a operações escalares; também pode ser usado para classificar as coisas em baldes. (Isto é o que eu uso reduzir com mais frequência).

Imagine um caso em que você tenha uma lista de objetos e deseje reorganizá-la hierarquicamente com base nas propriedades armazenadas de maneira simples no objeto. No exemplo a seguir, produzo uma lista de objetos de metadados relacionados a artigos em um jornal codificado em XML com a articlesfunção articlesgera uma lista de elementos XML e os mapeia um a um, produzindo objetos que contêm informações interessantes sobre eles. No front-end, quero permitir que o usuário navegue nos artigos por seção / subseção / título. Então, eu uso reducea lista de artigos e retorno um único dicionário que reflete a hierarquia da seção / subseção / artigo.

from lxml import etree
from Reader import Reader

class IssueReader(Reader):
    def articles(self):
        arts = self.q('//div3')  # inherited ... runs an xpath query against the issue
        subsection = etree.XPath('./ancestor::div2/@type')
        section = etree.XPath('./ancestor::div1/@type')
        header_text = etree.XPath('./head//text()')
        return map(lambda art: {
            'text_id': self.id,
            'path': self.getpath(art)[0],
            'subsection': (subsection(art)[0] or '[none]'),
            'section': (section(art)[0] or '[none]'),
            'headline': (''.join(header_text(art)) or '[none]')
        }, arts)

    def by_section(self):
        arts = self.articles()

        def extract(acc, art):  # acc for accumulator
            section = acc.get(art['section'], False)
            if section:
                subsection = acc.get(art['subsection'], False)
                if subsection:
                    subsection.append(art)
                else:
                    section[art['subsection']] = [art]
            else:
                acc[art['section']] = {art['subsection']: [art]}
            return acc

        return reduce(extract, arts, {})

Dou as duas funções aqui porque acho que mostra como mapear e reduzir podem se complementar muito bem ao lidar com objetos. O mesmo poderia ter sido realizado com um loop for ... mas passar algum tempo sério com uma linguagem funcional tende a me fazer pensar em termos de mapa e redução.

A propósito, se alguém tiver uma maneira melhor de definir propriedades como eu extract, em que os pais da propriedade que você deseja definir ainda não existem, informe-me.


3

Não tenho certeza se é isso que você procura, mas você pode pesquisar o código-fonte no Google .

Siga o link para uma pesquisa sobre 'function: reduzir () lang: python' na pesquisa de código do Google

À primeira vista, os seguintes projetos usam reduce()

  • MoinMoin
  • Zope
  • Numérico
  • ScientificPython

etc. etc., mas não são de surpreender, pois são grandes projetos.

A funcionalidade de redução pode ser feita usando a recursão da função, que eu acho que Guido achou mais explícita.

Atualizar:

Como a Pesquisa de código do Google foi descontinuada em 15 de janeiro de 2012, além de reverter para pesquisas regulares do Google, existe algo chamado Coleção de trechos de código que parece promissor. Vários outros recursos são mencionados nas respostas a esta pergunta (fechada) Substituição da Pesquisa de código do Google? .

Atualização 2 (29 de maio de 2017):

Uma boa fonte para exemplos de Python (em código de código aberto) é o mecanismo de pesquisa Nullege .


1
"A funcionalidade de redução pode ser feita usando a recursão da função" ... Ou um forloop.
10139 Jason Orendorff

2
Além disso, a pesquisa de reduzir () gera projetos que definem funções de redução em seu código. Você deve procurar lang: python "reduzir (" para encontrar usos reais da função built-in.
Seun Osewa

@ Seun Osewa: Mesmo a busca lang:python "reduce("encontrará definições de reducedependendo do estilo de codificação do código fonte.
27511 martineau

2
import os

files = [
    # full filenames
    "var/log/apache/errors.log",
    "home/kane/images/avatars/crusader.png",
    "home/jane/documents/diary.txt",
    "home/kane/images/selfie.jpg",
    "var/log/abc.txt",
    "home/kane/.vimrc",
    "home/kane/images/avatars/paladin.png",
]

# unfolding of plain filiname list to file-tree
fs_tree = ({}, # dict of folders
           []) # list of files
for full_name in files:
    path, fn = os.path.split(full_name)
    reduce(
        # this fucction walks deep into path
        # and creates placeholders for subfolders
        lambda d, k: d[0].setdefault(k,         # walk deep
                                     ({}, [])), # or create subfolder storage
        path.split(os.path.sep),
        fs_tree
    )[1].append(fn)

print fs_tree
#({'home': (
#    {'jane': (
#        {'documents': (
#           {},
#           ['diary.txt']
#        )},
#        []
#    ),
#    'kane': (
#       {'images': (
#          {'avatars': (
#             {},
#             ['crusader.png',
#             'paladin.png']
#          )},
#          ['selfie.jpg']
#       )},
#       ['.vimrc']
#    )},
#    []
#  ),
#  'var': (
#     {'log': (
#         {'apache': (
#            {},
#            ['errors.log']
#         )},
#         ['abc.txt']
#     )},
#     [])
#},
#[])

1
Você poderia adicionar uma pequena explicação sobre o que está acontecendo aqui? Caso contrário, a utilidade não é realmente óbvia.
Zoran Pavlovic

2
def dump(fname,iterable):
  with open(fname,'w') as f:
    reduce(lambda x, y: f.write(unicode(y,'utf-8')), iterable)

2

Eu costumava reduce concatenar uma lista de vetores de pesquisa do PostgreSQL com o ||operador em sqlalchemy-searchable:

vectors = (self.column_vector(getattr(self.table.c, column_name))
           for column_name in self.indexed_columns)
concatenated = reduce(lambda x, y: x.op('||')(y), vectors)
compiled = concatenated.compile(self.conn)

1

Eu tenho uma implementação Python antiga do pipegrep que usa o reduzir e o módulo glob para criar uma lista de arquivos para processar:

files = []
files.extend(reduce(lambda x, y: x + y, map(glob.glob, args)))

Achei útil na época, mas não é realmente necessário, pois algo semelhante é igualmente bom e provavelmente mais legível

files = []
for f in args:
    files.extend(glob.glob(f))

Que tal uma compreensão de lista? Este parece ser um aplicativo perfeito para ele: files = [glob.glob(f) for f in args]
steveha

Na verdade, @steveha, seu exemplo resultará em uma lista de listas de globs expandidos, em vez de uma lista simples de todos os itens que correspondem aos globs, mas você pode usar uma lista de compreensão + soma, como @ [Eli Courtwright] (# 16198 ) ressalta.
Blair Conrad

1
Ok, você está correto, desculpe por isso. Ainda não gosto muito da combinação de extensão / redução / lambda / mapa! Eu recomendaria importar itertools, usando a flatten()receita de docs.python.org/library/itertools.html e depois escrevendo: files = flatten(glob.glob(f) for f in args) (E desta vez, testei o código antes de publicá-lo e sei que isso funciona corretamente.)
steveha

files = chain.from_iterable(imap(iglob, args))where chain, imapsão do itertoolsmódulo e glob.iglobé útil se um padrão de argspode gerar arquivos de vários diretórios.
JFS

1

Digamos que existem alguns dados estatísticos anuais armazenados em uma lista de contadores. Queremos encontrar os valores MIN / MAX em cada mês nos diferentes anos. Por exemplo, em janeiro, seriam 10. E, em fevereiro, seriam 15. Precisamos armazenar os resultados em um novo contador.

from collections import Counter

stat2011 = Counter({"January": 12, "February": 20, "March": 50, "April": 70, "May": 15,
           "June": 35, "July": 30, "August": 15, "September": 20, "October": 60,
           "November": 13, "December": 50})

stat2012 = Counter({"January": 36, "February": 15, "March": 50, "April": 10, "May": 90,
           "June": 25, "July": 35, "August": 15, "September": 20, "October": 30,
           "November": 10, "December": 25})

stat2013 = Counter({"January": 10, "February": 60, "March": 90, "April": 10, "May": 80,
           "June": 50, "July": 30, "August": 15, "September": 20, "October": 75,
           "November": 60, "December": 15})

stat_list = [stat2011, stat2012, stat2013]

print reduce(lambda x, y: x & y, stat_list)     # MIN
print reduce(lambda x, y: x | y, stat_list)     # MAX

1

Eu tenho objetos representando algum tipo de intervalo sobreposto (exons genômicos) e redefinimos sua interseção usando __and__:

class Exon:
    def __init__(self):
        ...
    def __and__(self,other):
        ...
        length = self.length + other.length  # (e.g.)
        return self.__class__(...length,...)

Então, quando eu tenho uma coleção deles (por exemplo, no mesmo gene), eu uso

intersection = reduce(lambda x,y: x&y, exons)

1

Acabei de encontrar um uso útil de reduce: split string sem remover o delimitador . O código é inteiramente do blog Programatically Speaking. Aqui está o código:

reduce(lambda acc, elem: acc[:-1] + [acc[-1] + elem] if elem == "\n" else acc + [elem], re.split("(\n)", "a\nb\nc\n"), [])

Aqui está o resultado:

['a\n', 'b\n', 'c\n', '']

Observe que ele lida com casos extremos que a resposta popular no SO não. Para uma explicação mais aprofundada, estou redirecionando você para a postagem original do blog.


0

Usando o reduza () para descobrir se uma lista de datas é consecutiva:

from datetime import date, timedelta


def checked(d1, d2):
    """
    We assume the date list is sorted.
    If d2 & d1 are different by 1, everything up to d2 is consecutive, so d2
    can advance to the next reduction.
    If d2 & d1 are not different by 1, returning d1 - 1 for the next reduction
    will guarantee the result produced by reduce() to be something other than
    the last date in the sorted date list.

    Definition 1: 1/1/14, 1/2/14, 1/2/14, 1/3/14 is consider consecutive
    Definition 2: 1/1/14, 1/2/14, 1/2/14, 1/3/14 is consider not consecutive

    """
    #if (d2 - d1).days == 1 or (d2 - d1).days == 0:  # for Definition 1
    if (d2 - d1).days == 1:                          # for Definition 2
        return d2
    else:
        return d1 + timedelta(days=-1)

# datelist = [date(2014, 1, 1), date(2014, 1, 3),
#             date(2013, 12, 31), date(2013, 12, 30)]

# datelist = [date(2014, 2, 19), date(2014, 2, 19), date(2014, 2, 20),
#             date(2014, 2, 21), date(2014, 2, 22)]

datelist = [date(2014, 2, 19), date(2014, 2, 21),
            date(2014, 2, 22), date(2014, 2, 20)]

datelist.sort()

if datelist[-1] == reduce(checked, datelist):
    print "dates are consecutive"
else:
    print "dates are not consecutive"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.