Qualcuno qui ha un codice utile che utilizza la funzione reduce () in Python? Esiste un codice diverso dai soliti + e * che vediamo negli esempi?
Fare riferimento a Fate of reduce () in Python 3000 di GvR
Qualcuno qui ha un codice utile che utilizza la funzione reduce () in Python? Esiste un codice diverso dai soliti + e * che vediamo negli esempi?
Fare riferimento a Fate of reduce () in Python 3000 di GvR
Risposte:
Gli altri usi che ho trovato oltre a + e * erano con e e o, ma ora abbiamo any
e all
per sostituire quei casi.
foldl
e foldr
vieni spesso in Scheme ...
Ecco alcuni usi carini:
Appiattisci un elenco
Obiettivo: trasformarsi [[1, 2, 3], [4, 5], [6, 7, 8]]
in [1, 2, 3, 4, 5, 6, 7, 8]
.
reduce(list.__add__, [[1, 2, 3], [4, 5], [6, 7, 8]], [])
Elenco delle cifre di un numero
Obiettivo: trasformarsi [1, 2, 3, 4, 5, 6, 7, 8]
in 12345678
.
Modo brutto e lento:
int("".join(map(str, [1,2,3,4,5,6,7,8])))
Bel reduce
modo:
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)
impiega ~ 0,09 secondi mentre 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)
impiega 0,36 secondi (circa 4 volte più lentamente). Fondamentalmente la moltiplicazione per 10 diventa costosa quando l'elenco diventa grande, mentre int to str e la concatenazione rimangono a buon mercato.
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)
richiede 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)
0,12 se la conversione delle cifre nel metodo str richiede 0,16 s.
reduce()
può essere utilizzato per trovare il minimo comune multiplo per 3 o più numeri :
#!/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)
Esempio:
>>> lcm(100, 23, 98)
112700
>>> lcm(*range(1, 20))
232792560
lcm
nella seconda riga?
Trova l'intersezione di N liste date:
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))
ritorna:
result = set([3, 4, 5])
L'utilizzo di reduce
ciò che ho trovato nel mio codice implicava la situazione in cui avevo una struttura di classi per l'espressione logica e avevo bisogno di convertire un elenco di questi oggetti espressione in una congiunzione delle espressioni. Avevo già una funzione make_and
per creare una congiunzione date due espressioni, quindi ho scritto reduce(make_and,l)
. (Sapevo che l'elenco non era vuoto, altrimenti sarebbe stato qualcosa di simile reduce(make_and,l,make_true)
.)
Questo è esattamente il motivo per cui (ad alcuni) programmatori funzionali piacciono reduce
(o piegano le funzioni, come tali funzioni sono tipicamente chiamate). Ci sono spesso già molte funzioni binari come +
, *
, min
, max
, la concatenazione e, nel mio caso, make_and
e make_or
. Avere a reduce
rende banale elevare queste operazioni a liste (o alberi o qualunque cosa tu abbia, per le funzioni di piegatura in generale).
Ovviamente, se sum
vengono utilizzate spesso alcune istanze (come ), non si desidera continuare a scrivere reduce
. Tuttavia, invece di definire la sum
con qualche ciclo for, è possibile altrettanto facilmente definirlo con reduce
.
La leggibilità, come menzionato da altri, è davvero un problema. Si potrebbe obiettare, tuttavia, che l'unica ragione per cui le persone trovano reduce
meno "chiara" è perché non è una funzione che molte persone conoscono e / o usano.
and
dell'operatore: L and reduce(make_and, L)
se in questo caso è appropriato restituire una lista vuota
Composizione delle funzioni : se disponi già di un elenco di funzioni che desideri applicare in successione, ad esempio:
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]
Quindi puoi applicarli tutti consecutivamente con:
>>> 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'
In questo caso, il concatenamento dei metodi potrebbe essere più leggibile. Ma a volte non è possibile e questo tipo di composizione può essere più leggibile e gestibile di un f1(f2(f3(f4(x))))
tipo di sintassi.
Potresti sostituire value = json_obj['a']['b']['c']['d']['e']
con:
value = reduce(dict.__getitem__, 'abcde', json_obj)
Se hai già il percorso a/b/c/..
come elenco. Ad esempio, modificare i valori in dict di dict annidati utilizzando elementi in un elenco .
@ Blair Conrad: potresti anche implementare il tuo glob / reduce usando sum, in questo modo:
files = sum([glob.glob(f) for f in args], [])
Questo è meno prolisso di uno dei tuoi due esempi, è perfettamente Pythonic ed è ancora solo una riga di codice.
Quindi, per rispondere alla domanda originale, personalmente cerco di evitare di utilizzare reduce perché non è mai realmente necessario e trovo che sia meno chiaro rispetto ad altri approcci. Tuttavia, alcune persone si abituano a ridurre e arrivano a preferirlo per elencare le comprensioni (specialmente i programmatori Haskell). Ma se non stai già pensando a un problema in termini di riduzione, probabilmente non devi preoccuparti di usarlo.
sum
e reduce
portano a un comportamento quadratico. Si può fare in tempo lineare: files = chain.from_iterable(imap(iglob, args))
. Anche se probabilmente non ha importanza in questo caso a causa del tempo impiegato da glob () per accedere a un disco.
reduce
può essere utilizzato per supportare le ricerche di attributi concatenate:
reduce(getattr, ('request', 'user', 'email'), self)
Ovviamente questo è equivalente a
self.request.user.email
ma è utile quando il codice deve accettare un elenco arbitrario di attributi.
(Gli attributi concatenati di lunghezza arbitraria sono comuni quando si ha a che fare con i modelli Django.)
reduce
è utile quando è necessario trovare l'unione o l'intersezione di una sequenza di set
oggetti simili.
>>> reduce(operator.or_, ({1}, {1, 2}, {1, 3})) # union
{1, 2, 3}
>>> reduce(operator.and_, ({1}, {1, 2}, {1, 3})) # intersection
{1}
(A parte i messaggi reali set
, un esempio di questi sono gli oggetti Q di Django .)
D'altra parte, se hai a che fare con bool
s, dovresti usare any
e all
:
>>> any((True, False, True))
True
Dopo aver greppato il mio codice, sembra che l'unica cosa per cui ho usato reduce sia il calcolo del fattoriale:
reduce(operator.mul, xrange(1, x+1) or (1,))
Sto scrivendo una funzione di composizione per un linguaggio, quindi costruisco la funzione composta usando reduce insieme al mio operatore di applicazione.
In poche parole, compose prende un elenco di funzioni per comporre in una singola funzione. Se ho un'operazione complessa che viene applicata in più fasi, voglio metterla insieme in questo modo:
complexop = compose(stage4, stage3, stage2, stage1)
In questo modo, posso quindi applicarlo a un'espressione come questa:
complexop(expression)
E voglio che sia equivalente a:
stage4(stage3(stage2(stage1(expression))))
Ora, per costruire i miei oggetti interni, voglio che dica:
Lambda([Symbol('x')], Apply(stage4, Apply(stage3, Apply(stage2, Apply(stage1, Symbol('x'))))))
(La classe Lambda crea una funzione definita dall'utente e Apply crea un'applicazione di funzione.)
Ora, riduci, sfortunatamente, le pieghe nel modo sbagliato, quindi ho finito per usare, più o meno:
reduce(lambda x,y: Apply(y, x), reversed(args + [Symbol('x')]))
Per capire cosa produce la riduzione, prova questi nel 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)
per generare tutte le possibili combinazioni di funzioni per il test delle prestazioni.
ridurre può essere utilizzato per ottenere l'elenco con il massimo n-esimo elemento
reduce(lambda x,y: x if x[2] > y[2] else y,[[1,2,3,4],[5,2,5,7],[1,6,0,2]])
restituirebbe [5, 2, 5, 7] poiché è l'elenco con un massimo di 3 ° elemento +
Reduce non è limitato alle operazioni scalari; può anche essere usato per ordinare le cose in secchi. (Questo è ciò per cui uso ridurre più spesso).
Immagina un caso in cui hai un elenco di oggetti e vuoi riorganizzarlo gerarchicamente in base alle proprietà memorizzate in modo piatto nell'oggetto. Nell'esempio seguente, produco un elenco di oggetti metadati relativi ad articoli in un giornale con codifica XML con la articles
funzione. articles
genera un elenco di elementi XML, quindi li mappa uno per uno, producendo oggetti che contengono alcune informazioni interessanti su di essi. Sul front-end, voglio consentire all'utente di sfogliare gli articoli per sezione / sottosezione / titolo. Quindi uso reduce
per prendere l'elenco degli articoli e restituire un singolo dizionario che riflette la gerarchia di sezione / sottosezione / articolo.
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, {})
Fornisco entrambe le funzioni qui perché penso che mostri come map e reduce possono completarsi a vicenda quando si tratta di oggetti. La stessa cosa si sarebbe potuta ottenere con un ciclo for, ... ma passare del tempo seriamente con un linguaggio funzionale ha avuto la tendenza a farmi pensare in termini di mappa e riduzione.
A proposito, se qualcuno ha un modo migliore per impostare le proprietà come sto facendo io extract
, in cui i genitori della proprietà che desideri impostare potrebbero non esistere ancora, fammelo sapere.
Non sono sicuro che questo sia ciò che stai cercando, ma puoi cercare il codice sorgente su Google .
Segui il link per una ricerca su "function: reduce () lang: python" su Google Code search
A prima vista i seguenti progetti utilizzano reduce()
ecc. ecc. ma poi questi non sono affatto sorprendenti poiché sono progetti enormi.
La funzionalità di reduce può essere eseguita usando la ricorsione delle funzioni che immagino Guido pensava fosse più esplicita.
Aggiornare:
Poiché la ricerca del codice di Google è stata interrotta il 15 gennaio 2012, oltre a tornare alle normali ricerche su Google, c'è qualcosa chiamato Raccolta di frammenti di codice che sembra promettente. Nelle risposte a questa domanda (chiusa) viene menzionata una serie di altre risorse. Sostituzione per Google Code Search? .
Aggiornamento 2 (29 maggio 2017):
Una buona fonte per gli esempi Python (nel codice open-source) è il motore di ricerca Nullege .
for
ciclo.
lang:python "reduce("
troverà definizioni che reduce
dipendono dallo stile di codifica del codice sorgente.
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']
# )},
# [])
#},
#[])
Ho usato reduce
per concatenare un elenco di vettori di ricerca PostgreSQL con l' ||
operatore in 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)
Ho una vecchia implementazione Python di pipegrep che utilizza reduce e il modulo glob per creare un elenco di file da elaborare:
files = []
files.extend(reduce(lambda x, y: x + y, map(glob.glob, args)))
L'ho trovato utile in quel momento, ma in realtà non è necessario, poiché qualcosa di simile è altrettanto buono e probabilmente più leggibile
files = []
for f in args:
files.extend(glob.glob(f))
files = [glob.glob(f) for f in args]
itertools
, usando la flatten()
ricetta da docs.python.org/library/itertools.html , e poi di scrivere: files = flatten(glob.glob(f) for f in args)
(E questa volta, ho testato il codice prima di pubblicarlo, e so che funziona correttamente.)
files = chain.from_iterable(imap(iglob, args))
dove chain
, imap
provengono dal itertools
modulo ed glob.iglob
è utile se un modello da args
può produrre file da diverse directory.
Supponiamo che ci siano alcuni dati statistici annuali memorizzati in un elenco di contatori. Vogliamo trovare i valori MIN / MAX in ogni mese nei diversi anni. Ad esempio, per gennaio sarebbe 10. E per febbraio sarebbe 15. Dobbiamo memorizzare i risultati in un nuovo contatore.
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
Ho oggetti che rappresentano una sorta di intervalli sovrapposti (esoni genomici) e ho ridefinito la loro intersezione usando __and__
:
class Exon:
def __init__(self):
...
def __and__(self,other):
...
length = self.length + other.length # (e.g.)
return self.__class__(...length,...)
Quindi, quando ne ho una raccolta (ad esempio, nello stesso gene), uso
intersection = reduce(lambda x,y: x&y, exons)
Ho appena trovato un uso utile di reduce
: dividere la stringa senza rimuovere il delimitatore . Il codice proviene interamente dal blog Programatically Speaking. Ecco il codice:
reduce(lambda acc, elem: acc[:-1] + [acc[-1] + elem] if elem == "\n" else acc + [elem], re.split("(\n)", "a\nb\nc\n"), [])
Ecco il risultato:
['a\n', 'b\n', 'c\n', '']
Nota che gestisce casi limite che la risposta popolare in SO non fa. Per una spiegazione più approfondita, ti reindirizzo al post del blog originale.
Utilizzando reduce () per scoprire se un elenco di date è consecutivo:
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
consente allo stesso codice di funzionare sia su Python 2 che su 3.