2017-09-26 3 views
1

Encore très nouveau à Clojure et à la programmation en général alors pardonnez la question stupide.Comment renvoyer une séquence paresseuse d'une boucle avec un conditionnel dans Clojure?

Le problème est:

Recherche n et k tel que la somme des nombres jusqu'à n (exclusive) est égale à la somme des nombres de n + 1 à k (y compris).

Ma solution (qui fonctionne très bien) est de définir les fonctions suivantes:

(defn addd [x] (/ (* x (+ x 1)) 2)) 
(defn sum-to-n [n] (addd(- n 1))) 
(defn sum-to-k [n=1 k=4] (- (addd k) (addd n))) 
(defn is-right[n k] 
    (= (addd (- n 1)) (sum-to-k n k))) 

Et puis exécutez la boucle suivante:

(loop [n 1 k 2] 
    (cond 
    (is-right n k) [n k] 
    (> (sum-to-k n k) (sum-to-n n))(recur (inc n) k) 
    :else (recur n (inc k)))) 

Ce que les retours d'une réponse mais si je mets manuellement n et k je peux avoir des valeurs différentes. Cependant, je voudrais définir une fonction qui retourne une séquence paresseuse de toutes les valeurs de sorte que:

(= [6 8] (take 1 make-seq)) 

Comment puis-je faire aussi efficacement que possible? J'ai essayé plusieurs choses mais je n'ai pas eu beaucoup de chance.

Merci

: Modifier:

Je pense que je suis venu avec une meilleure façon de le faire, mais son retour "devrait laisser être un vecteur. Clojure docs ne sont pas beaucoup d'aide ...

Heres le nouveau code:

(defn calc-n [n k] 
(inc (+ (* 2 k) (* 3 n)))) 

(defn calc-k [n k] 
(inc (+ (* 3 k)(* 4 n)))) 

(defn f 
    (let [n 4 k 6] 
     (recur (calc-n n k) (calc-k n k)))) 

(take 4 (f)) 
+0

Dans votre édition, il vous manque le vecteur d'argument pour 'f'. – madstap

Répondre

-1

Si vous n'avez pas envie de "rouler le vôtre", voici une autre solution. J'ai également nettoyé un peu l'algorithme en le renommant/en le reformatant.

La principale différence est que vous traitez votre boucle-récurr comme une boucle infinie à l'intérieur du formulaire t/lazy-gen. Lorsque vous trouvez une valeur que vous souhaitez conserver, vous utilisez l'expression t/yield pour créer une séquence paresseuse de sorties. Cette structure est la version Clojure d'une fonction de générateur .

(ns tst.demo.core 
    (:use tupelo.test) 
    (:require [tupelo.core :as t])) 

(defn integrate-to [x] 
    (/ (* x (+ x 1)) 2)) 
(defn sum-to-n [n] 
    (integrate-to (- n 1))) 
(defn sum-n-to-k [n k] 
    (- (integrate-to k) (integrate-to n))) 
(defn sums-match[n k] 
    (= (sum-to-n n) (sum-n-to-k n k))) 

(defn recur-gen [] 
    (t/lazy-gen 
    (loop [n 1 k 2] 
     (when (sums-match n k) 
     (t/yield [n k])) 
     (if (< (sum-to-n n) (sum-n-to-k n k)) 
     (recur (inc n) k) 
     (recur n (inc k)))))) 

(t/spyx (take 5 (recur-gen))) 

Vous trouverez tous les détails in the Tupelo Library.

2

Oui, vous pouvez créer un paresseux-seq, de sorte que la prochaine itération prendra la suite de l'itération précédente. Voici ma suggestion:

(defn cal [n k] 
    (loop [n n k k] 
    (cond 
     (is-right n k) [n k] 
     (> (sum-to-k n k) (sum-to-n n))(recur (inc n) k) 
     :else (recur n (inc k))))) 

(defn make-seq [n k] 
    (if-let [[n1 k1] (cal n k)] 
     (cons [n1 k1] 
      (lazy-seq (make-seq (inc n1) (inc k1)))))) 

(take 5 (make-seq 1 2)) 
;;=> ([6 8] [35 49] [204 288] [1189 1681] [6930 9800]) 
+1

Pourquoi utilisez-vous 'if-let'? Il semble qu'un «laissez» soit tout ce qui est requis. –

+2

Un 'if-let' permet une séquence qui se termine, si une implémentation future de' cal' peut déterminer que plus aucune réponse ne peut jamais être trouvée. Comparez à, par exemple, la définition de 'take', qui ressemble à' '' '(defn take [n xs] (paresseux-seq (si (zéro? N)(), (if-let [coll (seq xs)] (contre (premier coll) (prendre (dec n (reste coll)))))))) '. – amalloy

