【Common Lispで遊ぼう】
書き換えながら学ぶ プログラミング入門

関連記事

1. はじめる前に

コードを書いて、動かして、書き換える。
それだけで、だんだんわかってきます。

VS Code と SBCL と Alive 拡張を使って、まずは動かしてみましょう。

main.lisp というファイルを開いて、Alive の REPL を起動しておきましょう。

2. 計算のやり方を決める(defun)

2.1. やってみよう

main.lisp にこのコードを書いてください。

(defun sum3 (a b c)
  (+ a b c))Code language: Lisp (lisp)
  • sum(サム):合計

書いたら Alt + Shift + L を押します。
これで REPL にコードが読み込まれます。

次に REPL にこれを打ち込んでみましょう。

(sum3 1 2 3)Code language: Lisp (lisp)

6 が返ってきましたか?
続けて試してみましょう。

(sum3 10 20 30)
(sum3 100 200 300)Code language: Lisp (lisp)

2.2. 書き換えてみよう

次は、+* に変えて、名前もmultiply3に変えてみます。

(defun multiply3 (a b c)
  (* a b c))Code language: Lisp (lisp)
  • multiply(マルチプライ):掛ける、掛け算する

書き換えたら Alt + Shift + L でもう一度読み込んで、REPL で試します。

(multiply3 2 3 4)
(multiply3 5 5 5)Code language: Lisp (lisp)

受け取る数をもっと増やしてみましょう。

(defun multiply9 (a b c d e f g h i)
  (* a b c d e f g h i))Code language: Lisp (lisp)
(multiply9 1 2 3 4 5 6 7 8 9)Code language: Lisp (lisp)

何が返ってきましたか?

2.3. ここで覚えたこと

defun(デファン)は、「計算のやり方を決める」命令です。
define function(ディファイン・ファンクション)の略です。

  • define(ディファイン):決める
  • function(ファンクション):数値や文字を受け取って、結果を返す仕組み

(a b c) の部分は受け取る値の名前で、引数(ひきすう、パラメータ)といいます。
受け取った値を使って、(+ a b c) のように、計算します。

関数の名前は自由につけていい

キーワードである defun と違って、sum3multiply3 の部分、あるいは a b c などの名前は自分で決められます。
たとえば、mittsu-tasu でも keisan でも動きます。

ただ、短い英単語を使うとかっこよく見えます。
辞書で「合計」を調べると sum(サム)、「面積」なら area(エリア)など、気に入った名前を見つけて使ってみましょう。

この章で出てきた英単語

コード読み方意味
defunデファン計算のやり方を決める
+プラス足し算(plus)
*アスタリスク掛け算(multiply)

3. 引き算と割り算

3.1. やってみよう

(defun diff (a b)
  (- a b))Code language: Lisp (lisp)
  • diff(ディフ):差(difference)の略
(diff 10 3)
(diff 100 45)Code language: Lisp (lisp)

3.2. 書き換えてみよう——切り捨て割り算

(defun half (n)
  (floor n 2))Code language: Lisp (lisp)
  • half(ハーフ):半分
(half 7)
(half 10)
(half 99)Code language: Lisp (lisp)

2 の部分を 34 に変えるとどうなりますか?

3.3. 大きい方・小さい方

(defun biggest (a b c)
  (max a b c))Code language: Lisp (lisp)
  • biggest(ビゲスト):いちばん大きい
(biggest 5 12 8)
(biggest 100 3 77)Code language: Lisp (lisp)

maxmin に変えてみましょう。

(defun smallest (a b c)
  (min a b c))Code language: Lisp (lisp)
  • smallest(スモーレスト):いちばん小さい
(smallest 5 12 8)Code language: Lisp (lisp)

3.4. ここで覚えたこと

floor は「床(ゆか)」という意味で、小数点以下を切り捨てます。
/ を使うと分数が返ることがあるので、整数のまま割るときは floor を使います。

コード中の ; より後ろはコメントです。
実行されないので、メモを書く場所として使えます。

コード読み方意味
-マイナス引き算(minus)
floor a bフロアa を b で割って小数点以下を切り捨て
maxマックス最大値(maximum)
minミン最小値(minimum)
;セミコロンコメント(メモ)

4. 途中の結果に名前をつける(let*)

4.1. やってみよう

(defun two-step (n)
  (let* ((step1 (* n 2))
         (step2 (+ step1 10)))
    step2))Code language: Lisp (lisp)
  • step(ステップ):段階、手順
