Codice utile che usa reduce ()? [chiuso]


123

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


1
from functools import reduceconsente allo stesso codice di funzionare sia su Python 2 che su 3.
jfs

Risposte:


66

Gli altri usi che ho trovato oltre a + e * erano con e e o, ma ora abbiamo anye allper sostituire quei casi.

foldle foldrvieni 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 reducemodo:

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

23
Per appiattire una lista, preferisco list (itertools.chain (* nested_list))
Roberto Bonvallet

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

3
È anche utile per le operazioni bit per bit. Cosa succede se si desidera prendere il bit per bit o un gruppo di numeri, ad esempio se è necessario convertire i flag da un elenco a una maschera di bit?
Antimonio

6
Facendo alcuni benchmark, il modo "brutto" è più veloce per elenchi di grandi dimensioni. 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.
dr jimbob

3
Certo, sì per elenchi piccoli (dimensione 10), il metodo di riduzione è 1,3 volte più veloce. Tuttavia, anche in questo caso, evitare di ridurre ed eseguire un ciclo semplice è ancora più veloce 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.
dr jimbob

51

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

1
Cosa c'è lcmnella seconda riga?
beardc

1
@BirdJaguarIV: segui il link nella risposta. lcm()restituisce il minimo comune multiplo di due numeri.
jfs

39

reduce()potrebbe essere usato per risolvere i nomi puntati (dove eval()è troppo pericoloso da usare):

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


12

Penso che ridurre sia un comando stupido. Quindi:

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

1
Mi piace anche l'ironia qui
Roman

11

L'utilizzo di reduceciò 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_andper 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_ande make_or. Avere a reducerende banale elevare queste operazioni a liste (o alberi o qualunque cosa tu abbia, per le funzioni di piegatura in generale).

Ovviamente, se sumvengono utilizzate spesso alcune istanze (come ), non si desidera continuare a scrivere reduce. Tuttavia, invece di definire la sumcon 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 reducemeno "chiara" è perché non è una funzione che molte persone conoscono e / o usano.


per evitare una lista vuota si potrebbe sfruttare il comportamento di cortocircuito anddell'operatore: L and reduce(make_and, L)se in questo caso è appropriato restituire una lista vuota
jfs

9

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.


1
Un vantaggio è che puoi modificare l'elenco delle funzioni da applicare nel codice.
hakanc


7

@ 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.


2
Entrambi sume reduceportano 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.
jfs

6

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


4

reduceè utile quando è necessario trovare l'unione o l'intersezione di una sequenza di setoggetti 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 bools, dovresti usare anye all:

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


3

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

Ho usato 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.
jfs

3

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 +


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

3

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 articlesfunzione. articlesgenera 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 reduceper 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.


3

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()

  • MoinMoin
  • Zope
  • Numerico
  • ScientificPython

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 .


1
"La funzionalità di reduce può essere eseguita utilizzando la funzione ricorsione" ... O un forciclo.
Jason Orendorff

2
Inoltre, la ricerca di reduce () produce progetti che definiscono le funzioni di riduzione all'interno del loro codice. Dovresti cercare lang: python "reduce (" to per trovare gli utilizzi effettivi della funzione incorporata.
Seun Osewa

@Seun Osewa: Anche la ricerca di lang:python "reduce("troverà definizioni che reducedipendono dallo stile di codifica del codice sorgente.
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
Potresti forse aggiungere una piccola spiegazione su cosa sta succedendo qui? Altrimenti, l'utilità non è affatto ovvia.
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

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)

1

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

Che ne dici di una lista di comprensione? Questa sembra un'applicazione perfetta per questo: files = [glob.glob(f) for f in args]
steveha

In realtà, @steveha, il tuo esempio risulterà in un elenco di elenchi di glob espansi, piuttosto che un elenco semplice di tutti gli elementi che corrispondono ai glob, ma potresti usare una lista di comprensione + somma, come @ [Eli Courtwright] (# 16198 ) sottolinea.
Blair Conrad

1
Va bene, hai ragione, mi dispiace. Non mi piace ancora molto la combinazione di estensione / riduzione / lambda / mappa! Consiglierei di importare 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.)
steveha

files = chain.from_iterable(imap(iglob, args))dove chain, imapprovengono dal itertoolsmodulo ed glob.iglobè utile se un modello da argspuò produrre file da diverse directory.
jfs

1

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

1

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)

1

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.


0

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.