Code utile qui utilise réduire ()? [fermé]


123

Quelqu'un ici a-t-il un code utile qui utilise la fonction reduction () en python? Existe-t-il un code autre que les + et * habituels que nous voyons dans les exemples?

Reportez-vous à Fate of Reduce () en Python 3000 par GvR


1
from functools import reducepermet au même code de fonctionner sur Python 2 et 3.
jfs

Réponses:


66

Les autres utilisations que j'ai trouvées pour cela en plus de + et * étaient avec et et ou, mais maintenant nous avons anyet allpour remplacer ces cas.

foldlet foldrviennent souvent dans Scheme ...

Voici quelques usages mignons:

Aplatir une liste

Objectif: [[1, 2, 3], [4, 5], [6, 7, 8]]devenir [1, 2, 3, 4, 5, 6, 7, 8].

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

Liste des chiffres d'un nombre

Objectif: [1, 2, 3, 4, 5, 6, 7, 8]devenir 12345678.

Voie moche et lente:

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

Jolie reducefaçon:

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

23
Pour aplatir une liste, je préfère list (itertools.chain (* nested_list))
Roberto Bonvallet

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

3
C'est également utile pour les opérations au niveau du bit. Que faire si vous voulez prendre le bit à bit ou un tas de nombres, par exemple si vous avez besoin de convertir des indicateurs d'une liste en un masque de bits?
Antimoine

6
En faisant quelques benchmarks, la méthode «laide» est plus rapide pour les grandes listes. timeit.repeat('int("".join(map(str, digit_list)))', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)prend environ 0,09 seconde tandis 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)prend 0,36 seconde (environ 4 fois plus lent). Fondamentalement, la multiplication par 10 devient coûteuse lorsque la liste devient grande, tandis que int vers str et la concaténation restent bon marché.
dr jimbob

3
Certes, oui pour les petites listes (taille 10), la méthode de réduction est 1,3 fois plus rapide. Cependant, même dans ce cas, éviter de réduire et faire une simple boucle est encore plus rapide 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)prend 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)prend 0,12 s et la conversion des chiffres en méthode str prend 0,16 s.
dr jimbob

51

reduce()peut être utilisé pour trouver le plus petit multiple commun pour 3 nombres ou plus :

#!/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)

Exemple:

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

1
Qu'y a-t-il lcmdans la deuxième ligne?
beardc

1
@BirdJaguarIV: suivez le lien dans la réponse. lcm()renvoie le plus petit multiple commun de deux nombres.
jfs

39

reduce()pourrait être utilisé pour résoudre les noms en pointillés (où eval()est trop dangereux pour être utilisé):

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


12

Je pense que réduire est une commande idiote. Par conséquent:

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

1
J'aime aussi l'ironie ici
Roman

11

L'utilisation de ce reduceque j'ai trouvé dans mon code impliquait la situation où j'avais une structure de classe pour l'expression logique et j'avais besoin de convertir une liste de ces objets d'expression en une conjonction des expressions. J'avais déjà une fonction make_andpour créer une conjonction à partir de deux expressions, alors j'ai écrit reduce(make_and,l). (Je savais que la liste n'était pas vide; sinon, cela aurait été quelque chose comme ça reduce(make_and,l,make_true).)

C'est exactement la raison pour laquelle (certains) programmeurs fonctionnels aiment reduce(ou replient les fonctions, comme de telles fonctions, sont généralement appelées). Il y a souvent déjà de nombreuses fonctions binaires comme +, *, min, max, concaténation et, dans mon cas, make_andet make_or. Avoir un reducerend trivial de lever ces opérations vers des listes (ou des arbres ou tout ce que vous avez, pour les fonctions de repli en général).

Bien sûr, si certaines instanciations (telles que sum) sont souvent utilisées, vous ne voulez pas continuer à écrire reduce. Cependant, au lieu de définir le sumavec une boucle for, vous pouvez tout aussi facilement le définir avec reduce.

La lisibilité, comme mentionné par d'autres, est en effet un problème. Vous pourriez cependant faire valoir que la seule raison pour laquelle les gens trouvent reducemoins «clair» est que ce n'est pas une fonction que beaucoup de gens connaissent et / ou utilisent.


pour se prémunir contre une liste vide, vous pourriez exploiter le comportement de court-circuit de l' andopérateur: L and reduce(make_and, L)si le retour d'une liste vide est approprié dans ce cas
jfs

9

Composition des fonctions : si vous disposez déjà d'une liste de fonctions que vous souhaitez appliquer successivement, telles que:

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]