(two-step 5)
(two-step 100)Code language: Lisp (lisp)

let は、「縦かける横を『答え(kotae)』とする」という意味です。
step1n * 2 の結果を入れて、次の行の step2 でそれを使っています。
step2 の計算式を変えてみましょう。

4.2. ここで覚えたこと

let*(レット・スター)は途中の計算結果に名前をつける書き方です。
上から順番に計算され、前の名前を次の行で使えます。

(let* ((名前11)
       (名前22))
  結果として返す式)Code language: Lisp (lisp)
コード読み方意味
let*レット・スター途中の結果に名前をつける

5. 入力と出力(read princ)

5.1. やってみよう

main.lisp にこのまま書いてください。

(defun sum3 (a b c)
  (+ a b c))

(defun main ()
  (let* ((a (read))
         (b (read))
         (c (read)))
    (princ (sum3 a b c))
    (terpri)))Code language: Lisp (lisp)

これをREPLで実行します。

(main)

入力待ちになるので、
3 4 5 と入力して Enter を押すと 12 が返ってきます。

5.2. 文字列を読む

(defun echo ()
  (let* ((s (read-line)))
    (princ s)
    (terpri)))

(echo)Code language: Lisp (lisp)
  • echo(エコー):やまびこ。入力をそのまま返すことを「エコーする」という

何か文字を入力してみましょう。
そのまま返ってきましたか?

5.3. ここで覚えたこと

read(リード)はキーボードから数値を1つ受け取ります。
スペースで区切れば複数回呼んで順番に読めます。
read-line(リード・ライン)は、1行まるごと文字列として受け取ります。
princ(プリンク)は値を画面に表示します。
terpri(タープリ)は改行します。

コード読み方意味
readリード数値を1つ読み込む(read)
read-lineリード・ライン1行を文字列として読む
princプリンク値を出力する(print)
terpriタープリ改行する

プログラムの問題(AtCoderなど)では、コードはこの形で書いて提出することが多いです。

(defun solve (...)   ; 計算のやり方
  ...)

(defun main ()       ; 読み込みと出力
  (let* (...)
    (princ (solve ...))
    (terpri)))

(main)Code language: Lisp (lisp)
  • main(メイン):主な、中心の。プログラムの入口として使う名前
  • solve(ソルブ):解く、解決する

6. 条件で分ける(if)

6.1. やってみよう

(defun pass-or-fail (score)
  (if (>= score 60)
      "合格"
      "不合格"))Code language: Lisp (lisp)
  • pass(パス):合格する、通過する
  • fail(フェイル):不合格、失敗する
  • score(スコア):点数
(pass-or-fail 80)
(pass-or-fail 45)
(pass-or-fail 60)Code language: Lisp (lisp)

6.2. 書き換えてみよう

合格ラインを 60 から 80 に変えてみましょう。
文字も変えてみましょう。

6.3. 余りで分ける

(defun odd-or-even (n)
  (if (evenp n)
      "偶数"
      "奇数"))Code language: Lisp (lisp)
  • odd(オッド):奇数の、変な
  • even(イーブン):偶数の、平らな
(odd-or-even 4)
(odd-or-even 7)
(odd-or-even 100)Code language: Lisp (lisp)

6.4. 2つの条件を組み合わせる

(defun both-pass (a b)
  (if (and (>= a 60) (>= b 60))
      "両方合格"
      "どちらかが不合格"))Code language: Lisp (lisp)
  • both(ボース):両方
(both-pass 80 70)
(both-pass 80 40)Code language: Lisp (lisp)

andor に変えるとどうなりますか?

6.5. ここで覚えたこと

if(イフ)は「もし〜なら」という分岐です。
条件が正しいとき(真)は1つ目の式が、まちがいのとき(偽)は2つ目の式が返ります。

(if 条件
    正しいとき
    まちがいのとき)Code language: Lisp (lisp)
コード読み方意味
ifイフもし〜なら(if)
= < <= > >=イコール、より小さい、以下、より大きい、以上数値を比べる
andアンド両方が正しい(and)
orオアどちらかが正しい(or)
evenpイーブン・ピー偶数かどうか(even)
oddpオッド・ピー奇数かどうか(odd)
zeropゼロ・ピー0かどうか(zero)
mod a bモッドa を b で割った余り(modulo)

7. 3つ以上に分ける(cond)

7.1. やってみよう

