普段Clojureを書く中であまり使わない(というか一度も使ったことない)popとpeekについて調べてまとめてみた。
要点
- peek = 最後に追加された要素を取得、 pop = 最後に追加された要素以外を取得
- それぞれIPersistentStackのインターフェースメソッド
- ClojureのlistとvectorはIPersistentStackを実装している
スタック?
一般的にはLIFOな抽象データ型で以下の3つの操作をサポートしている物
- push(要素の追加)
- pop(最後に追加した要素をスタックから除外し返却)
- peek(最後に追加した要素を取得)
このうちpushとpop操作がスタックを満すのに必須で、peekは任意っぽい
ClojureでImmutableなスタック?
一般的なスタックの操作はミュータブルで、Clojureと相性が悪いためスタック操作のイミュータブル版は以下になる
- push(要素を追加した後のコレクションを取得) -> 要はconjで後述する
- pop('最後に追加した'要素を除外したコレクションを取得)
- peek('最後に追加した'要素を取得)
注:イミュータブルなコレクションの文脈では'最後'というのは関数の適用の順序で
'追加'も要素を追加した後のコレクションを返す関数をコレクションに適用するという意味になる
conj
listは先頭に、vectorは最後尾に要素を追加した物が返ってくる
;; conj to a list
(conj '(1 2 3 4) 5) => (5 1 2 3 4)
;; conj to a vector
(conj [1 2 3 4] 5) => [1 2 3 4 5]
peek
追加する先がlistにせよvectorにせよイミュータブルなスタックとみなした場合、
'最後'にconjした要素が5なのでpeekをすると5が返ってくる。
(-> [1 2 3 4]
(conj 5)
peek) =>5
(-> '(1 2 3 4)
(conj 5)
peek) => 5
pop
追加する先がlistにせよvectorにせよイミュータブルなスタックとみなした場合、
'最後'にconjした要素が5なのでpopをすると5を除外したコレクションが返ってくる。
;; popping from a vector
(-> [1 2 3 4]
(conj 5)
pop) => [1 2 3 4]
;; popping from a list
(-> '(1 2 3 4)
(conj 5)
pop) => (1 2 3 4)
tips:vectorの最後の要素を取得したい場合はlastよりもpeek
lastはシーケンスの抽象に依存しているなので、先頭から最後に辿り付くまで要素を全て走査してしまうため遅いが、
vectorに対するpeekは高速で最後尾の要素を取得できる
(time
(last [1 2 3 4 5 6 7 8 9]))
;; "Elapsed time: 0.171368 msecs"
(time
(peek [1 2 3 4 5 6 7 8 9]))
;; "Elapsed time: 0.066708 msecs"
参考
- https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29
- https://github.com/clojure/clojure/blob/f572a60262852af68cdb561784a517143a5847cf/src/jvm/clojure/lang/IPersistentStack.java
- https://github.com/clojure/clojure/blob/ebfdbca535b81808b3ddc369d4a4e98b8f1524c7/src/jvm/clojure/lang/APersistentVector.java#L318