Código útil que utiliza reduce ()? [cerrado]


123

¿Alguien aquí tiene algún código útil que use la función reduce () en python? ¿Hay algún código que no sea el habitual + y * que vemos en los ejemplos?

Consulte el destino de reduce () en Python 3000 por GvR


1
from functools import reducepermite que el mismo código funcione en Python 2 y 3.
jfs

Respuestas:


66

Los otros usos que he encontrado para él además de + y * fueron con y y o, pero ahora tenemos anyy allpara reemplazar esos casos.

foldly foldraparecen mucho en Scheme ...

Aquí hay algunos usos lindos:

Acoplar una lista

Objetivo: convertirse [[1, 2, 3], [4, 5], [6, 7, 8]]en [1, 2, 3, 4, 5, 6, 7, 8].

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

Lista de dígitos a un número

Objetivo: convertirse [1, 2, 3, 4, 5, 6, 7, 8]en 12345678.

Feo, camino lento:

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

Bonita reducemanera:

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

23
Para aplanar una lista, prefiero list (itertools.chain (* nested_list))
Roberto Bonvallet

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

3
También es útil para operaciones bit a bit. ¿Qué sucede si desea tomar el bit a bit o de un grupo de números, por ejemplo, si necesita convertir banderas de una lista a una máscara de bits?
Antimonio

66
Haciendo algunos puntos de referencia, la forma 'fea' es más rápida para listas grandes. timeit.repeat('int("".join(map(str, digit_list)))', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)toma ~ 0.09 segundos mientras que 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)toma 0.36 segundos (aproximadamente 4 veces más lento). Básicamente, la multiplicación por 10 se vuelve costosa cuando la lista se hace grande, mientras que int to str y la concatenación se mantienen baratas.
dr jimbob

3
De acuerdo, sí para listas pequeñas (tamaño 10), entonces el método de reducción es 1.3 veces más rápido. Sin embargo, incluso en este caso, evitar reducir y hacer un bucle simple es aún más 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)toma 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)toma 0.12 sy el conversión de dígitos al método str toma 0.16 s.
dr jimbob

51

reduce()se puede usar para encontrar el mínimo común múltiplo de 3 o más 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)

Ejemplo:

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

1
¿Qué hay lcmen la segunda línea?
beardc

1
@BirdJaguarIV: sigue el enlace en la respuesta. lcm()devuelve el mínimo común múltiplo de dos números.
jfs

39

reduce()podría usarse para resolver nombres punteados (donde no eval()es seguro usarlo):

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


12

Creo que reducir es un comando tonto. Por lo tanto:

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

1
También me gusta la ironía aquí
Roman

11

El uso de reduceeso que encontré en mi código involucró la situación en la que tenía cierta estructura de clase para la expresión lógica y necesitaba convertir una lista de estos objetos de expresión en una conjunción de las expresiones. Ya tenía una función make_andpara crear una conjunción dada dos expresiones, así que escribí reduce(make_and,l). (Sabía que la lista no estaba vacía; de lo contrario, habría sido algo así reduce(make_and,l,make_true)).

Esta es exactamente la razón por la que (a algunos) programadores funcionales les gusta reduce(o pliegan funciones, como se suele llamar a tales funciones). A menudo existen ya muchas funciones binarias como +, *, min, max, concatenación y, en mi caso, make_andy make_or. Tener un reducehace que sea trivial levantar estas operaciones a listas (o árboles o lo que sea que tenga, para funciones de plegado en general).

Por supuesto, si a summenudo se usan ciertas instancias (como ), entonces no querrá seguir escribiendo reduce. Sin embargo, en lugar de definirlo sumcon algún ciclo for, puede definirlo con la misma facilidad reduce.

La legibilidad, como lo mencionaron otros, es de hecho un problema. Sin embargo, podría argumentar que la única razón por la cual las personas encuentran reducemenos "claro" es porque no es una función que mucha gente conoce y / o usa.


para protegerse contra la lista vacía, podría explotar el comportamiento de cortocircuito del andoperador: L and reduce(make_and, L)si devolver la lista vacía es apropiado en este caso
jfs

9