(defun grade (score)
  (cond ((>= score 90) "A")
        ((>= score 70) "B")
        ((>= score 50) "C")
        (t             "D")))Code language: Lisp (lisp)
  • grade(グレード):等級、成績
  • score(スコア):点数
(grade 95)
(grade 75)
(grade 55)
(grade 30)Code language: Lisp (lisp)

7.2. 書き換えてみよう

境界の数値や返す文字を変えてみましょう。
条件をもう1つ増やしてみましょう。

7.3. 文字列で分ける

(defun next-weather (s)
  (cond ((string= s "はれ")   "くもり")
        ((string= s "くもり") "あめ")
        (t                    "はれ")))Code language: Lisp (lisp)
  • next(ネクスト):次の
  • weather(ウェザー):天気
(next-weather "はれ")
(next-weather "くもり")
(next-weather "あめ")Code language: Lisp (lisp)

7.4. ここで覚えたこと

cond(コンド)は3つ以上に分けるときに使います。
condition(コンディション、条件)の略です。

上から順番に確認して、最初に正しかった条件の値が返ります。
t は、ほかの条件に当てはまらないときになります。

文字列を比べるときは string= を使います。
数値の = とは別物です。

コード読み方意味
condコンド3つ以上の条件で分ける(condition)
tティーどれにも当てはまらないとき
string=ストリング・イコール文字列が等しいか調べる
string<=ストリング・以下文字列を辞書順で比べる

8. 文字を扱う

8.1. やってみよう

