LINUX.ORG.RU

[common lisp][ищу морфизм] ещё одна годная задачка про списки

 


0

1

В продолжение этой темы (a.k.a. «детский сад») и функциональный код vs. императивный. (Другая задачка была тут.)

Вот пусть есть такая простейшая задача - нужно отфильтровать список по предикату на две части и свернуть первую с помощью какой-то функции.

;;;; motivation (dully functional perspective)
;;;;
;;;;   (+ a 1 b 2 c 3) => (+ 6 a b c)
;;;;
;;;; wanted
;;;;
;;;;   (filter/reduce #'+ 0 #'numberp '(a 1 b 2 c 3))
;;;;   => (6 a b c)
;;;;
;;;; with
;;;;
;;;;   1) (n + m) iterations over the LIST (where n is the number of LIST elements
;;;;   that satisfy to PREDICATE and m is the number of elements which do not
;;;;   satisfy, (n + m) is the length of the LIST of course).
;;;;
;;;;   2) memory allocation only for the new list (with (m + 1) elements),
;;;;   without temporary lists.

т.е. нужна более менее общая функция filter/reduce которая фильтрует на две части и сворачивает на одной из них. Но тут ещё поставлено условие: это должно осуществляться в _один_ проход (n + m итераций) и не должно использовать лишнюю память - только аллокация списка-результата.

Отличительная особенность тут в том, что одна списочная функция (reduce) применяется к результату работы другой списочной функции (filter). Понятно, что это приводит к излишним итерациям и аллокации излишней памяти, поэтому есть варианты.

Соответственно, возможные решения:

;;;; assume that compiler don't have the list fusion optimizations

;;;; for all variants:
(declaim (ftype (function (function t function list) (values t list)) filter/reduce))

;;;; I. functional variants

;;;; higher-order functions (expressed in terms of cata/ana/para/hylo)

;;; 1) FILTER/REDUCE as two FILTER and FOLD-LEFT combination.
;;;
;;;   this version do (3*n + 2*m) iterations and allocate (2*n + m + 1) elements.
;;;
(defun filter/reduce (function initial-value predicate list)
  (values (fold-left function initial-value (filter predicate list))
          (filter (compose 'not predicate) list)))

;;; 2) FILTER/REDUCE as PARTITION and FOLD-LEFT combination.
;;;
;;;   (2*n + m) iterations in this version and (2*n + m + 1) new elements.
;;;
(defun filter/reduce (function initial-value predicate list)
  (multiple-value-bind (allows not-allows)
      (partition predicate list)
    (values (fold-left function initial-value allows) not-allows)))

;;; 3) FILTER/REDUCE straigh from the folder FOLD-LEFT.
;;;
;;;   (n + m) iterations and allocate more than (2*n + m + 1) new elements
;;;   because fold-left used accumulators which add quadratic item to the sum.
;;;
(defun filter/reduce (function initial-value predicate list)
  (fold-left #'(lambda (rest e)
                 (if (funcall predicate e)
                     (cons (funcall function (car rest) e) (cdr rest))
                     (cons (car rest) (cons e (cdr rest)))))
             (list* initial-value nil)
             list))

;;;; recursive variant (when you sedulous SICP reader)

;;; 4) FILTER/REDUCE as recursive function (in TCO form).
;;;
;;;   (n + m) iterations and allocate (m + 1) new elements + n garbage elements.
;;;
;;;   KLUDGE: REST in reverse order.
;;;
(defun filter/reduce (function initial-value predicate list)
  (labels ((rec (list reduced rest)
             (cond ((endp list)
                    (values reduced rest))
                   ((funcall predicate (first list))
                    (rec (rest list)
                         (funcall function reduced (first list))
                         rest))
                   (t
                    (rec (rest list)
                         reduced
                         (cons (first list) rest))))))
    (rec list initial-value nil)))

;;;; II. imperative variants

