2014-09-06 2 views
1

Я играл вокруг и пытается создать свою собственную реализацию сокращений, до сих пор у меня есть это, который работает с этим данные испытаний:работы с переменным числом аргументов

((fn [func & args] 
    (reduce (fn [acc item] 
      (conj acc (func (last acc) item)) 
      )[(first args)] (first (rest args)))) * 2 [3 4 5] 

Что мне не нравится то, как я отделяя args.

(first args), что я бы ожидать, то есть 2, но (rest args) является ([3 4 5]) и поэтому я получаю остаток, как этот (first (rest args)), который мне не нравится.

Я пропустил какой-то трюк, который облегчает работу с переменными аргументами?

+1

Примечание вы можете использовать 'second':' (второй [3 4 5]) '' является 4'. – bfontaine

+0

Вы можете найти интересный [этот блог] (http://blog.jayfields.com/2010/07/clojure-destructuring.html). – Mark

ответ

3

Аргументы Variadic предназначены только для получения неопределенного количества аргументов в списке, поэтому здесь можно применить все операции списка/деструктуризации.

Например:

(let [[fst & rst] a-list] 
    ; fst is the first element 
    ; rst is the rest 
) 

Это более читабельным, чем:

(let [fst (first a-list) 
     rst (rest a-list)] 
    ; ... 
) 

Вы можете пойти дальше, чтобы получить первый и второй элементы списка (если он есть> 1 элементы) в одна строка:

(let [fst snd & rst] 
    ; ... 
) 

Первоначально я изначально неправильно понял ваш вопрос и подумал, что вы пытаетесь переопределить reduce функция. Вот пример реализации я написал для этого ответа, который does't использовать first или rest:

(defn myreduce 
    ;; here we accept the form with no initial value 
    ;; like in (myreduce * [2 3 4 5]), which is equivalent 
    ;; to (myreduce * 2 [3 4 5]). Notice how we use destructuring 
    ;; to get the first/rest of the list passed as a second 
    ;; argument 
    ([op [fst & rst]] (myreduce op fst rst)) 
    ;; we take an operator (function), accumulator and list of elements 
    ([op acc els] 
    ;; no elements? give the accumulator back 
    (if (empty? els) 
     acc 
     ;; all the function's logic is in here 
     ;; we're destructuring els to get its first (el) and rest (els) 
     (let [[el & els] els] 
     ;; then apply again the function on the same operator, 
     ;; using (op acc el) as the new accumulator, and the 
     ;; rest of the previous elements list as the new 
     ;; elements list 
     (recur op (op acc el) els))))) 

Я надеюсь, что это поможет вам увидеть, как работать со списком деструктуризации, который, вероятно, что вы хотите в вашей функции. Вот relevant blog post на эту тему.

1

Уборка вашей функции.

Как @bfontaine комментировал, вы можете использовать (second args) вместо (first (rest args)):

(defn reductions [func & args] 
    (reduce 
    (fn [acc item] (conj acc (func (last acc) item))) 
    [(first args)] 
    (second args))) 

Это использует

  • func
  • (first args)
  • (second args)

... но игнорирует остальную часть args.

Таким образом, мы можем использовать деструктурирующие назвать первый и второй элементы args - init и coll кажется подходящим - давая

(defn reductions [func & [init coll & _]] 
    (reduce 
    (fn [acc item] (conj acc (func (last acc) item))) 
    [init] 
    coll)) 

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

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

(defn reductions [func & [init coll]] ...) 

... а затем

(defn reductions [func init coll] ...) 

... - прямолинейная функция трех аргументов.

Работа с основными проблемами.

Ваша функция имеет две проблемы:

  • медленность
  • отсутствие лени.

Медлительность

мигающий красный свет в этой функции является использование last в

(fn [acc item] (conj acc (func (last acc) item))) 

Это сканирует всю acc каждый раз, когда она называется, даже если acc является вектор. Таким образом, это reductions занимает время пропорционально квадратных длины coll: безнадежно медленно для длинных последовательностей.

Простым решением является замена (last acc) на (acc (dec (count acc))), который принимает эффективное постоянное время.

Отсутствие лени

Мы еще не можем использовать то, что лениво производит функцию. Например, было бы неплохо, чтобы инкапсулировать последовательность факториалов так:

(def factorials (reductions * 1N (next (range))))) 

с вашим reductions, это определение никогда не возвращается.

Вы должны полностью переделать свою функцию, чтобы сделать ее ленивой. Давайте изменим стандартную -lazy - reductions использовать деструктурирующие:

(defn reductions [f init coll] 
    (cons 
    init 
    (lazy-seq 
     (when-let [[x & xs] (seq coll)] 
     (reductions f (f init x) xs))))) 

Теперь мы можем определить

(def factorials (reductions * 1N (next (range)))) 

Тогда, например,

(take 10 factorials) 
;(1N 1N 2N 6N 24N 120N 720N 5040N 40320N 362880N) 

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

(defn reductions [f init coll] 
    (let [answer (lazy-seq (reductions f init coll))] 
    (cons init (map f answer coll)))) 

Но это содержит скрытую рекурсию (скрытый от меня, по крайней мере):

(nth (reductions * 1N (next (range))) 10000) 
;StackOverflowError ... 
Смежные вопросы