(defun count-a (s)
  (count #\a s))Code language: Lisp (lisp)
  • count(カウント):数える
(count-a "banana")
(count-a "apple")
(count-a "lemon")Code language: Lisp (lisp)

#\aa を別の文字に変えてみましょう。

8.2. 文字列から1文字取り出す

(defun first-char (s)
  (char s 0))Code language: Lisp (lisp)
  • first(ファースト):最初の
  • char(チャー):character(文字)の略
(first-char "hello")
(first-char "world")Code language: Lisp (lisp)

012 に変えるとどうなりますか?

8.3. 次のアルファベット

(defun next-char (c)
  (code-char (+ (char-code c) 1)))Code language: Lisp (lisp)
  • next(ネクスト):次の
(next-char #\a)
(next-char #\z)
(next-char #\A)Code language: Lisp (lisp)

8.4. 小文字にする

(defun to-lower (c)
  (char-downcase c))Code language: Lisp (lisp)
  • lower(ロワー):小文字の、低い
(to-lower #\A)
(to-lower #\B)Code language: Lisp (lisp)

8.5. ここで覚えたこと

"hello" は文字列、#\h は文字1文字です。
書き方が違います。
文字には内側で数値(文字コード)が割り当てられていて、a97b98c99……という順番になっています。
だから +1 すると次のアルファベットになります。

コード読み方意味
#\文字ハッシュ・バックスラッシュ文字1つ
char s iチャー文字列 s の i 番目の文字(0から数える)
char= c1 c2チャー・イコール文字が等しいか
count #\文字 sカウント文字列の中にその文字が何個あるか
char-code cチャー・コード文字を数値に変える
code-char nコード・チャー数値を文字に変える
char-downcase cチャー・ダウンケース文字を小文字にする

9. リストを使う

9.1. やってみよう

REPL に直接打ち込んでみましょう。

(list 1 2 3)
(list 10 20 30 40)Code language: Lisp (lisp)

9.2. リストから値を取り出す

(first  (list 5 8 3))
(second (list 5 8 3))
(third  (list 5 8 3))Code language: Lisp (lisp)

9.3. 並べ替え

(sort (list 5 2 8 1) #'<)Code language: Lisp (lisp)
  • sort(ソート):並べ替え

#'<#'> に変えると大きい順になります。

9.4. リスト全体に計算する

(apply #'+ (list 1 2 3 4))
(apply #'max (list 5 2 8 1))
(apply #'min (list 5 2 8 1))Code language: Lisp (lisp)
  • apply(アプライ):適用する、使う

9.5. 重複を取り除く

(remove-duplicates (list 1 2 2 3 3 3))Code language: Lisp (lisp)

9.6. 組み合わせて使う

(defun range3 (a b c)
  (let* ((lst (sort (list a b c) #'<)))
    (- (third lst) (first lst))))Code language: Lisp (lisp)
  • range(レンジ):幅、範囲。最大値と最小値の差のこと
  • lst(リスト):list の略として変数名によく使われる
(range3 3 7 1)
(range3 10 2 5)Code language: Lisp (lisp)

何をしている関数か、わかりますか?

9.7. ここで覚えたこと

リストは複数の値をひとまとめにしたもので、list で作ります。

コード読み方意味
list a b cリスト値をリストにまとめる
first second thirdファースト・セカンド・サードリストの要素を取り出す
sort リスト #'<ソート昇順に並べる
lengthレングスリストの要素数
apply #'関数 リストアプライリスト全体に関数を使う
remove-duplicatesリムーブ・デュプリケーツ重複を取り除く

10. 文字列を変換する

10.1. やってみよう

(coerce "abc" 'list)Code language: Lisp (lisp)
(coerce (list #\a #\b #\c) 'string)Code language: Lisp (lisp)

10.2. 文字列を1文字ずつ変換する

(defun swap-1-9-char (c)
  (if (char= c #\1) #\9 #\1))

(defun swap-1-9 (s)
  (coerce (loop for c across s collect (swap-1-9-char c))
          'string))Code language: Lisp (lisp)
  • swap(スワップ):入れ替える
  • collect(コレクト):集める
(swap-1-9 "119")
(swap-1-9 "191")
(swap-1-9 "911")Code language: Lisp (lisp)

swap-1-9-char を変えて、ab に変換する関数を作ってみましょう。

10.3. 文字列の1文字だけ変える

(defun change-first (s)
  (let* ((result (copy-seq s)))
    (setf (char result 0) #\X)
    result))Code language: Lisp (lisp)
  • change(チェンジ):変える
  • result(リザルト):結果
  • copy(コピー):複製する、コピーする
(change-first "hello")
(change-first "world")Code language: Lisp (lisp)

01 に変えると、どこが変わりますか?

10.4. ここで覚えたこと

loop for c across s は文字列 s を1文字ずつ取り出す書き方です。
collect で集めたものを coerce で文字列に戻します。

コード読み方意味
coerce s 'listコアース文字列をリストに変える
coerce リスト 'stringコアースリストを文字列に変える
loop for c across s collect ...ループ文字列を1文字ずつ処理する
copy-seq sコピー・シーク文字列をコピーする
setfセットエフ値を書き換える

11. AtCoder に挑戦しよう

ここまで覚えた機能を組み合わせると、AtCoder のA問題が解けます。

11.1. 解答の基本形

(defun solve (a b)
  (+ a b))

(defun main ()
  (let* ((a (read))
         (b (read)))
    (princ (solve a b))
    (terpri)))

(main)Code language: Lisp (lisp)

REPL で (solve 3 4) と試してから main でつなぐ——この流れで進めましょう。
次は「ABC A問題で学ぶ Common Lisp 入門」に進んで、実際の問題を解いてみましょう。

11.2. これまでに覚えたすべての機能

コード読み方意味
defunデファン計算のやり方を決める
let*レット・スター途中の結果に名前をつける
readリード数値を1つ読み込む
read-lineリード・ライン1行を文字列として読む
princプリンク値を出力する
terpriターポリ改行する
+ - *プラス・マイナス・アスタリスク足し算・引き算・掛け算
floor a bフロア切り捨て除算
ceiling a bシーリング切り上げ除算
mod a bモッド余り
max
min
マックス
ミン
最大値・最小値
ifイフ2通りに分ける
condコンド3通り以上に分ける
= < <= > >=イコール、より小、以下、より大、以上数値を比べる
and orアンド・オア条件を組み合わせる
evenp
oddp
zerop
イーブンピー
オッドピー
ゼロピー
偶数・奇数・0の判定
string=
string<=
ストリング・イコール文字列を比べる
char s iチャー文字列の i 番目の文字
char=チャー・イコール文字を比べる
count #\文字 sカウント文字列の中の文字の個数
char-code
code-char
チャー・コード、
コード・チャー
文字と数値を変換する
char-downcaseチャー・ダウンケース文字を小文字にする
listリスト値をリストにまとめる
first
second
third
ファースト・
セカンド・
サード
リストの要素を取り出す
sort リスト #'<ソート昇順に並べる
lengthレングス要素数
apply #'関数 リストアプライリスト全体に関数を使う
remove-duplicatesリムーブ・デュプリケーツ重複を取り除く
coerceコアース文字列とリストを変換する
loop for c across s collectループ文字列を1文字ずつ処理する
copy-seqコピー・シーク文字列をコピーする
setfセットエフ値を書き換える