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
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
Respostas:
Os outros usos que encontrei além de + e * foram com e e ou, mas agora temos any
e all
substituímos esses casos.
foldl
e foldr
aparecem 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)
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.
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.
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
lcm
na segunda linha?
Encontre a interseção de N listas dadas:
input_list = [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7]]
result = reduce(set.intersection, map(set, input_list))
retorna:
result = set([3, 4, 5])
O uso reduce
que 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_and
de 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_and
e 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 sum
loop 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 reduce
menos "claras" é porque não é uma função que muitas pessoas conheçam e / ou usem.
and
operador: L and reduce(make_and, L)
se retornar a lista vazia for apropriado neste caso
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.
Você pode substituir value = json_obj['a']['b']['c']['d']['e']
por:
value = reduce(dict.__getitem__, 'abcde', json_obj)
Se você já possui o caminho a/b/c/..
como uma lista. Por exemplo, altere valores no dict de dested aninhados usando itens em uma lista .
@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.
sum
e reduce
levam 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.
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.)
reduce
é útil quando você precisa encontrar a união ou interseção de uma sequência de set
objetos 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 set
s reais , um exemplo desses são os objetos Q do Django .)
Por outro lado, se você está lidando com bool
s, deve usar any
e all
:
>>> any((True, False, True))
True
Após grepping meu código, parece que a única coisa que usei reduzi é calcular o fatorial:
reduce(operator.mul, xrange(1, x+1) or (1,))
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)))
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.
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 +
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 articles
função articles
gera 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 reduce
a 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.
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()
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 .
for
loop.
lang:python "reduce("
encontrará definições de reduce
dependendo do estilo de codificação do código fonte.
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']
# )},
# [])
#},
#[])
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)
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))
files = [glob.glob(f) for f in args]
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.)
files = chain.from_iterable(imap(iglob, args))
where chain
, imap
são do itertools
módulo e glob.iglob
é útil se um padrão de args
pode gerar arquivos de vários diretórios.
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
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)
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.
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"
from functools import reduce
permite que o mesmo código para trabalhar em ambos Python 2 e 3.