Composición de funciones : si ya tiene una lista de funciones que le gustaría aplicar en sucesión, 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]

Luego puede aplicarlos todos 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'

En este caso, el encadenamiento de métodos puede ser más legible. Pero a veces no es posible, y este tipo de composición puede ser más fácil de leer y mantener que un f1(f2(f3(f4(x))))tipo de sintaxis.


1
Una ventaja es que puede cambiar la lista de funciones para aplicar en el código.
hakanc


7

@Blair Conrad: También podría implementar su glob / reduce usando la suma, así:

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

Esto es menos detallado que cualquiera de sus dos ejemplos, es perfectamente pitónico y sigue siendo solo una línea de código.

Entonces, para responder la pregunta original, personalmente trato de evitar el uso de reducir porque nunca es realmente necesario y encuentro que es menos claro que otros enfoques. Sin embargo, algunas personas se acostumbran a reducir y prefieren enumerar las comprensiones (especialmente los programadores de Haskell). Pero si aún no está pensando en un problema en términos de reducción, probablemente no necesite preocuparse por usarlo.


2
Ambos sumy reduceconducen a un comportamiento cuadrático. Se puede hacer en un tiempo lineal: files = chain.from_iterable(imap(iglob, args)). Aunque probablemente no importe en este caso debido al tiempo que le toma a glob () acceder a un disco.
jfs

6

reduce se puede usar para admitir búsquedas de atributos encadenados:

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

Por supuesto, esto es equivalente a

self.request.user.email

pero es útil cuando su código necesita aceptar una lista arbitraria de atributos.

(Los atributos encadenados de longitud arbitraria son comunes cuando se trata de modelos Django).


4

reducees útil cuando necesita encontrar la unión o intersección de una secuencia de setobjetos similares.

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

(Además de los sets reales , un ejemplo de estos son los objetos Q de Django ).

Por otro lado, si está tratando con bools, debe usar anyy all:

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


3

Estoy escribiendo una función de composición para un idioma, así que construyo la función compuesta usando reducir junto con mi operador de aplicación.

En pocas palabras, componer toma una lista de funciones para componer en una sola función. Si tengo una operación compleja que se aplica por etapas, quiero poner todo junto de esta manera:

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

De esta manera, puedo aplicarlo a una expresión así:

complexop(expression)

Y quiero que sea equivalente a:

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

Ahora, para construir mis objetos internos, quiero que diga:

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

(La clase Lambda crea una función definida por el usuario y Apply crea una aplicación de función).

Ahora, reducir, desafortunadamente, se dobla de la manera incorrecta, así que terminé usando, aproximadamente:

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

Para averiguar qué reducción produce, pruebe estos en REPL:

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

He usado compose = lambda *func: lambda arg: reduce(lambda x, f: f(x), reversed(funcs), arg)para generar todas las posibles combinaciones de las funciones para las pruebas de rendimiento.
jfs

3

reducir se puede utilizar para obtener la lista con el 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]])

devolvería [5, 2, 5, 7] ya que es la lista con un tercer elemento máximo +


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

3

Reducir no se limita a operaciones escalares; También se puede utilizar para clasificar cosas en cubos. (Esto es lo que uso reducir para la mayoría de las veces).

Imagine un caso en el que tiene una lista de objetos y desea reorganizarla jerárquicamente en función de las propiedades almacenadas de manera plana en el objeto. En el siguiente ejemplo, produzco una lista de objetos de metadatos relacionados con artículos en un periódico codificado en XML con la articlesfunción. articlesgenera una lista de elementos XML y luego los mapea uno por uno, produciendo objetos que contienen información interesante sobre ellos. En la parte frontal, voy a querer que el usuario explore los artículos por sección / subsección / título. Así que solía reducetomar la lista de artículos y devolver un solo diccionario que refleja la jerarquía de sección / subsección / artículo.

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, {})

Doy ambas funciones aquí porque creo que muestra cómo map y reduce pueden complementarse muy bien cuando se trata de objetos. Lo mismo podría haberse logrado con un bucle for ... pero pasar un tiempo serio con un lenguaje funcional ha tendido a hacerme pensar en términos de mapa y reducir.

