Корисний код, який використовує зменшити ()? [зачинено]


123

Хтось тут має корисний код, який використовує функцію redu () у python? Чи є якийсь код, крім звичайних + та *, який ми бачимо в прикладах?

Дивіться долю скорочення () у Python 3000 від GvR


1
from functools import reduceдозволяє однаковому коду працювати і на Python 2 і 3.
jfs

Відповіді:


66

Інші застосунки, які я знайшов для цього, окрім + і *, були з та і або, але тепер ми маємо anyі allзамінити ці випадки.

foldlі foldrпридумати в схемі багато ...

Ось кілька милих звичаїв:

Вирівняти список

Мета: перетворитися [[1, 2, 3], [4, 5], [6, 7, 8]]на [1, 2, 3, 4, 5, 6, 7, 8].

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

Список цифр до числа

Мета: перетворитися [1, 2, 3, 4, 5, 6, 7, 8]на 12345678.

Некрасивий, повільний шлях:

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

Гарний reduceспосіб:

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

23
Для вирівнювання списку я віддаю перевагу списку (itertools.chain (* nested_list))
Роберто Бонвальлет

13
сума ([[1, 2, 3], [4, 5], [6, 7, 8]], [])
Гордон Вріглі

3
Це також корисно для розрядних операцій. Що робити, якщо ви хочете взяти порозрядне чи купу чисел, наприклад, якщо вам потрібно перетворити прапори зі списку в бітмаску?
Сурма

6
Виконуючи деякі орієнтири, "потворний" шлях швидший для великих списків. timeit.repeat('int("".join(map(str, digit_list)))', setup = 'digit_list = list(d%10 for d in xrange(1,1000))', number=1000)займає ~ 0,09 секунди, тоді як 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)займає 0,36 секунди (приблизно в 4 рази повільніше). В основному множення на 10 стає дорогим, коли список стає великим, тоді як int до str та конкатенація залишаються дешевими.
dr jimbob

3
Зазначено, так, для невеликих списків (розмір 10), тоді метод зменшення в 1,3 рази швидший. Однак навіть у цьому випадку уникнення зменшення та виконання простого циклу ще швидше 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)займає 0,06 с, 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 с, а перетворення цифр у метод str займає 0,16 с.
dr jimbob

51

reduce()можна використовувати для пошуку найменшого загального кратного для 3 або більше чисел :

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

Приклад:

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

1
Що lcmу другому рядку?
борода

1
@BirdJaguarIV: перейдіть за посиланням у відповіді. lcm()повертає найменше спільне кратне два числа.
jfs

39

reduce()може використовуватися для вирішення пунктирних імен (де eval()занадто небезпечно використовувати):

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


12

Я думаю, що зменшити - це дурна команда. Звідси:

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

1
Мені також подобається іронія тут
Роман

11

Використання цього, reduceщо я знайшов у своєму коді, включало ситуацію, коли у мене була якась структура класу для логічного вираження, і мені потрібно було перетворити список цих об'єктів вираження у сполучення виразів. У мене вже була функція make_andстворити сполучник із заданими двома виразами, тому я написав reduce(make_and,l). (Я знав, що список не порожній; інакше це було б щось подібне reduce(make_and,l,make_true).)

Це саме та причина, що (деякі) функціональні програмісти люблять reduce(або складають функції, як такі функції зазвичай називають). Є часто вже багато бінарних функцій , таких як +, *, min, max, конкатенація і, в моєму випадку, make_andі make_or. Наявність reduceманіфесту піднімає ці операції до списків (або дерев чи будь-чого, що ви отримали, для функцій складання загалом).

Звичайно, якщо sumчасто використовуються певні моменти (наприклад ), то ви не хочете продовжувати писати reduce. Однак замість того, щоб визначити sumз деяким for-loop, ви можете так само легко визначити його reduce.

Читання, як зазначають інші, справді є проблемою. Ви можете стверджувати, що єдиною причиною, чому люди вважають reduceменш "зрозумілою" є те, що це не є функцією, яку багато людей знають та / або використовують.


для захисту від порожнього списку ви можете скористатися поведінкою andоператора короткого замикання : L and reduce(make_and, L)якщо повернення порожнього списку в цьому випадку
доречне

9