+0

oui comme amalloy dit, si-let est nécessaire pour terminer le paresseux-seq –

0

Cette première fonction a probablement un meilleur nom de maths, mais je ne connais pas très bien les maths. J'utiliserais inc (incrément) au lieu de (+ ,,, 1), mais c'est juste une préférence personnelle.

(defn addd [x] 
    (/ (* x (inc x)) 2)) 

Je vais légèrement nettoyer ici l'espacement et utiliser la fonction dec (décrément).

(defn sum-to-n [n] 
    (addd (dec n))) 

(defn sum-n-to-k [n k] 
    (- (addd k) (addd n))) 

Dans certaines langues prédicats, les fonctions qui renvoient booléens, ont des noms comme is-odd ou is-whatever.En clojure ils sont généralement appelés odd? ou whatever?. Le point d'interrogation n'est pas de la syntaxe, c'est juste une partie du nom.

(defn matching-sums? [n k] 
    (= (addd (dec n)) (sum-n-to-k n k))) 

La boucle forme spéciale est un peu comme une fonction anonyme pour RECUR pour revenir en arrière à. S'il n'y a pas de forme de boucle, recur retourne à la fonction englobante. Aussi, ne sais pas comment appeler cela, donc je vais l'appeler f.

(defn f [n k] 
    (cond 
    (matching-sums? n k) [n k] 
    (> (sum-n-to-k n k) (sum-to-n n)) (recur (inc n) k) 
    :else (recur n (inc k)))) 

(comment 

    (f 1 2) ;=> [6 8] 

    (f 7 9) ;=> [35 49] 

) 

Maintenant, pour votre question actuelle. Comment faire une séquence paresseuse. Vous pouvez utiliser la macro lazy-seq, comme dans la réponse de minhtuannguyen, mais il existe un moyen plus facile et plus haut niveau. Utilisez la fonction iterate. iterate prend une fonction et une valeur et renvoie une suite infinie de la valeur suivie en appelant la fonction de la valeur, puis en appelant la fonction sur que valeur etc.

(defn make-seq [init] 
    (iterate (fn [n-and-k] 
      (let [n (first n-and-k) 
        k (second n-and-k)] 
       (f (inc n) (inc k)))) 
      init)) 

(comment 

    (take 4 (make-seq [1 2])) ;=> ([1 2] [6 8] [35 49] [204 288]) 
) 

qui peut être un peu simplifié par utilisant la déstructuration dans l'argument-vecteur de la fonction anonyme.

(defn make-seq [init] 
    (iterate (fn [[n k]] 
      (f (inc n) (inc k))) 
      init)) 

Edit: A propos des calculs répétés f. En enregistrant le résultat des calculs à l'aide d'un let, vous pouvez éviter de calculer addd plusieurs fois pour chaque numéro.

(defn f [n k] 
    (let [to-n (sum-to-n n) 
     n-to-k (sum-n-to-k n k)] 
    (cond 
     (= to-n n-to-k) [n k] 
     (> n-to-k to-n) (recur (inc n) k) 
     :else (recur n (inc k))))) 
+0

C'est ce que je voulais, mais n'est-il pas tout à fait inefficace? Si je comprends bien, vous répéterez beaucoup de travail chaque fois que vous appelez f. Y at-il un moyen de définir la valeur suivante de n et k à leurs valeurs précédentes + 1? – armincerf

+0

@armincerf 'itérer' en lui-même n'est pas inefficace, dans l'exemple la fonction anon sera appelée 4 fois, à chaque fois avec la valeur précédente. La fonction 'f', cependant, répète effectivement certains calculs. – madstap

+0

@armincerf J'ai modifié ma réponse. C'est ce que vous vouliez dire? – madstap

1

juste génèrent seq paresseux de candidatess avec iterate puis les filtrer devraient probablement être ce dont vous avez besoin:

(def pairs 
    (->> [1 2] 
     (iterate (fn [[n k]] 
        (if (< (sum-to-n n) (sum-n-to-k n k)) 
        [(inc n) k] 
        [n (inc k)]))) 
     (filter (partial apply is-right)))) 

user> (take 5 pairs) 
;;=> ([6 8] [35 49] [204 288] [1189 1681] [6930 9800]) 

sémantiquement il est comme générer manuellement un paresseux-seq, et devrait être aussi efficace , mais celui-ci est probablement plus idiomatique