Ensuite, vous pouvez tous les appliquer consécutivement avec:

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

Dans ce cas, le chaînage de méthodes peut être plus lisible. Mais parfois, ce n'est pas possible, et ce type de composition peut être plus lisible et maintenable qu'une f1(f2(f3(f4(x))))sorte de syntaxe.


1
Un avantage est que vous pouvez modifier la liste des fonctions à appliquer dans le code.
hakanc


7

@Blair Conrad: Vous pouvez également implémenter votre glob / reduction en utilisant sum, comme ceci:

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

C'est moins verbeux que l'un ou l'autre de vos deux exemples, est parfaitement pythonique et ne représente toujours qu'une seule ligne de code.

Donc, pour répondre à la question initiale, j'essaie personnellement d'éviter d'utiliser réduire car ce n'est jamais vraiment nécessaire et je trouve que c'est moins clair que d'autres approches. Cependant, certaines personnes s'habituent à réduire et en viennent à le préférer à la liste des compréhensions (en particulier les programmeurs Haskell). Mais si vous ne pensez pas déjà à un problème en termes de réduction, vous n'avez probablement pas à vous soucier de son utilisation.


2
Les deux sumet reduceconduisent à un comportement quadratique. Il peut se faire dans le temps linéaire: files = chain.from_iterable(imap(iglob, args)). Bien que cela n'ait probablement pas d'importance dans ce cas en raison du temps nécessaire à glob () pour accéder à un disque.
jfs

6

reduce peut être utilisé pour prendre en charge les recherches d'attributs chaînés:

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

Bien sûr, cela équivaut à

self.request.user.email

mais c'est utile lorsque votre code doit accepter une liste arbitraire d'attributs.

(Les attributs chaînés de longueur arbitraire sont courants lorsqu'il s'agit de modèles Django.)


4

reduceest utile lorsque vous avez besoin de trouver l'union ou l'intersection d'une séquence d' setobjets semblables.

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

(Outre les sets réels , un exemple de ceux-ci sont les objets Q de Django .)

D'un autre côté, si vous traitez avec bools, vous devez utiliser anyet all:

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


3

J'écris une fonction de composition pour un langage, donc je construis la fonction composée en utilisant réduire avec mon opérateur apply.

En un mot, compose prend une liste de fonctions à composer en une seule fonction. Si j'ai une opération complexe qui est appliquée par étapes, je veux tout mettre ensemble comme ceci:

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

De cette façon, je peux ensuite l'appliquer à une expression comme ceci:

complexop(expression)

Et je veux que ce soit équivalent à:

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

Maintenant, pour construire mes objets internes, je veux qu'il dise:

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