Склад функції : Якщо у вас вже є список функцій, які ви хочете застосовувати послідовно, такі як:

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]

Потім ви можете застосовувати їх послідовно за допомогою:

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

У цьому випадку ланцюжок методів може бути більш читабельним. Але іноді це неможливо, і такий вид композиції може бути більш читабельним та доглянутим, ніж f1(f2(f3(f4(x))))якийсь синтаксис.


1
Перевагою є те, що ви можете змінити список функцій, які потрібно застосувати в коді.
hakanc


7

@Blair Conrad: Ви також можете реалізувати глобус / зменшити за допомогою суми, наприклад:

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

Це менш багатослівно, ніж будь-який із ваших двох прикладів, ідеально піфонічний і все ще є лише одним рядком коду.

Тому, щоб відповісти на початкове запитання, я особисто намагаюся уникати використання скорочення, оскільки це ніколи не потрібно, і я вважаю його менш зрозумілим, ніж інші підходи. Однак деякі люди звикають до скорочення і вважають за краще перелічити розуміння (особливо програмістів Haskell). Але якщо ви вже не замислюєтесь над проблемою з точки зору зменшення, вам, ймовірно, не потрібно хвилюватися про її використання.


2
І те sumй reduceінше веде до квадратичної поведінки. Це може бути зроблено в лінійний час: files = chain.from_iterable(imap(iglob, args)). Хоча це, мабуть, не має значення в цьому випадку через час, який потрібно глобалу () отримати доступ до диска.
jfs

6

reduce може використовуватися для підтримки ланцюгових пошукових атрибутів:

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

Звичайно, це рівнозначно

self.request.user.email

але корисно, коли ваш код повинен прийняти довільний список атрибутів.

(Приковані атрибути довільної довжини є загальними при роботі з моделями Джанго.)


4

reduceкорисно, коли вам потрібно знайти об'єднання або перетин послідовності setоб'єктів-подібних.

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

(Окрім фактичних sets, прикладом таких є Q-об'єкти Django .)

З іншого боку, якщо ви маєте справу з boolами, слід використовувати anyі all:

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


3

Я пишу композитну функцію для мови, тому я будую складену функцію, використовуючи скорочення разом зі своїм оператором застосування.

Коротше кажучи, композиція приймає список функцій для складання в одну функцію. Якщо у мене є складна операція, яка застосовується поетапно, я хочу все це скласти так:

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

Таким чином, я можу застосувати його до такого виразу:

complexop(expression)

І я хочу, щоб це було рівнозначним:

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

Тепер, щоб створити свої внутрішні об’єкти, я хочу, щоб він сказав:

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

(Клас Lambda створює функцію, визначену користувачем, і Apply будує додаток функції.)

Тепер, зменшуйте, на жаль, складки неправильно, тому я завершив використання, приблизно:

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

Щоб розібратися, що знижує кількість продуктів, спробуйте це у відповіді:

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)для створення всіх можливих комбінацій функцій для тестування продуктивності.
jfs

3

скорочення може бути використане для отримання списку з максимальним n-м елементом

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

повернеться [5, 2, 5, 7], оскільки це список з max 3-го елемента +


макс (lst, ключ = лямбда x: x [2])
aoeu256

3

Зменшення не обмежується скалярними операціями; його також можна використовувати для сортування речей у відра. (Це найчастіше я використовую у зниженні).

Уявіть собі випадок, коли у вас є список об’єктів, і ви хочете переорганізувати його ієрархічно на основі властивостей, що зберігаються на об'єкті. У наступному прикладі я створюю список об’єктів метаданих, пов’язаних зі статтями в газеті, кодованій XML, з articlesфункцією. articlesстворює список XML-елементів, а потім по черзі відображає їх, створюючи об'єкти, що містять цікаву інформацію про них. На передньому кінці я хочу дозволити користувачу переглядати статті за розділом / підрозділом / заголовком. Тому я використовую reduceперелік статей і повертаю єдиний словник, який відображає ієрархію розділу / підрозділу / статті.

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

Тут я надаю обидві функції, тому що я думаю, що це показує, як карта та зменшення можуть красиво доповнювати один одного при роботі з об'єктами. Те ж саме можна було б зробити з циклом for, ... але витратити якийсь серйозний час з функціональною мовою, як правило, змусив мене думати з точки зору карти та зменшити.

