2010-02-01 14 views
5

Aquí es un planteamiento del problema:Diferentes soluciones para la implementación de Clojure problema

definir un procedimiento que dura tres números como argumentos y devuelve la suma de los cuadrados de los dos números más grandes.

La solución es larga,

(defn large [x y] 
(if (> x y) x y)) 

(defn large-3 [x y z] 
(if(> (large x y) z) (large x y) z)) 

(defn small [x y] 
(if (< x y) x y)) 

(defn small-3 [x y z] 
(if (< (small x y) z) (small x y) z)) 

(defn second-largest [x y z] 
    (let [greatest (large-3 x y z) 
    smallest (small-3 x y z)] 
    (first (filter #(and (> greatest %) (< smallest %)) [x y z])))) 

(defn square [a] 
    (* a a) 
) 

(defn sum-of-square [x y z] 
    (+ (square (large-3 x y z)) (square (second-largest x y z)))) 

sólo quería saber qué diferentes maneras/sucintas este problema puede ser resuelto en Clojure.

+0

¿Alguien puede encontrar una solución perezosa? Me encantaría. Tendría que solucionar el problema de alguna manera. –

+0

No puede tener una solución perezosa a un problema que inherentemente requiere la evaluación de todos los datos de entrada, como elegir los dos más pequeños tres números dados lo hacen. Su pregunta me inspiró, sin embargo, para llegar a una versión ligeramente modificada del problema - vea mi respuesta a continuación para el problema modificado y una solución. –

Respuesta

3

(Vea una versión de secuencia del problema junto con una solución perezoso en mi segunda actualización a esta respuesta a continuación.)

(defn square [n] 
    (* n n)) 

;; generalises easily to larger numbers of arguments 
(defn sum-of-larger-squares [x y z] 
    (apply + (map square (take 2 (reverse (sort [x y z])))))) 

;; shorter; generalises easily if you want 
;; 'the sum of the squares of all numbers but n smallest' 
(defn sum-of-larger-squares [x y z] 
    (apply + (map square (drop 1 (sort [x y z]))))) 

Actualización:

Para ampliar sobre los comentarios de la anterior , la generalización straighforward de la primera versión es a esto:

(defn sum-of-larger-squares [n & xs] 
    (apply + (map square (take n (reverse (sort xs)))))) 

la segunda versión generaliza rodeos Arthur a la versión publicada en el ínterin:

(defn sum-of-larger-squares [n & xs] 
    (apply + (map square (drop n (sort xs))))) 

Además, he visto exactamente el mismo problema a resolver en el esquema, posiblemente incluso en SO ... Se incluyó algunas soluciones diversión, como una que calcula el alguna de los tres cuadrados, luego restó el cuadrado más pequeño (que es muy sencillo de expresar con las primitivas del Esquema). Eso es "ineficiente" ya que calcula el cuadrado adicional, pero es ciertamente muy legible. Parece que no puede encontrar el enlace ahora, desafortunadamente.

Actualización 2:

En respuesta al comentario de Arthur Ulfeldt sobre la cuestión, una solución perezosa a un (esperemos divertido) versión diferente del problema. Código en primer lugar, a continuación la explicación:

(use 'clojure.contrib.seq-utils) ; recently renamed to clojure.contrib.seq 

(defn moving-sum-of-smaller-squares [pred n nums] 
    (map first 
     (reductions (fn [[current-sum [x :as current-xs]] y] 
        (if (pred y x) 
         (let [z (peek current-xs)] 
         [(+ current-sum (- (* z z)) (* y y)) 
          (vec (sort-by identity pred (conj (pop current-xs) y)))]) 
         [current-sum 
         current-xs])) 
        (let [initial-xs (vec (sort-by identity pred (take n nums))) 
         initial-sum (reduce + (map #(* % %) initial-xs))] 
        [initial-sum initial-xs]) 
        (drop n nums)))) 

El clojure.contrib.seq-utils (o c.c.seq) lib está ahí para que la función reductions. iterate podría usarse en su lugar, pero no sin cierta complejidad añadida (a menos que uno esté dispuesto a calcular la longitud de la secuencia de números para procesar al comienzo, lo que estaría en desacuerdo con el objetivo de permanecer tan flojo como sea posible).

Explicación con ejemplo de uso:

user> (moving-sum-of-smaller-squares < 2 [9 3 2 1 0 5 3]) 
(90 13 5 1 1 1) 

;; and to prove laziness... 
user> (take 2 (moving-sum-of-smaller-squares < 2 (iterate inc 0))) 
(1 1) 

;; also, 'smaller' means pred-smaller here -- with 
;; a different ordering, a different result is obtained 
user> (take 10 (moving-sum-of-smaller-squares > 2 (iterate inc 0))) 
(1 5 13 25 41 61 85 113 145 181) 

Generalmente, (moving-sum-of-smaller-squares pred n & nums) genera una SEC perezoso de sumas de cuadrados de los n números Pred-más pequeños en fragmentos iniciales cada vez más largas de la SEC original de números, donde 'pred "más pequeño" significa el más pequeño con respecto a la ordenación inducida por el predicado pred. Con pred = >, se calcula la suma de n cuadrados más grandes.

Esta función utiliza el truco que mencioné anteriormente al describir la solución de Esquema que suma tres cuadrados, luego resta el más pequeño, y así puede ajustar la suma en curso por la cantidad correcta sin volver a calcularla en cada paso.

Por otro lado, realiza una gran cantidad de clasificación; Encuentro que realmente no vale la pena tratar de optimizar esta parte, ya que los elementos que se ordenan son siempre n elementos largos y hay un máximo de una operación de clasificación en cada paso (ninguno si la suma no requiere ajuste).

+0

necesita cuadrar cada número antes de sumarlo. podría cambiar + por # (+ (*% 1% 1) (*% 2% 2)) para que se lea más como (aplicar suma de cuadrados ...) –

+0

Me gusta su generalización del problema :) –

+0

Sí, olvidé la escuadra al principio, pero me corrigieron en 15 segundos ... Has sido rápido para atraparme en esta. :-) En cuanto a la función anónima que resume y cuadra sus argumentos, está bien si está cuadrando dos números, pero no se puede usar con 'reduce'. Bueno, si quieres resolver este problema no puede, sí lo hace para una solución a un problema diferente. ;-) –

7

; ¿por qué solo 3? ¿qué hay de N

(defn sum-of-squares [& nums] 
    (reduce + (map #(* % %) (drop 1 (sort nums))))) 

o si desea que "la suma de los mayores dos números:

(defn sum-of-squares [& nums] 
    (reduce + (map #(* % %) (take 2 (reverse (sort nums)))))) 

(take 2 (Reversa (nums ordenar))) La respuesta de fromMichał Marczyk

+0

Por lo general, prefiero reducir para aplicar porque ahorra una gran función de llamada y puede conservar la evaluación diferida (aunque en este caso el (ordena el flojo) –

+0

En realidad, '(aplicar primero [(iterar inc 0)]) 'y' (apply (fn [x & xs] (iterate inc 0)) 'both yield 0 ... Me parece perezoso. :-) Además,' apply' hace una llamada de función en lugar de las funciones múltiples 'reduce' haría. No es que no me guste' reducir' ... por el contrario, me resulta extraordinariamente útil, y para cosas como sumas lo usé indistintamente con 'apply' debido a los hábitos Scheme/Haskell en conflicto. Sin embargo, me resulta difícil preferir que 'apply' donde ambos lo hagan. –

+0

Puede haber diferencias en aplicar y reducir. Considere '(reducir str algunas-cadenas)' y '(aplicar str algunas-cadenas)'. esto último es mejor porque usa un StringBuilder internamente. Cosas como '+' están optimizadas en el caso de dos arcos. No estoy seguro de si las optimizaciones se aplican con 'reduce', pero puede hacer la diferencia aquí. – kotarak

8
(defn foo [& xs] 
    (let [big-xs (take 2 (sort-by - xs))] 
    (reduce + (map * big-xs big-xs)))) 
.
+0

Oh, eso es genial ... Hace que el '(reverse (sort xs))' parezca un poco detallado. Acabo de redescubrir 'sort-by' (para usar en solución a una versión alternativa del problema indicado en mi respuesta) y nuevamente encontré que no es exactamente como' sortBy' de Haskell ... que soy bastante mas acostumbrado a. He estado murmurando maldiciones acerca de esto por un minuto, luego me di cuenta de esto, y entendí el mensaje. +1 por eso. –

Cuestiones relacionadas