;;;; all variants do (n + m) iterations and allocate (m + 1) new elements
;;;; + n garbage elements.

;;; 5) COLLECT + DOLIST + SETF
(defun filter/reduce (function initial-value predicate list)
  (collect ((not-constants))
    (dolist (e list)
      (if (funcall predicate e)
          (setf initial-value (funcall function initial-value e))
          (not-constants e)))
    (values initial-value (not-constants))))

;;;; 6) LOOP
(defun filter/reduce (function initial-value predicate list)
  (loop :for e :in list
        :if (funcall predicate e)
          :do (setf initial-value (funcall function initial-value e))
        :else
          :collect e :into not-constants
        :end
        :finally (return (values initial-value not-constants))))

;;;; 7) ITERATE
(defun filter/reduce (function initial-value predicate list)
  (iter (for e in list)
        (if (funcall predicate e)
            (setf initial-value (funcall function initial-value e))
            (collect e into not-constants))
        (finally (return (values initial-value not-constants)))))

(CL notes: fold-left это reduce, filter - mapcan, collect это SB-IMPL::COLLECT)

Т.е. можно:

(1) и (2) Писать декларативно, не глядя на производительность.

Учитывая, что filter/reduce может быть представлена как:

  1. Свёртка

  2. Рекурсивная функция

  3. Инструкции некой регистровой машины с последовательным исполнением команд и GOTO.

(т.е. это три таких независимых формализма) можно:

(3) Выражать filter/reduce напрямую через свёртку, что несколько уменьшает понятность и декларативность, но эффективно. Правда, тут есть оверхед на память под аккумуляторы, чего нет в (4) варианте.

(4) Писать filter/reduce как рекурсивную процедуру (если реализация поддерживает TCO - писать в TC форме). В некоторых простых случаях это самый естесвенный способ (в SICP много таких примеров). Но в данном случае это ещё больше уменьшает понятность, но также как и (3) имеет хорошую производительность (если TCO).

(5) Писать императивно, в смысле (c).

(6) и (7) Писать на DSL для обработки последовательностей, который сводится к императивному коду. Тут это LOOP и ITERATE. Это эффективно, не декларативно (отстаёт в понятности от (1)), но довольно легко по коду проследить за логикой и понять что этот код делает.

Ну и последний вариант:

  • Использовать реализацию языка с list fusion оптимизациями :)

В целом получается такая картинка:

                                                       читаемость
  -(4)----(3)----(6?)--(5)--(7)-------------------------(2)-(1)->

 4,5,6,7
    V
  <-*--(3)--------------------------------------------------(1)--
  производительность

Т.е. (1) совсем плохо работате, а iterate оказывается компромисом в вопросе производительность/читаемость.

З.Ы.

Сама задачка такая - как заставить (1) работать эквивалентно последним вариантам? :) Почему три последних варианта работают быстро понятно - они легко сводятся к естественному для железа представлению (регистровая машина с jump-ами), про (4) тоже понятно - TCO делает своё дело (сводит к естественному для железа ...). Но что касается (1) - единственная основа которую сюда можно подвести это какие-нибудь алгебраические соображения, на тему того что cata = out . rec . gen и есть соответствующая диаграма отображений; ana, para и hylo имеют подобные соотношения (и некоторый категорный смысл), а все сабжевые функции выражаются через них (и аналогично для других АТД). Например, filter/reduce очень просто выражается через две функции одна из которых свёртка, а другая выражается через свёртку, а также filter/reduce выражается через свёртку непосредственно - должно существовать преобразование из первого во второе. Может уже есть где-нибудь такие оптимизации (list fusion?), или какие-нибудь статьи на тему?

★★★★

Ответ на: комментарий от archimag

Казалось бы, что опыт использования разных подходов должен был научить немного поразмыслить, прежде чем откладывать код в сторону с фразой «частный костыль» :)

balodja ★★★
()
Ответ на: комментарий от archimag