Por cierto, si alguien tiene una mejor manera de establecer propiedades como lo estoy haciendo yo extract, donde los padres de la propiedad que desea establecer aún no existen, por favor hágamelo saber.


3

No estoy seguro de si esto es lo que busca, pero puede buscar el código fuente en Google .

Siga el enlace para buscar 'function: reduce () lang: python' en la búsqueda de Google Code

A primera vista, los siguientes proyectos utilizan reduce()

  • MoinMoin
  • Zope
  • Numérico
  • ScientificPython

etc. etc., pero estos no son sorprendentes ya que son grandes proyectos.

La funcionalidad de reducir se puede hacer usando la función de recursión, que supongo que Guido pensó que era más explícita.

Actualizar:

Dado que la búsqueda de código de Google se suspendió el 15 de enero de 2012, además de volver a las búsquedas regulares de Google, hay algo llamado Colección de fragmentos de código que parece prometedor. Varios otros recursos se mencionan en las respuestas a esta pregunta (cerrada) ¿ Reemplazo para Google Code Search? .

Actualización 2 (29 de mayo de 2017):

Una buena fuente para ejemplos de Python (en código de código abierto) es el motor de búsqueda Nullege .


1
"La funcionalidad de reducción se puede hacer usando la función de recursión" ... O un forbucle.
Jason Orendorff

2
Además, la búsqueda de reduce () genera proyectos que definen funciones de reducción dentro de su código. Debe buscar lang: python "reduce (" para encontrar usos reales de la función incorporada.
Seun Osewa

@Seun Osewa: Incluso la búsqueda lang:python "reduce("encontrará definiciones que reducedependerán del estilo de codificación del código fuente.
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
¿Podrías quizás agregar una pequeña explicación de lo que está pasando aquí? De lo contrario, la utilidad realmente no es obvia en absoluto.
Zoran Pavlovic

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


1

Tengo una antigua implementación Python de pipegrep que usa reduce y el módulo glob para construir una lista de archivos para procesar:

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

Lo encontré útil en ese momento, pero realmente no es necesario, ya que algo similar es igual de bueno, y probablemente más legible

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

¿Qué tal una lista de comprensión? Esta parece ser una aplicación perfecta para ello: files = [glob.glob(f) for f in args]
steveha

En realidad, @steveha, su ejemplo dará como resultado una lista de listas de globos expandidos, en lugar de una lista plana de todos los elementos que coinciden con los globos, pero podría usar una lista de comprensión + suma, como @ [Eli Courtwright] (# 16198 ) Señala.
Blair Conrad

1
De acuerdo, tienes razón, perdón por eso. ¡Todavía no me gusta mucho la combinación de extender / reducir / lambda / mapa! Recomendaría importar itertools, usando la flatten()receta de docs.python.org/library/itertools.html , y luego escribiendo: files = flatten(glob.glob(f) for f in args) (Y esta vez, probé el código antes de publicarlo, y sé que esto funciona correctamente.)
steveha

files = chain.from_iterable(imap(iglob, args))where chain, imapare from itertoolsmodule y glob.iglobes útil si un patrón de argspuede generar archivos de varios directorios.
jfs

1

Digamos que hay algunos datos estadísticos anuales almacenados en una lista de contadores. Queremos encontrar los valores MIN / MAX en cada mes a través de los diferentes años. Por ejemplo, para enero sería 10. Y para febrero sería 15. Necesitamos almacenar los resultados en un nuevo 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

Tengo objetos que representan algún tipo de intervalos superpuestos (exones genómicos) y redefiní su intersección usando __and__:

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

Luego, cuando tengo una colección de ellos (por ejemplo, en el mismo gen), uso

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

1

Acabo de encontrar un uso útil de reduce: dividir cadenas sin quitar el delimitador . El código es completamente del blog Programatic Speaking. Aquí está el código:

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

Aquí está el resultado:

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

Tenga en cuenta que maneja casos extremos que la respuesta popular en SO no. Para una explicación más detallada, te estoy redirigiendo a la publicación original del blog.


0

Usando reduce () para averiguar si una lista de fechas es 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"
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.