2013-05-15 24 views
2

直角三角形我撇「瞭解你一個Haskell」,結果發現,在this page最底部,找到一個三元組(A,B,C)代表一個直角三角形與周邊我指定的方式發現非常高雅 -發現Lisp中

ghci> let rightTriangles' = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2, a+b+c == 24] 

我想知道是否有一種方法可以在Lisp中以類似的方式做到這一點/不需要明確使用循環。這就是我所做的 -

(defun sq (x) (expt x 2)) 

(loop for c from 1 to 10 do 
    (loop for a from 1 to c do 
     (let ((b (- 24 a c))) 
      (if (= (sq c) (+ (sq a) (sq b))) 
       (format t "~a, ~a, ~a~%" a b c))))) 

,但它顯然不看像你一樣Haskell的版本,它還會打印出解決方案的兩倍((6,8,10)和(8,6,10) ),因爲a從1變爲c

+1

那麼,Haskell代碼不會像你這樣計算b。它實際上循環c從1到10,b從1到c,以及從1到b。 (OK,它不會循環,它會生成序列。)這就解釋了爲什麼Haskell只打印一個解決方案,而您的LISP生成兩個解決方案。 –

+0

在LISP中沒有用於生成序列的內建函數;你可以使用尾遞歸來實現,但使用循環更容易閱讀,並且可能效率更高。你可以用一個函數模擬Haskell *(defun序列(ab)(循環爲我從a到b收集i))* –

+0

如果你在外層循環中添加一個名爲outer的'並且具有真實條件'(從外部返回),這將使它在邏輯上正確。但醜陋。 –

回答

4

自從我在CL中爲集合論寫了一個玩具庫之後,我無法抗拒給這個嘗試。 參見http://repo.or.cz/w/flub.git/blob/HEAD:/bachelor-cs/set-theory.lisp

(use-package '(:alexandria :bachelor-cs.set-theory)) 

(defun triangles (h) 
    (let ((range (iota h :start 1))) 
    (∩ (× (× range range) range) 
     (lambda (triangle) 
     (destructuring-bind ((a b) c) triangle 
      (>= c b a)))))) 

(defun perimeter (n) 
    (lambda (triangle) 
    (destructuring-bind ((a b) c) triangle 
     (= n (+ a b c))))) 

(defun right-triangles (triangle) 
    (destructuring-bind ((a b) c) triangle 
    (= (* c c) (+ (* a a) (* b b))))) 

(∩ (∩ (triangles 10) (perimeter 24)) #'right-triangles) ↦ (((6 8) 10)) 

在此醜陋位是「,因爲該組操作中((A-B)C)的三角形的表示被定義爲二進制。所以,現在我得到了一個很好的解決方法:定義可變參數列表的集合操作。

乾杯, 最大

編輯:我做了一組操作n元。現在可以這樣寫:

(∩ (× (iota 10 :start 1) (iota 10 :start 1) (iota 10 :start 1)) 
    (lambda (tri) 
    (destructuring-bind (a b c) tri 
     (>= c b a))) 
    (lambda (tri) 
    (destructuring-bind (a b c) tri 
     (= 24 (+ a b c)))) 
    (lambda (tri) 
    (destructuring-bind (a b c) tri 
     (= (+ (* a a) (* b b)) (* c c))))) 

如果添加一個簡單的宏→

(defmacro → (args &rest body) 
    (let ((g!element (gensym "element"))) 
    `(lambda (,g!element) 
     (destructuring-bind ,args ,g!element 
     ,@body)))) 

您在可讀性恕我直言方面都非常接近Haskell的版本:

(∩ (× (iota 10 :start 1) (iota 10 :start 1) (iota 10 :start 1)) 
    (→ (a b c) (>= c b a)) 
    (→ (a b c) (= 24 (+ a b c))) 
    (→ (a b c) (= (+ (* a a) (* b b)) (* c c)))) 
+1

使用熟悉的g很好的觸摸!語法與顯式gensym,使defmacro!不必提及。 –

+0

heh。是的,爆炸的語法是我使用defmacro時留下的,我發現它最終是完全抽象的。我認爲它沒有很好地處理嵌套的lambda列表。有一些相當毛茸茸的病例,但我不記得確切。應該是可以修復的。 – max

1

您可以使用dotimes而不是循環來使循環不那麼明顯。

(defun right-triangles (circ) 
     (dotimes (c (/ circ 2)) 
     (dotimes (b c) 
      (dotimes (a b) 
       (when (and (= circ (+ a b c)) 
          (= (* c c) (+ (* a a) (* b b)))) 
        (format t "~a, ~a, ~a~%" a b c)))))) 

(dotimes (i n))從0循環in-1abc都會是不同的。因此不會找到等腰三角形。然而,由於在所有邊長都是有理數的情況下不存在等腰直角三角形,所以這不是問題。

2

你可以使用一個(遞歸)宏以訪問列表理解:

(defmacro lcomp-h (var domain condition varl) 
    (if (= 1 (length var)) 
    `(loop for ,(car var) from ,(caar domain) to ,(cadar domain) 
      when ,condition 
      collect (list ,@varl)) 
     `(loop for ,(car var) from ,(caar domain) to ,(cadar domain) append 
     (lcomp-h ,(cdr var) ,(cdr domain) ,condition ,varl)))) 

(defmacro lcomp (var domain condition) 
    `(lcomp-h ,var ,domain ,condition ,var)) 

現在你的語法如下:

CL-USER> (lcomp (a b c) ((1 10) (a 10) (1 10)) (= (* c c) (+ (* a a) (* b b)))) 

和LISP得到:

((3 4 5) (6 8 10)) 

我花了一段時間,肯定沒有完成,但似乎工作。

1

以下是使用Screamer包中的基於約束的DSL的解決方案(Quicklisp可安裝):

CL-USER> 
(in-package :screamer) 
#<Package "SCREAMER"> 
SCREAMER> 
(let* ((c (an-integer-betweenv 1 10)) 
     (b (an-integer-belowv c)) 
     (a (an-integer-belowv b))) 
    (assert! (=v (*v c c) 
       (+v (*v a a) 
        (*v b b)))) 
    (assert! (=v (+v a b c) 
       24)) 
    (one-value 
    (solution (list a b c) 
       (static-ordering #'linear-force)))) 
(6 8 10)