Common Lisp備忘録

忘れた記憶を取り戻す

ABC 005b.lisp

ACしたコード

(let* ((n (read))
       (lst (loop repeat n
                  collect (read))))
  (format t "~A~%" (apply #'min lst)))

integerを要素とするlistの中で最小のものを返すのは
(apply #'min
と書ける。ただのminだと、

Break 1 [3]> (min 9 2 4 2 5)
2

みたいに、直接引数として複数の整数を渡さないといけない。

ABC 004b.lisp

ACしたコード その1

(let* ((r1 (read-line))
       (r2 (read-line))
       (r3 (read-line))
       (r4 (read-line))
       (lst (list r1 r2 r3 r4)))

  (format t "~{~A~%~}" (reverse (mapcar #'reverse lst))))

文字列を要素とするリストの中身それぞれに対してreverseするときは
(mapcar #'reverse
する。

ACしたコード その2

(let ((matrix (loop repeat 4 collect (read-line))))
  (format t "~{~A~%~}" (reverse (mapcar #'reverse matrix))))

標準入力から与えられる文字列を改行で区切って要素とするリストを生成するときに
(loop repeat 行数 collect (read-line)
と書ける。

ABC 003b.lisp

ACしたコード その1

(let ((a (concatenate 'list (read-line)))
      (b (concatenate 'list (read-line)))
      (ans '())
      (flag t))

  (mapcar #'(lambda (n m)
              (if (char= n m)
                nil
                (cond ((char= n #\@) (push m ans))
                      ((char= m #\@) (push n ans))
                      (t (push #\b ans)))))
          a b)

  (mapcar #'(lambda (q)
              (if (find q (concatenate 'list "atcoder"))
                nil
                (setq flag nil)))
          ans)

文字qが文字列"atcoder"の中に含まれるかどうかの判定にfindが使える。

[1]> (find #\a (concatenate 'list "atcoder"))
#\a
[2]> (find #\z (concatenate 'list "atcoder"))
NIL

ACしたコード その2

(let ((a (concatenate 'list (read-line)))
      (b (concatenate 'list (read-line))))

  (defun create-chklst (lst1 lst2)
    (let ((lst '()))
      (mapcar #'(lambda (n m)
                  (if (char= n m)
                    nil
                    (cond ((char= n #\@) (push m lst))
                          ((char= m #\@) (push n lst))
                          (t (push #\b lst)))))
              lst1 lst2)
      lst))

  ;;; (format t "~A~%" (create-chklst a b))

  (defun check-lst-p (lst1)
    (not (member 'nil
                 (mapcar #'(lambda (c)
                             (if (find c (concatenate 'list "atcoder"))
                               t
                               nil))
                         lst1))))

  ;;; (format t "~S~%" (check-lst-p (create-chklst a b)))


  (format t "~A~%"
          (if (check-lst-p (create-chklst a b))
            "You can win"
            "You will lose")))

副作用の扱い(ansとflag)が手続き型っぽかったので関数型っぽい書き方にした。
標準入力から与えられた二つのリストの同じインデックスの要素の片方のみが@の場合、もう一つのリストの同じインデックスの文字を要素とするリストを生成し、それをチェックリストとした。

booleanを要素とするリストにNILが含まれているかどうかの判定

Break 6 [7]> (member 'nil '(T T T nil))
(NIL)
Break 6 [7]> (member 'nil '(T T T))
NIL

のように、member関数を使えば、
すべての要素がTの場合にNIL
一つでもNILを含んでいればリスト(真偽値はT)
を返す処理が書ける。しかし、このままではTとNILが逆転しているため、member関数の前にnotをつけた。

ACしたコード その3

(let ((a (concatenate 'list (read-line)))
      (b (concatenate 'list (read-line))))

  (defun create-chklst (lst1 lst2)
    (let ((lst '()))
      (mapcar #'(lambda (n m)
                  (if (char= n m)
                    nil
                    (cond ((char= n #\@) (push m lst))
                          ((char= m #\@) (push n lst))
                          (t (push #\b lst)))))
              lst1 lst2)
      lst))

  ;;; (format t "~A~%" (create-chklst a b))

  (defun check-lst-p (lst1)
    (every #'identity
           (mapcar #'(lambda (c)
                       (if (find c (concatenate 'list "atcoder"))
                         t
                         nil))
                   lst1)))

  ;;; (format t "~S~%" (check-lst-p (create-chklst a b)))


  (format t "~A~%"
          (if (check-lst-p (create-chklst a b))
            "You can win"
            "You will lose")))

(not (member 'nil
の代わりに
(every #'identity
した。こっちの方がロジックがわかりやすい。

ABC 003a.lisp

ACしたコード

(let ((x (read)))
  (format t "~A~%" (* (/ (reduce #'+ (loop for i from 1 to x collect i)) x) 10000)))

LOOPマクロ

(loop for i from 1 to 10 collect i)
(1 2 3 4 5 6 7 8 9 10)

INTEGERを要素とするリストの和

Break 3 [4]> (reduce #'+ (loop for i from 1 to 10 collect i))
55

ABC 002b.lisp

標準入力した文字列を、文字を要素とするリストに変換

(let* ((lst (concatenate 'list (read-line)))
       (lst2 (mapcar #'type-of lst)))

  (format t "~A~%" lst)
  (format t "~S~%" lst)
  (format t "~A~%" lst2)
  (format t "~S~%" lst2))

とすると

 » cl 002b.lisp
hello
(h e l l o)
(#\h #\e #\l #\l #\o)
(STANDARD-CHAR STANDARD-CHAR STANDARD-CHAR STANDARD-CHAR STANDARD-CHAR)
(STANDARD-CHAR STANDARD-CHAR STANDARD-CHAR STANDARD-CHAR STANDARD-CHAR)

となる。ただし、helloは標準入力であり、clはalias cl=clispである。
なお、FORMAT関数の~aはaesthetic、~sはstandardであり、コンピュータに優しい表記だとCommon Lispの文字は#\aなどの表記になる。

ACしたソースコード

上記の仕様確認用のコードを削除し、以下のように書き直した。

(format t "~A~%" (remove-if #'(lambda (x) (find x "aiueo")) (read-line)))

find

[1]> (find #\a "aiueo")
#\a
[2]> (concatenate 'list "aiueo")
(#\a #\i #\u #\e #\o)
[3]> (find #\a (concatenate 'list "aiueo"))
#\a

のように

(find アイテム シーケンス)

で、シーケンスの要素を返す。

remove-ifとlambdaと高階関数

関数を引数として受け取る関数を高階関数という。

[9]> (remove-if #'(lambda (x)
               (find x "aiueo"))
           "hello")
hll

ABC 001b.lisp

ABC 001bでACしたソースコードが以下の通り。

(let* ((m (read))
       (vv (cond ((< m 100) "00")
                 ((<= m 5000) (format nil "~2,'0d" (* m (expt 10 -2))))
                 ((<= m 30000) (+ (* m (expt 10 -3)) 50))
                 ((<= m 70000) (+ (/ (- (/ m 1000) 30) 5) 80))
                 (t 89))))

  (format t "~A~%" vv))

躓いたところ、忘れてたところを列挙する。Clispの出力結果をコピペしてるため、説明に不要な文字が含まれるが容赦されたし。

FORMAT

Break 5 [18]> (format nil "~2,'0d" 2)
"02"

のように、FORMAT関数の中で制御文字列を ~2,'0d とすると、フォーマット引数が1文字(ただしinteger)の場合に、頭に0を入れて文字列を生成する。
2が最低文字数、,'0でパディングする文字を指定し、~dで整数を10進法で出力する。(dはdecimalのd)
ちなみにFORMAT関数は

(format 出力先 制御文字列 フォーマット引数)

という並びになっており、制御文字列はリテラル文字列と指示子からなる。
~a ~d ~f
とかが指示子。
: @ 1 2 3
とかが前置パラメータ。
用語は、『実践Common Lisp』に準じた。

Power

Break 5 [18]> (expt 10 -2)
1/100

数字周りの型

(type-of (* 2000 (expt 10 -2)))
(INTEGER 0 288230376151711743)

floatでもratio(分数)でもなく、integer

Break 5 [18]> (type-of 1/3)
RATIO
Break 5 [18]> (type-of 0.1)
SINGLE-FLOAT

\の返り値がINTEGER

Break 5 [18]> (type-of (/ 2000 1000))
(INTEGER 0 288230376151711743)
Break 5 [18]> (/ 2000 1000)
2

mをkmに直すのに、10**(-3)を掛けたりせずに、1000でそのまま割ればよかった。