Schemeで二重のループ

昨日の記事のコメントで do を使うループを教えてもらった。ありがとうございます。

早速練習してみるよ。
詳しい情報はこのへんから。

 cf. 4.8 繰り返し – Gauche ユーザリファレンス

まずは単純に一重のループから。

(define loop
  (lambda (lis)
    (do ((l lis (cdr l)))
        ((null? l))
        (print (car l)))))

(loop '(1 2 3))

do 構文は次のような体裁をしている。

(do ((変数 初期値 ステップ))
    (終了条件)
    本体)

変数はループ内で使われる変数で、初期値で初期化される。ステップは次のループに移るときに変数に入る値だ。上の例では、l が変数、lis が初期値、(cdr l) がステップに当たる。終了条件は、まあそのまんま。リストが空になったら終了。で、1回のループごとに本体が評価される、と。
さてやってみよう。

takatoh@nightschool $ gosh loop-do.scm
1
2
3

いいね。イケてるね。
じゃあ、二重のループはというと、本体の部分にもうひとつ do を使えばいい。

(define loop
  (lambda (lis)
    (do ((l1 lis (cdr l1)))
        ((null? l1))
        (do ((l2 lis (cdr l2)))
            ((null? l2))
            (print (list (car l1) (car l2)))))))

(loop '(1 2 3))

二重にしたので、リストにして出力している。

takatoh@nightschool $ gosh loop-do2.scm
(1 1)
(1 2)
(1 3)
(2 1)
(2 2)
(2 3)
(3 1)
(3 2)
(3 3)

うまくいった。
さて、ここから本題。昨日やったように、ループの中で出力するんじゃなくて結果をリストとして返したい。斉藤さんの例では、result という変数を用意して、ループ1回ごとに追加した結果を代入している。
そうか、代入を使えばいいのか。というか Scheme でも代入を使うのか。

(define loop
  (lambda (lis)
    (let ((result '()))
      (do ((l1 lis (cdr l1)))
          ((null? l1))
          (do ((l2 lis (cdr l2)))
              ((null? l2))
              (set! result (cons (list (car l1) (car l2)) result))))
              (reverse result))))

(for-each print (loop '(1 2 3)))

(set! result …) のところが代入をしているところ。cons を使っているからリストが逆順になっているので、最後に (reverse result) している。
実行してみよう。

takatoh@nightschool $ gosh loop-do3.scm
(1 1)
(1 2)
(1 3)
(2 1)
(2 2)
(2 3)
(3 1)
(3 2)
(3 3)

よし、うまくいった。改めてコメントをくれた斉藤さんに感謝を。

おまけ

調子に乗って三重のループ。

(define loop
  (lambda (lis)
    (let ((result '()))
      (do ((l1 lis (cdr l1)))
          ((null? l1))
          (do ((l2 lis (cdr l2)))
              ((null? l2))
              (do ((l3 lis (cdr l3)))
                  ((null? l3))
                  (set! result (cons (list (car l1) (car l2) (car l3)) result)))))
                  (reverse result))))

(for-each print (loop '(1 2 3)))
takatoh@nightschool $ gosh loop-triple.scm
(1 1 1)
(1 1 2)
(1 1 3)
(1 2 1)
(1 2 2)
(1 2 3)
(1 3 1)
(1 3 2)
(1 3 3)
(2 1 1)
(2 1 2)
(2 1 3)
(2 2 1)
(2 2 2)
(2 2 3)
(2 3 1)
(2 3 2)
(2 3 3)
(3 1 1)
(3 1 2)
(3 1 3)
(3 2 1)
(3 2 2)
(3 2 3)
(3 3 1)
(3 3 2)
(3 3 3)

「Schemeで二重のループ」への3件のフィードバック

  1. こういう書き方をすれば代入を排除することも出来ますね。

    (define (loop-double lis)
    (do ((e1 lis (cdr e1))
    (r1 ‘() (do ((e2 lis (cdr e2))
    (r2 r1 (cons (list (car e1) (car e2)) r2)))
    ((null? e2) r2))))
    ((null? e1) (reverse r1))))

    1. コメントありがとうございます。
      自分でも書いて確かめてみました。でも、ちょっと難しくて理解するのに時間がかかりました。set! を使ったほうが分かりやすいですね。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください