Common Lisp備忘録

忘れた記憶を取り戻す

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 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でそのまま割ればよかった。