Hatena::ブログ(Diary)

ありの日記 このページをアンテナに追加 RSSフィード

2009-04-09

Common Lispのハッシュテーブル

| 00:18 |  Common Lispのハッシュテーブルのブックマークコメント

例によって、実践Common Lispを読みちゅう。11章でようやくリストの説明がはじまった。と思ったらまだだった。12章からみたい。著者としてはLispはリストだけじゃないんだよってことを言いたいらしい。

実践Common Lisp
実践Common Lisp
posted with amazlet at 09.04.09
Peter Seibel
オーム社
売り上げランキング: 199810

で、Common Lispハッシュテーブルって。結構面倒かも?

CL-USER> (defparameter *hash* (make-hash-table)) ; ハッシュテーブル「*hash*」を作成
*HASH*
CL-USER> (setf (gethash 'apple *hash*) 100) ; *hash*にセット、「キー:apple, 値:100」
100
CL-USER> (setf (gethash 'orange *hash*) 250); 「キー:orange, 値:250」
250
CL-USER> (setf (gethash 'banana *hash*) 150); 「キー:banana, 値:150」
150
CL-USER> (setf (gethash 'strawberry *hash*) 380) ; 「キー:strawberry, 値:380」
380
CL-USER> (gethash 'orange *hash*) ; *hash*から「キー:orange」の値を取得
250
T

最後のgethashは分かるんだけど、ハッシュに値をセットするときが何か謎。一旦gethashで値をとりだしといて、それをセットするみたいな。どゆこと?

(setf *hash* 'key "value")

↑こんな感じでいけそうなのに。だめっすか。



とりあえずそれは置いといて、gethashの戻り値は複数ある(上の例だと250とTが返ってきてる)。これを多値というらしい(20章で詳しく説明するらしい)。特別な処理で2つ目の値を見ない限り2つ目は普段は無視される。2つの値を処理したい時はMULTIPLE-VALUE-BINDっ関数を使うんだって。こんな感じ↓。

CL-USER> (multiple-value-bind (key value)
	     (gethash 'strawberry *hash*)
	   (format t "key: ~a value: ~a" key value))
key: 380 value: TNIL

ほほう。しかし、戻り値NILかどうかで判断するわけにはいかんのだろうか。

CL-USER> (gethash 'aaaa *hash*)
NIL
NIL

「*hash*」に定義されていないaaaaを指定したら1つ目にはNILだから値が入ってない場合の処理をしたいいから2つ目はいらないような・・・。

あ、aaaaがNILで設定されてるかも知れないからか。

CL-USER> (setf (gethash 'aaaa *hash*) NIL)
NIL
CL-USER> (gethash 'aaaa *hash*)
NIL
T

ほほう。2つ目の戻り値は指定したキーが設定されてるかどうかってことか。

cametancametan 2009/04/10 03:18 >Common Lispのハッシュテーブルって。結構面倒かも?

(笑)。
面倒ですよね(笑)。僕も嫌いです(笑)。

多分、歴史的には、元々Lispにはハッシュじゃなくって連想リストがあって、またsetqがあった、と。
それでCommon Lisp以前なんですけど、plistに要素を追加したりするputpropって言う関数があったんです。ハッシュならぬ属性リストにputpropでキーと値を追加してたんですね。
ここでハッシュテーブルを導入して、そして属性リストを操作するputpropが消えた。かつ、setqをもっと強化して汎用目的に使えるsetfが誕生したわけでしょう?つまり、ハッシュ操作の為の限定された関数を独立して作るより、汎用のsetqを強化してハッシュだろうと何だろうと操れるsetfマクロの設計に尽力したんだと思います。
材料さえあればどうにでもなりますしね。単純な形ですと、

(defun sethash (hash key value)
(setf (gethash key hash) value))

でもしてsethash関数でも定義すれば操りやすくなるんじゃないのか、とは思います。

>これを多値というらしい(20章で詳しく説明するらしい)。

多値って凄いでしょ(笑)。この機能は他の言語じゃそうそう無いですからね。関数が2つ以上の「答え」を返す、ってのは面白いです。Schemeにもそう言う機能はありますが。
もっとも出力が「一意に決まる」のが数学的関数なんじゃないのか、とか思うんで(笑)、明らかにこれだと数学的関数からは乖離してますよね(笑)。

cametancametan 2009/04/10 03:19 >Common Lispのハッシュテーブルって。結構面倒かも?

(笑)。
面倒ですよね(笑)。僕も嫌いです(笑)。

多分、歴史的には、元々Lispにはハッシュじゃなくって連想リストがあって、またsetqがあった、と。
それでCommon Lisp以前なんですけど、plistに要素を追加したりするputpropって言う関数があったんです。ハッシュならぬ属性リストにputpropでキーと値を追加してたんですね。
ここでハッシュテーブルを導入して、そして属性リストを操作するputpropが消えた。かつ、setqをもっと強化して汎用目的に使えるsetfが誕生したわけでしょう?つまり、ハッシュ操作の為の限定された関数を独立して作るより、汎用のsetqを強化してハッシュだろうと何だろうと操れるsetfマクロの設計に尽力したんだと思います。
材料さえあればどうにでもなりますしね。単純な形ですと、

(defun sethash (hash key value)
(setf (gethash key hash) value))

でもしてsethash関数でも定義すれば操りやすくなるんじゃないのか、とは思います。

>これを多値というらしい(20章で詳しく説明するらしい)。

多値って凄いでしょ(笑)。この機能は他の言語じゃそうそう無いですからね。関数が2つ以上の「答え」を返す、ってのは面白いです。Schemeにもそう言う機能はありますが。
もっとも出力が「一意に決まる」のが数学的関数なんじゃないのか、とか思うんで(笑)、明らかにこれだと数学的関数からは乖離してますよね(笑)。

hiro_nemuhiro_nemu 2009/04/11 00:40 >ハッシュならぬ属性リストにputpropでキーと値を追加してたんですね。
>ここでハッシュテーブルを導入して、そして属性リストを操作するputpropが消えた。かつ、setqをもっと強化して汎用目的に使えるsetfが誕生したわけでしょう?

なるほど。そういう経緯があった訳ですね。

>(defun sethash (hash key value)
>(setf (gethash key hash) value))
>
>でもしてsethash関数でも定義すれば操りやすくなるんじゃないのか、とは思います。

ですよね。絶対こういう関数つくっちゃいますよねー。


>もっとも出力が「一意に決まる」のが数学的関数なんじゃないのか、とか思うんで(笑)、明らかにこれだと数学的関数からは乖離してますよね(笑)。

そういえば。そうですよねw
多値ってもっと色々な使い方あるんでしょうね。ちょっと考えただけじゃ、配列とかリストとか返せば事足りるんじゃないかなーとか思ったりします。
Rubyだと配列を返す関数fooがあったとしたら
def foo
[1, 2]
end
a,b = foo
とすれば同じような感じですよね。(いや、違うのかな(笑))

cametancametan 2009/04/11 03:22 >多値ってもっと色々な使い方あるんでしょうね。ちょっと考えただけじゃ、配列とかリストとか返せば事足りるんじゃないかなーとか思ったりします。

うん、僕もそう思いました(笑)。
これは川合史郎さんの説明読む限り、「数学的関数の定義」と帳尻を合わせる、と言うより、「工学的な常識」を押し広げよう、と言うようなニュアンスに感じますね。あくまで限界に挑戦、みたいな(笑)。

Scheme:多値:
http://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E5%A4%9A%E5%80%A4

何か、「引数が複数取れるなら返り値も複数で良くね?」ってのが当初の「単純な」アイディアだったような……(笑)。
元々、ラムダ算法自体のラムダ式の引数は「複数の引数を単一に纏める」ような論理構造だったみたいで、要するに「単一引数→単一返り値」ってやり方だったようなんです。
これを実装上「複数の引数」を取れるようにしたんだったら……ってのがあったんでしょうね。「拡張」と言えば拡張なんです。理論的には。

んで、例えば複数の計算結果を配列に纏める、と言うのは僕もその通りだと思います。
ただし、多分、そうなると関数同士でやり取りする場合、「受け手」側にその配列をデータ操作して必要な値を取り出す操作を記述しないとなりません。
ですから、引数がレストパラメータ等を用いて「無制限に複数の引数が受け取れる」んでしたら、単一の関数の汎用性考えた場合、「受け取るデータの個数」に無頓着な方法があっても良いのではないか、とか(笑)。多分その辺の「怠惰」な要求が多値実装の本質なんじゃないかな、と(笑)。送信側は無神経に返り値を複数渡しておいて、受け手が「受け取れる」と想定した以上の情報を渡されたら「自然に無視してくれる」とか(笑)。その方が確かに記述量は減るんですよね。

hiro_nemuhiro_nemu 2009/04/13 22:28 >何か、「引数が複数取れるなら返り値も複数で良くね?」ってのが当初の「単純な」アイディアだったような……(笑)。

わはは。確かにそういう疑問って持ちますけど、どの言語でもそうだから(単一返り値)こんなもんなんだろうなと思ってましたけど、それを押し広げたら意外と便利だったってことになったんですかねー(笑)

>ただし、多分、そうなると関数同士でやり取りする場合、「受け手」側にその配列をデータ操作して必要な値を取り出す操作を記述しないとなりません。

なるほどー。確かにそうですよね。Rubyの場合、配列を返す場合「a,b = foo」ってな感じで簡単にする方法を敢えて用意してるってのは、そういうこですもんね、きっと。
Lispの場合、そうするよりも複数の値を返すことで対応したってだけなのかも知れないですね。

>多分その辺の「怠惰」な要求が多値実装の本質なんじゃないかな、と(笑)

その裏にはこんな要求があったわけですね(笑)

トラックバック - http://d.hatena.ne.jp/hiro_nemu/20090409/1239290291