скрипт погруженный в литературное окружение прекрасно(быстро) читается... не говоря о том что прекрасно(быстро) пишется.

одноразовость какое то плохое свойство? если что то решило задачу и прекрасно написанное в стиле литературного программирования лежит и кушать не просит, то что в этом плохого?

использовать ранее написанное в стиле литературного програмирования ничуть не труднее, чем прилагать усилия к нечеловеческому написанию кода в стиле «всегда все всем понятно».

psv1967 ★★★★★
()
Ответ на: комментарий от psv1967

> если что то решило задачу и прекрасно написанное в стиле литературного программирования

Начнем с того, что литературное программирование, не литературное,а literate, что значит это - советую посмотреть в словаре.

использовать ранее написанное в стиле литературного програмирования ничуть не труднее

Труднее. Потому что нужно вместе поддерживать и код, и описание в согласованном виде. LP был в свое время нужен для описания сложных алгоритмов на низкоуровневых языках. Сейчас - это архаизм.

paranonymous
()
Ответ на: комментарий от balodja

хмм странно, я спросил для интереса свою девушку, она не знакома с программированием почти. И она сразу поняла как это работает.

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

Ничего странного, просто не надо врать. Она могла догадаться, каков будет результат, но не _как_ это работает.

dmsh
()
Ответ на: комментарий от dmsh

я показывал вариант с пояснениями, который ниже написал, она поняла что происходит, шаг за шагом. правда пришлось в гугле уточнить что такое итерация

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

Ну это и называется отгадала ответ. Что, она сможет объяснить как работает итерейт? Как его можно использовать, а как нельзя? Если ты дашь ей похожую задачу, что она осознанно сможет выдать валидный код? Конечно, нет. Потому что идея «смотрите как все просто и понятно, это ведь почти естественный язык, только надо добавить пару скобок» не работает. Догадаться можно, понять что-то таким образом нельзя.

dmsh
()
Ответ на: комментарий от pseudo-cat

> она сразу поняла как это работает

Врать не стыдно? Видимо, ты сам не очень втыкаешь в «то, как это работает».

balodja ★★★
()
Ответ на: комментарий от dmsh

какая разница - главное я смог ей объяснить как работает этот код заменив каждую конструкцияю на её описание на естественном языке

pseudo-cat ★★★
()
Ответ на: комментарий от balodja

никто тебе не врёт, возможно твоё эго не признает, что, видимо, ты глупей неё и тебе нужно разжёвывать всё более подробно :)

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

Да-да, твоя подруга быстро достигла твоего уровня понимания работы iterator, это делает меня автоматически глупее нее. Что же, логично.

balodja ★★★
()
Ответ на: комментарий от balodja

ну да)

по мне так логично дать посмотреть человеку, не знакомому с какой либо формой записи работы с последовательностями и посмотреть насколько интуитивно понятен ему код, вместо того, чтобы спорить с заранее предвзятыми собеседниками)

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

почему это 4 нечитаемый?

Он читаемый, но нужно некоторое время разбираться с этим кодом, согласитесь - при первом взгляде на (4) совсем не понятно что он делает. Тоже самое касается и варианта с iterate, но (ИМХО) он более краткий. А вот код в (1) - просто спецификация, что он делает понятно с первого взгляда (без анализа, вроде «тут мы передаём кусок списка, на следующем шаге суммируем туда» и т.п.).

Я именно такую запись использую для сложных, на мой взгляд, алгоритмов.

Ну тут идёт речь про простейшие РТД - списки, для них имеет смысл как рекурсия или свёртка, так и итерация. Ну и логический код, когда к списку применяются функциональный примитивы - filter, reduce и т.п., это действительно некий DSL (но естественный, т.е. функциональный) поэтому я и подумал о преобразовании простого функционального кода в однопроходный алгоритм.