(La classe Lambda crée une fonction définie par l'utilisateur et Apply crée une application de fonction.)

Maintenant, réduisez, malheureusement, les plis dans le mauvais sens, alors j'ai fini par utiliser, à peu près:

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

Pour comprendre ce que produit la réduction, essayez-les dans le REPL:

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

J'avais l'habitude compose = lambda *func: lambda arg: reduce(lambda x, f: f(x), reversed(funcs), arg)de générer toutes les combinaisons possibles de fonctions pour les tests de performances.
jfs

3

réduire peut être utilisé pour obtenir la liste avec le nième élément maximum

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

renverrait [5, 2, 5, 7] car c'est la liste avec un maximum de 3ème élément +


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

3

Réduire ne se limite pas aux opérations scalaires; il peut également être utilisé pour trier les choses dans des seaux. (C'est ce que j'utilise le plus souvent pour réduire).

Imaginez un cas dans lequel vous avez une liste d'objets et que vous souhaitez la réorganiser hiérarchiquement en fonction des propriétés stockées à plat dans l'objet. Dans l'exemple suivant, je produis une liste d'objets de métadonnées liés à des articles dans un journal encodé en XML avec la articlesfonction. articlesgénère une liste d'éléments XML, puis les mappe un par un, produisant des objets contenant des informations intéressantes à leur sujet. Sur le front-end, je vais vouloir laisser l'utilisateur parcourir les articles par section / sous-section / titre. J'utilise donc reducepour prendre la liste des articles et retourner un dictionnaire unique qui reflète la hiérarchie section / sous-section / article.

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

Je donne les deux fonctions ici parce que je pense que cela montre comment la carte et la réduction peuvent bien se compléter lorsqu'il s'agit d'objets. La même chose aurait pu être accomplie avec une boucle for, ... mais passer du temps sérieux avec un langage fonctionnel a eu tendance à me faire penser en termes de map et de réduire.

Au fait, si quelqu'un a un meilleur moyen de définir des propriétés comme je le fais dans extract, où les parents de la propriété que vous souhaitez définir n'existent peut-être pas encore, veuillez me le faire savoir.


3

Je ne sais pas si c'est ce que vous recherchez, mais vous pouvez rechercher le code source sur Google .

Suivez le lien pour une recherche sur 'function: reduction () lang: python' sur la recherche Google Code

À première vue, les projets suivants utilisent reduce()

  • MoinMoin
  • Zope
  • Numérique
  • Scientifique Python

etc. etc. mais alors ce ne sont guère surprenants car ce sont d'énormes projets.

La fonctionnalité de réduction peut être réalisée en utilisant la récursivité de la fonction que Guido pensait plus explicite.

Mettre à jour:

Depuis que la recherche de code de Google a été interrompue le 15 janvier 2012, en plus du retour aux recherches Google régulières, il y a quelque chose appelé Collection d'extraits de code qui semble prometteur. Un certain nombre d'autres ressources sont mentionnées dans les réponses à cette question (fermée) Remplacement de Google Code Search? .

Mise à jour 2 (29 mai 2017):

Une bonne source d'exemples Python (en code open-source) est le moteur de recherche Nullege .


1
"La fonctionnalité de réduction peut se faire en utilisant la fonction de récursivité" ... Ou une forboucle.
Jason Orendorff

2
En outre, la recherche de reduction () produit des projets qui définissent des fonctions de réduction dans leur code. Vous devriez rechercher lang: python "réduire (" pour trouver les utilisations réelles de la fonction intégrée.
Seun Osewa

@Seun Osewa: Même la recherche de lang:python "reduce("trouvera des définitions en reducefonction du style de codage du code source.
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
Pourriez-vous peut-être ajouter une petite explication sur ce qui se passe ici? Sinon, l'utilité n'est vraiment pas du tout évidente.
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

J'avais l'habitude reduce de concaténer une liste de vecteurs de recherche PostgreSQL avec l' ||opérateur dans 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

J'ai une ancienne implémentation Python de pipegrep qui utilise réduire et le module glob pour créer une liste de fichiers à traiter:

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

Je l'ai trouvé pratique à l'époque, mais ce n'est vraiment pas nécessaire, car quelque chose de similaire est tout aussi bon, et probablement plus lisible

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

Que diriez-vous d'une compréhension de liste? Cela semble être une application parfaite pour cela: files = [glob.glob(f) for f in args]
steveha

En fait, @steveha, votre exemple se traduira par une liste de listes de globes développés, plutôt qu'une liste plate de tous les éléments qui correspondent aux globs, mais vous pouvez utiliser une liste de compréhension + somme, comme @ [Eli Courtwright] (# 16198 ) fait remarquer.
Blair Conrad

1
D'accord, vous avez raison, désolé. Je n'aime toujours pas beaucoup la combinaison étendre / réduire / lambda / map! Je recommanderais d'importer itertools, en utilisant la flatten()recette de docs.python.org/library/itertools.html , puis d'écrire: files = flatten(glob.glob(f) for f in args) (Et cette fois, j'ai testé le code avant de le publier, et je sais que cela fonctionne correctement.)
steveha

files = chain.from_iterable(imap(iglob, args))chain, imapproviennent du itertoolsmodule et glob.iglobest utile si un modèle de argspeut générer des fichiers de plusieurs répertoires.
jfs

1

Supposons qu'il existe des données statistiques annuelles stockées dans une liste de compteurs. Nous voulons trouver les valeurs MIN / MAX de chaque mois sur les différentes années. Par exemple, pour janvier, ce serait 10. Et pour février, ce serait 15. Nous devons stocker les résultats dans un nouveau compteur.

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

J'ai des objets représentant une sorte d'intervalles qui se chevauchent (exons génomiques), et j'ai redéfini leur intersection en utilisant __and__:

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

Ensuite, quand j'en ai une collection (par exemple, dans le même gène), j'utilise

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

1

Je viens de trouver une utilisation utile de reduce: fractionner une chaîne sans supprimer le délimiteur . Le code provient entièrement du blog Programately Speaking. Voici le code:

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

Voici le résultat:

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

Notez qu'il gère les cas extrêmes que la réponse populaire dans SO ne fait pas. Pour une explication plus approfondie, je vous redirige vers l'article de blog original.


0

Utilisation de réduire () pour savoir si une liste de dates est consécutive:

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"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.