До речі, якщо у когось є кращий спосіб встановити властивості, як я роблю extract, де батьки власності, яку ви хочете встановити, ще не існують, будь ласка, повідомте мене про це.


3

Не впевнений, що це те, що ви хочете, але ви можете шукати вихідний код в Google .

Перейдіть за посиланням для пошуку за функцією 'function: redu () lang: python' у пошуку Google Code

На перший погляд використовуються наступні проекти reduce()

  • MoinMoin
  • Zope
  • Числові
  • ScientificPython

і т. д., але тоді це навряд чи дивно, оскільки це величезні проекти.

Функціональність зменшення може бути виконана за допомогою функціональної рекурсії, яку, напевно, Гайдо вважав більш чіткою.

Оновлення:

Оскільки пошук коду Google було припинено 15 січня 2012 року, окрім того, як повернутися до звичайних пошукових запитів Google, є щось, що називається Колекція фрагментів коду, що виглядає перспективно. Ряд інших ресурсів згадується у відповідях на це (закрите) запитання Заміна для пошуку в коді Google?.

Оновлення 2 (29 травня-2017):

Хорошим джерелом для прикладів Python (у відкритому коді) є пошукова система Nullege .


1
"Функціональність зменшення може бути виконана за допомогою функції рекурсії" ... Або forциклу.
Джейсон Орендорф

2
Крім того, пошук за зменшенням () дає проекти, які визначають функції скорочення у своєму коді. Вам слід шукати lang: python "
redu

@Seun Osewa: Навіть у пошуку lang:python "reduce("знайдемо визначення reduceзалежно від стилю кодування вихідного коду.
мартіно

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
Не могли б ви додати трохи пояснень, що тут відбувається? Інакше корисність насправді зовсім не очевидна.
Зоран Павлович

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


1

У мене є стара реалізація Pygre на pipegrep, який використовує файл зменшення та модуль glob, щоб створити список файлів для обробки:

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

Я вважав це зручним у той час, але це дійсно не потрібно, оскільки щось подібне настільки ж добре, і, ймовірно, читабельніше

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

Як щодо розуміння списку? Це здається ідеальним додатком для цього: files = [glob.glob(f) for f in args]
steveha

Насправді, @steveha, ваш приклад призведе до переліку списків розширених глобусів, а не до плоского списку всіх елементів, що відповідають глобусам, але ви можете використовувати розуміння списку + суму, як @ [Eli Courtwright] (# 16198 ) вказує на те.
Блер Конрад

1
Гаразд, ви праві, вибачте за це. Мені все ще не подобається комбінація розширення / зменшення / лямбда / карти! Я рекомендую імпортувати itertools, скориставшись flatten()рецептом з docs.python.org/library/itertools.html , а потім напишіть : files = flatten(glob.glob(f) for f in args) (І цього разу я перевірив код, перш ніж публікувати його, і я знаю, що це працює правильно.)
steveha

files = chain.from_iterable(imap(iglob, args))де chain, imapзнаходяться з itertoolsмодуля і glob.iglobкорисно, якщо шаблон із argsфайлу може отримати файли з декількох каталогів.
jfs

1

Скажімо, що існує кілька щорічних статистичних даних, що зберігаються у списку лічильників. Ми хочемо знайти значення MIN / MAX в кожному місяці протягом різних років. Наприклад, за січень було б 10. А для лютого було б 15. Нам потрібно зберігати результати в новому лічильнику.

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

У мене є об'єкти, що представляють собою певні інтервали, що перекриваються (геномні екзони), і переглянув їх перетин, використовуючи __and__:

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

Потім, коли у мене є колекція їх (наприклад, у тому ж гені), я використовую

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

1

Щойно я знайшов корисне використання reduce: розділення рядка без вилучення роздільника . Код повністю створений з блогу Programatic Speaking. Ось код:

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

Ось результат:

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

Зауважте, що він обробляє крайові випадки, на які популярна відповідь в ТА не відповідає. Для більш поглибленого пояснення я перенаправляю вас до оригінальної публікації в блозі.


0

Використовуючи зменшити (), щоб дізнатися, чи перелік дат є послідовним:

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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.