Для более сложных РТД (деревьев, или тех же графов) итерация это уже traverse, а вот рекурсия это фактически воплощение принципа «разделяй и властвуй», так что не спорю - в таких случаях (4) может быть предпочтительней.

Хотя, если сводить графы к списками объектов и стрелок, то итерация всё равно возникает:

(defpackage #:graphs
  (:use     #:common-lisp
            #:iterate))

(in-package #:graphs)

;;;; define graph ADT:
;;;;   Graph ≡ { objects :: [ {index :: Index, object :: T}     ],
;;;;             arrays  :: [ {from  :: Index, to     :: Index} ] }

(deftype index () '(unsigned-byte 16))

(defstruct (object
            (:constructor make-object (index value)))
  (index nil :type index)
  value)

(defstruct (link
            (:constructor make-link (from to)))
  (from nil :type index)
  (to   nil :type index))

(declaim (inline %make-graph))
(defstruct (graph
            (:constructor %make-graph (objects links)))
  (objects nil :type list)
  (links   nil :type list))

;;;; graph iteration in term of the ITERATE macro

(defmacro do-graph ((&key object link) graph &body body)
  `(iter ,@(when object
            `((for ,object in (graph-objects ,graph))))
         ,@(when link
            `((for ,link in (graph-links ,graph))))
         ,@body))

;;;; define DSL for creating graphs:
;;;;   Graph ≡ [ index object [ -> | <- | <-> ] index object ]*

(declaim (ftype (function (list) graph) make-graph))
(defun make-graph (args)
  (iter (for link in args)
        (destructuring-bind (i a del k b) link
          (collect (make-object i a) into objects)
          (collect (make-object k b) into objects)
          (ecase del
            (->  (collect (make-link i k) into links))
            (<-  (collect (make-link k i) into links))
            (<-> (collect (make-link i k) into links)
                 (collect (make-link k i) into links)))
          (finally (return (%make-graph objects links))))))

(defmacro define-graph (var &rest args)
  (if var
      `(defparameter ,var (make-graph ',args))
      `(make-graph ',args)))

;;;; basic graph methods

(defgeneric find-object (graph index))
(defgeneric find-subgraph (graph graph))

(defmethod find-object ((graph graph) index)
  (do-graph (:object object) graph
    (when (= index (object-index object))
      (return-from find-object (object-value object)))))

(defmethod print-object ((graph graph) stream)
  (format stream "graph:~%")
  (dolist (link (graph-links graph))
    (format stream
            "  ~A . ~A -> ~A . ~A~%"
            (link-from link)
            (find-object graph (link-from link))
            (link-to link)
            (find-object graph (link-to link)))))

;;; Example:
;;;
;;; (define-graph () (1 a <-> 2 b) (2 b -> 3 c))
;;; graph:
;;;   1 . A -> 2 . B
;;;   2 . B -> 1 . A
;;;   2 . B -> 3 . C

(тут ещё можно хранить у объектов списки соседов - тогда будет легче путешествовать по графу, но придётся делать лишние движения при его обновлении).

Из последних - поиск подграфа в графе, который, коллеги пытались реализовать циклами и запутались в нагромождении получившегося кода=)

А зачем они путались? :) Ведь уже есть алгоритм Ульмана. Кстати, в нём нет циклов, но он императивный, в духе условий/следований/безусловных переходов. Правда есть ещё другое решение этой проблемы (изоморфизм подграфа), которое используется в Google's MapReduce - там именно рекурсия из цикла (проблема ведь NP-полная) и он заточен на параллельность (есть статья на эту тему).

А если нужно искать расцвеченый подграф в графе (т.е. вершины имеют «значения», как в коде выше), то сначала решают проблему изоморфизма, и если получен непустой ответ, то просто сравнивают два одинаковых структурно графа (итерацией).

quasimoto ★★★★
() автор топика
Ответ на: комментарий от pseudo-cat

А временную сложность твоя девушка оценить смогла? Если нет, то такое понимание ничего не стоит, ведь кроме тупого работает/не работает у кода есть ещё куча других важных характеристик, о которых без знания внутренностей iterate ничего сказать нельзя

tarc
()
Ответ на: комментарий от tarc

что? ахах. она не программист, зачем ей это? я думаю если сказать ей как оценивается сложность(это займёт минут 5 с примерами) то никаких проблем не будет. Главное что посмотрев на код, человек(не обязательно програмер) _интуитивно_ понимает что он делает

pseudo-cat ★★★
()
Ответ на: комментарий от quasimoto

Хотя, если сводить графы к списками объектов и стрелок, то итерация всё равно возникает:

не спорю

А зачем они путались? :)...

спасибо, обязательно разберусь с этими вещами) я писал свою реализацию не зная ничего про эти алгоритмы - были очень сжатые сроки, но теперь можно углубиться

pseudo-cat ★★★
()
Ответ на: комментарий от tarc

Временная сложность там, вроде, всегда одна и та же. Это однопроходная итерация по одной или более последовательности.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от pseudo-cat

Что значит «какая разница»? Огромная разница. Что она теперь пользоваться этим iterate сможет по-твоему? Черта с два.

dmsh
()
Ответ на: комментарий от pseudo-cat

«Интуитивного понимания» не существет. Это программирование, а не гадание на кофейной гуще. Или понимаешь как что-то работает, или нет. Будешь гадать - наступишь на грабли. Наступишь на грабли - получишь по лбу. Всё просто.

dmsh
()
Ответ на: комментарий от dmsh

> Что она теперь пользоваться этим iterate сможет по-твоему? Черта с два.

а зачем? у нас тут вроде речь шла о читабельности.

korvin_ ★★★★★
()
Ответ на: комментарий от dmsh

правда чтоли? так зачем нам интуитивно понятные названия функций? давайте вместо них будем подставлять ссылки на исходный код этих функций

Или понимаешь как что-то работает, или нет.

говорю в третий раз - понимает. Или по-вашему только опытный программист может представить себе абстрактную сущность типа коллекции элементов и понять, что по этой коллекции можно двигаться производя операции над элементами на каждом шаге движения?

pseudo-cat ★★★
()
Ответ на: комментарий от korvin_

> а зачем? у нас тут вроде речь шла о читабельности.

Потому что между «похоже, что этот код делает то, что нужно» и «да, это решение» огромная пропасть. Какой смысл читать тот код, который не сможешь исправить?

balodja ★★★
()
Ответ на: комментарий от pseudo-cat

> правда чтоли? так зачем нам интуитивно понятные названия функций? давайте вместо них будем подставлять ссылки на исходный код этих функций

«Интуитивно понятные названия функций» для того, чтобы код _проще_ читать, чтобы работал ассоциативный ряд, чтобы он помогал осознать происходящее. А не для того, чтобы изучать код на уроках литературы с фразами «дети, что хотел сказать автор?» Код пишется для программиста, а не для подружки программиста.

Зачем человеку, далекому от программирования, читать этот код? Да, он прочитает. Он увидит вместо лисповых списков коллекцию марок, вместо лисповых and/or увидит обычные логические и/или, не поймет разницу между finally, return и values, и уж тем более разницу между values и cons. Какой в этом всём смысл тогда? Его нет.

Простые аналогии из естественного языка хороши только для образовательных целей, чтобы дать хоть какое-то начальное представление о языке, чтобы дать возможность побыстрее начать ориентироваться в абстракциях и средствах языка.

«Интуитивно понятные названия функций» для программиста, «аналогии из естественного языка» для непрограммиста. Зачем последнему читать код, я не понимаю.

balodja ★★★
()
Ответ на: комментарий от paranonymous

если что то решило задачу и прекрасно написанное в стиле литературного программирования

Начнем с того, что литературное программирование, не литературное,а literate, что значит это - советую посмотреть в словаре.

надо же я всё время говорил прозой (С)

это мой друг устоявшийся термин, что отражено «в словарях».

использовать ранее написанное в стиле литературного програмирования ничуть не труднее

Труднее. Потому что нужно вместе поддерживать и код, и описание в согласованном виде. LP был в свое время нужен для описания сложных алгоритмов на низкоуровневых языках. Сейчас - это архаизм.

какие милые глупости :) попробуйте сначала, а потом учите других.

ну какие в пень «высокоуровневые языки»? даже если вы программируете решение в терминах предметной области, вы не сможете сохранить свои рассуждения почему решение получилось именно такое. А для сколь нибудь сложной задачи вы гарантированно облажаетесь наути решение просто в виду его сложности, и свойства мозга головы забывать.

это вам не олипиадные задачки на алгоритмы дрочить.

PS вброс годный, одобрям.

psv1967 ★★★★★
()
Ответ на: комментарий от balodja

> Какой смысл читать тот код, который не сможешь исправить?

А вы вообще программист? Вообще реально разработкой занимаетесь? Очень похоже, что нет. Ибо вообще тему не понимаете.

archimag ★★★
()
Ответ на: комментарий от balodja

код пишется для программиста -> программист мыслит на естественном языке(хотя последнее время я начинаю в этом сомневаться) -> код, максимально близкий к естественному языку более понятен программисту

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

код, максимально близкий к естественному языку более понятен программисту

Ну началось. Объяснить, почему это так, сможешь? А заодно расскажи как измерить «близость».

dmsh
()
Ответ на: комментарий от archimag

> А вы вообще программист?

Я тут во множественном числе?

Вообще реально разработкой занимаетесь?

Да.

Очень похоже, что нет. Ибо вообще тему не понимаете.

Ну так скажите, как Вы понимаете «тему», господин судья. А я с великим удовольствием послушаю.

balodja ★★★
()
Ответ на: комментарий от balodja

Я не учитель, если вы не понимаете важность читабельности кода, не понимаете как она достигается и не владеете соответствующими навыками, то я вам ничем помочь не смогу.

archimag ★★★
()
Ответ на: комментарий от archimag

###. Вот где я сказал, что читабельность кода, который пишешь, не важна? Где? Передергивать не надоело еще?

balodja ★★★
()
Ответ на: комментарий от balodja

> Вот где я сказал, что читабельность кода, который пишешь, не важна?

Как где?

Какой смысл читать тот код, который не сможешь исправить?

Только речь ведь не только о коде, который пишешь, а вообще о любом коде. Важна читабельность любого кода.

archimag ★★★
()
Ответ на: комментарий от archimag

Давай-ка посмотрим внимательно на контекст.

> Что она теперь пользоваться этим iterate сможет по-твоему? Черта с два.

а зачем? у нас тут вроде речь шла о читабельности.

Потому что между «похоже, что этот код делает то, что нужно» и «да, это решение» огромная пропасть. Какой смысл читать тот код, который не сможешь исправить?

Тут речь о том, что нет смысла читать тот код, который не поймешь в мере, достаточной для его исправления. Чисто в силу того, что нет достаточных для этого знаний.

balodja ★★★
()
Ответ на: комментарий от balodja

Хм, чего-то я не подумал, что архимаг банально троллит, выдергивая фразы из контекста и пропуская большую часть ответов мимо ушей.

balodja ★★★
()
Ответ на: комментарий от balodja

> Предлагаю Вам написать книгу «Код, как самоцель».

Уже написали, SICP называется.

archimag ★★★
()
Ответ на: комментарий от balodja

> Тут речь о том, что нет смысла читать тот код, который не поймешь

в мере, достаточной для его исправления.


Я об этом и говорю, что это принципиально не верно.

archimag ★★★
()
Ответ на: комментарий от balodja

нет смысла читать тот код, который не поймешь в мере, достаточной для его исправления

к примеру есть функция, проходящая по коллекции слева направо и ф-ция, идущая справа налево. Т.е. применять их нельзя пока я не узнаю как внутри они устроены? Ведь без этого я не смогу изменить их поведение.

чорт что же мне делать, пора сорцы прикладных программ изучать, как я могу, ничтожество, пользоваться их возможностями не умея изменять поведение!

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

чорт что же мне делать, пора сорцы прикладных программ изучать, как я могу, ничтожество, пользоваться их возможностями не умея изменять поведение!

небольшое уточнение: я про программирование, использующие возможности этих программ как примитивы.

pseudo-cat ★★★
()
Ответ на: комментарий от archimag

То есть на твой взгляд человеку есть смысл читать код, который он (человек) заведомо не поймет в деталях? Да это мазохизм какой-то.

balodja ★★★
()
Ответ на: комментарий от archimag

> Я об этом и говорю, что это принципиально не верно.

И, кстати, нет, Вы не об этом говорите. Вы говорите о том, мол я утверждаю, что код не должен быть читаемым. И это не так.

balodja ★★★
()
Ответ на: комментарий от pseudo-cat

Нет, ты ведешь к тому, что не нужно знать, что обозначает каждый примитив, использованный в коде. А я утверждаю, что нужно.

balodja ★★★
()
Ответ на: комментарий от balodja

Вот именно. Нужно знать, что примитив обозначает, но не нужно знать, как этот примитив работает. В коде на общелиспе любой поймет, что обозначают те или иные примитивы просто потому, что они обозначают именно то, чем называются. Хотя оптимален в плане читабельности, конечно же, вариант с рекурсией - в отличии от loop/iterate он декларативен, по-этому не надо разбираться в процессе вычисления. Трудность в чтении тут возникает исключительно из-за неудобства синтаксиса.

anonymous
()
Ответ на: комментарий от balodja

не нужно знать, что обозначает каждый примитив, использованный в коде

нет, без этого, очевидно, не поймёшь что делает код.

А вообще забавно что тебе рассказываешь про простоту понимания на определённом уровне абстракции и ты, в конце концов, понимаешь про абстракцию и предлагаешь погуглить. ты случаем не бот?)

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

> нет, без этого, очевидно, не поймёшь что делает код.

Твоя подружка — яркий пример, что это не так.

А вообще забавно что тебе рассказываешь про простоту понимания на определённом уровне абстракции

Нет, ты не про это изначально рассказывал. Абстракция — это одно, а аналогии из естественного языка — это другое.

и ты, в конце концов, понимаешь про абстракцию и предлагаешь погуглить. ты случаем не бот?)

Всё не так. Тебе следует внимательнее следить за разговором и внимательнее слушать собеседника, если собираешься с ним спорить.

Пора завершить это бесполезие.

balodja ★★★
()
Ответ на: комментарий от balodja

Если так считать, то в любом случае получится, что надо через фильтр прогнать (n + m) элементов и n элементов сложить.

Можно делать это одновременно, прямо во время первого прохода (без временных списков). Под количеством итераций подразумевается общее количество - и проход по исходному списку и по всем временным спискам.

Любой алгоритм будет «эквивалентен двум проходам».

Все варианты, за исключением двух первых, делают n+m итерациий общим числом, т.е. именно один проход без итераций по временным спискам вообще.

Твой вариант

(defun filter/reduce (function initial-value predicate list)
  (funcall (multiple-value-compose
            #'(lambda (xs ys) (cons (fold-left function initial-value xs) ys))
            #'(lambda (list) (partition predicate list)))
           list))

можно включить, но производительность у него - примерно такая как у (2), т.е. никакая :) Хотя читаемость не плохая - тоже вроде 2-ого варинта.

Вариант Miguel ни чем не отличается от (3) (за исключением haskell-специфичного сахара) кроме того что используется правая свёртка - это плохо сказывается на производительности.

quasimoto ★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.