ペアとリストはSchemeにおける最も基本的なデータ構造のひとつです。
Gaucheのコアは全ての標準のリスト手続きと、多くのScheme実装に見られる
便利な手続きのいくつかを提供します。それらで足りない場合は
さらに多くの手続きがscheme.list
- R7RSリスト、
util.combinations
- 組み合わせ
といったモジュールで提供されています。
また、リストに限らないジェネリックなシーケンス/コレクションの操作が
gauche.collection
- コレクションフレームワークと
gauche.sequence
- シーケンスフレームワークで提供されています。
• pairクラスとnullクラス: | ||
• 変更可能なペアと変更不可なペア: | ||
• リストに関する述語: | ||
• リストの作成: | ||
• リストへのアクセスと変更: | ||
• リストをたどる手続き: | ||
• 他のリスト手続き: | ||
• 連想リスト: | ||
• 拡張ペアとペア属性: |
Next: 変更可能なペアと変更不可なペア, Previous: ペアとリスト, Up: ペアとリスト [Contents][Index]
リストを表す抽象クラスで、クラス<null>
とクラス<pair>
の親クラスです。
クラス<sequence>
を継承します。
循環リストやドットリストも<list>
クラスのインスタンスですが、
list?
は偽の値を返すことに注意して下さい。
(use srfi.1) (list? (circular-list 1 2)) ⇒ #f (is-a? (circular-list 1 2) <list>) ⇒ #t
空リストのクラスです。()
がこのクラスの唯一のインスタンスです。
ペアのクラスです。
Next: リストに関する述語, Previous: pairクラスとnullクラス, Up: ペアとリスト [Contents][Index]
ペアには変更可能なものと変更不可なものがあります。
変更可能なペアは、set-car!
やset-cdr!
等の破壊的変更手続き
(通常、名前の最後に!
がついています)で変更することが可能です。
変更不可なペアを変更しようとするとエラーが報告されます。
Gaucheでは、変更しようとしない限り、どちらのペアも全く同じように振る舞います。
pair?
にはどちらも真を返します。ペアが変更不可であることを調べたければ
ipair?
が使えます(リストに関する述語参照)。
伝統的なコンストラクタcons
は変更可能なペアを作ります。
変更不可なペアを作るにはipair
を使ってください (リストの作成参照)。
変更不可なペアを特に扱う手続きは、R7RS-largeのscheme.ilist
モジュールに
定義されています (scheme.ilist
- R7RS変更不可リスト参照)。
クオートされたリテラルは変更不可です。
ただ、古いバージョンのGaucheは変更不可なペアを持っておらず、クオートされたリテラルリストも
変更可能でした。その頃に書かれたコードでうっかりリテラルを変更していたら、
現在のGaucheではエラーになってしまうでしょう。
問題を修正せずにどうしても古いコードのまま走らせたい場合は、環境変数
GAUCHE_MUTABLE_LITERALS
を定義してください。
Next: リストの作成, Previous: 変更可能なペアと変更不可なペア, Up: ペアとリスト [Contents][Index]
[R7RS base]
objがペアなら#t
を、そうでなければ#f
を返します。
[R7RS ilist]
objが変更不可なペアなら#t
を、そうでなければ#f
を返します。
変更不可なペアは、この述語を使うか、あるいはそのペアを変更しようとした場合を除いては、 変更可能なペアと全く同じように動作します。 詳しくは変更可能なペアと変更不可なペア参照。
[R7RS base]
objが空リストなら#t
を、そうでなければ#f
を返します。
[R7RS list]
obj が空リストなら#t
を、ペアなら#f
を返し、
それ以外のときはエラーを報告します。
リスト終了条件を検査する時に、正しいリスト以外を除外したい場合に
null?
の代わりに使えます。
[R7RS base]
objが正しいリストなら#t
を、そうでなければ#f
を返します。
この手続きはobjがドットリストや循環リストなら#f
を返します。
下に説明する、
proper-list?
、circular-list?
、dotted-list?
といった手続きも参照してください。
[R7RS list]
x が真性リスト、つまり()
で終端された有限のリストであれば
#t
を、そうでなければ#f
を返します。
[R7RS list]
x が循環リストであれば #t
を返します。リストをcdr
方向に
辿って行って、既に訪れたペアに出会ったら、それは循環リストです。
リストの頭 (x
) が循環に含まれている必要はありません。
また、car
方向への循環があってもそれだけでは循環リストとはみなされません。
[R7RS list]
x が有限の大きさで、空リストで終端していないリストなら
#t
を返します。これには、ペアではなく、空リストもない値(たとえば
シンボルや数値)のような長さ0のドットリストと考えられるものを含みます。
Next: リストへのアクセスと変更, Previous: リストに関する述語, Up: ペアとリスト [Contents][Index]
[R7RS base] obj1とobj2の変更可能なペアを作成して返します。
(cons 'a 'b) ⇒ (a . b)
[R7RS ilist] obj1とobj2の変更不可なペアを作成して返します。
(ipair 'a 'b) ⇒ (a . b)
[R7RS base] 長さlenの正規のリストを返します。引数fillが与えられていれば、各要素は fillになります。そうでなければ各要素の値は不定です。
(make-list 5 #t) ⇒ (#t #t #t #t #t)
[R7RS base] 要素がobj …であるリストを作成します。
(list 1 2 3) ⇒ (1 2 3) (list) ⇒ ()
[R7RS ilist] 要素がobj …であり、変更不可なペアで作られたリストを作成します。
(ilist 1 2 3) ⇒ (1 2 3) (ilist) ⇒ () (list-set! (ilist 1 2 3) 1 'a) ⇒ ERROR: Attempt to modify an immutable pair: (2 3)
[R7RS list]
list
とほぼ同じですが、最後の引数が作成されるリストの最後のペアのcdrになります。
二つの手続きは全く同じです。Gaucheはもともとlist*
を持っていましたが、
SRFI-1 (R7RS (scheme list)
) がcons*
という名前を定義しました。
(list* 1 2 3) ⇒ (1 2 . 3) (list* 1) ⇒ 1
[R7RS base] listの浅いコピーを行います。 listが循環リストの場合、エラーが投げられます。 (循環リストを検出するのはGaucheの拡張です。 R7RSではこの手続きは停止しなくても良いことになっています。)
[R7RS list] startから始まり、stepずつ増加する、 count 個の要素からなる数値のリストを返します。countは 非負の整数でなければなりません。startとstepが ともに正確数であれば、結果は正確数のリストになります。そうでなければ 結果は非正確数のリストです。
(iota 5) ⇒ (0 1 2 3 4) (iota 5 1 3/7) ⇒ (1 10/7 13/7 16/7 19/7) (iota 5 0 -0.1) ⇒ (0 -0.1 -0.2 -0.3 -0.4)
この手続きはリストを最後まで作って返します。リストが短ければ
十分速いですが、何万という数をリストにしたければ、遅延リストを使った方が
良いかもしれません。liota
を見てください(遅延シーケンス参照)。
条件によりエントリを追加することによりリストを構築します。 それぞれのclauseは条件と式を持ちます。 条件が真であれば、関連する式の結果が結果のリストの構築に使われます。 条件が偽であれば、何も挿入されません。
clauseは、以下のフォームのうちの1つでなければなりません。
(test expr …)
testが評価され、それが真ならばexpr …が評価され、 戻り値が結果の一部となります。exprが与えられなければ、 testの結果が偽でなければその結果が使われます。
(test => proc)
testが評価され、それが真ならばprocがその値とともに 呼ばれ、その戻り値が結果を構築するために使われます。
(test @ expr …)
(test expr …)
のように動作しますが、最後のexprの
値はリストでなければならず、それは結果のリストに(unquote-splicingのように)
スプライスされます。
(test => @ proc)
(test => proc)
のように動作しますが、procの戻り値
はリストでなければならず、それは結果のリストに(unquote-splicingのように)
スプライスされます。
(let ((alist '((x 3) (y -1) (z 6)))) (cond-list ((assoc 'x alist) 'have-x) ((assoc 'w alist) 'have-w) ((assoc 'z alist) => cadr))) ⇒ (have-x 6) (let ((x 2) (y #f) (z 5)) (cond-list (x @ `(:x ,x)) (y @ `(:y ,y)) (z @ `(:z ,z)))) ⇒ (:x 2 :z 5)
Next: リストをたどる手続き, Previous: リストの作成, Up: ペアとリスト [Contents][Index]
[R7RS base] pairのcarもしくはcdrをobjで置き換えます。
注: (setter car)
≡ set-car!
であり、
(setter cdr)
≡ set-cdr!
です。
[R7RS base][R7RS cxr]
caar
≡ (car (car x))
,
cadr
≡ (car (cdr x))
, 等々。
R7RSでは、2レベルよりも深いアクセサは(scheme cxr)
ライブラリに含まれます。
対応するsetterも定義されています。
(let ((x (list 1 2 3 4 5))) (set! (caddr x) -1) x) ⇒ (1 2 -1 4 5)
[R7RS base] 正規のリストlistの長さを返します。 listがドットリストならばエラーが起きます。 listが循環リストの場合、この関数は無限ループします。
[R7RS list]
x が真性リストなら、その長さを返します。
x がそうでなければ(たとえ循環リストであっても)
#f
を返します。
ドットリストの「長さ」を、構成するペアの数で知りたい場合は、
下のnum-pairs
が使えます。
それぞれ、リストxの長さが正確な整数kと等しいか、k未満か、k以下か、kより大きいか、
k以上かの場合に#t
を、そうでなければ#f
を返します。
この関数はk要素までしかリストを辿らないので、特にxが遅延シーケンス
(遅延シーケンス参照)である場合に、必要以上にリストを実体化しなくて済みます。
xはドットリストや循環リストでも構いません。
ドットリストの長さを数える際には最後のペアのcdrは無視します。
つまり、ペアでないオブジェクトの長さは0で、(a . b)
の長さは1になります。
循環リストは無限の長さを持つとみなします。
(length<=? '(a b) 2) ⇒ #t (length<=? '(a b) 1) ⇒ #f (length<=? '() 0) ⇒ #t ;; dotted list cases (length<=? 'a 0) ⇒ #t (length<=? '(a . b) 0) ⇒ #f (length<=? '(a . b) 1) ⇒ #t
註: これらの手続きの名前は混乱を招きやすいかもしれません。something<=?
等の
名前は通常、同じ型のオブジェクト同士の比較に使われるからです。
残念ながら、今のところより良い名前を思いつけていません。
リストlisを構成するペアの数を正確な整数で返します。lisはドットリストや循環リスト
でも構いません。lisが正しいリストなら戻り値はlength
と同じです。
(num-pairs '(a b c d e)) ⇒ 5 (num-pairs '()) ⇒ 0 (num-pairs '(a b c d . e)) ⇒ 4 (num-pairs 'a) ⇒ 0 (num-pairs '#0=(a b . #0#)) ⇒ 2 (num-pairs '(a . #0=(b c . #0#))) ⇒ 3
[R7RS list]
take
はリスト x の最初のi個の要素を返します。
drop
はリスト x の最初のi個の要素を除いたリストを返します。
(take '(a b c d e) 2) => (a b) (drop '(a b c d e) 2) => (c d e)
x はあらゆる値をとりえます。
(take '(1 2 3 . d) 2) => (1 2) (drop '(1 2 3 . d) 2) => (3 . d) (drop '(1 2 3 . d) 3) => d
drop
は x に i 回 cdr 操作をおこなうのと全く
同じです。返される値は、x と共通の末尾を共有します。一方、
take は、引数のリストが長さ0でないリストなら必ず新しいリストの
領域を確保します。
i がリスト x の終端を超えたらエラーが発生します。
より寛容な手続きについては、下のtake*
とdrop*
を見てください。
あらゆる並びからの部分並びを抽出する汎用的な方法に関しては、
シーケンスのスライス にある subseq
を参照してください。
take
とdrop
のより寛容なバージョンです。
これらの手続きは、リストがk
より短くてもエラーを通知しません。
その場合、take*
はデフォルトではlistのコピーを
返します。もしfill?が真であれば、足りない要素の部分に
paddingを追加してk要素にしたリストを返します。
一方、drop*
はリストの長さが不足する場合は単に空リストを返します。
(take* '(a b c d) 3) ⇒ (a b c) (take* '(a b c d) 6) ⇒ (a b c d) (take* '(a b c d) 6 #t) ⇒ (a b c d #f #f) (take* '(a b c d) 6 #t 'z) ⇒ (a b c d z z) (drop* '(a b c d) 3) ⇒ (d) (drop* '(a b c d) 5) ⇒ ()
注意: 一般的な、いかなるシーケンスからのサブシーケンスの抽出については、
シーケンスのスライスのsubseq
を見て下さい。
[R7RS list]
take-right
は lis の最後の k個の要素
からなるリストを返します。
drop-right
は lis の最後の k個の要素を
除いたリスト返します。
(take-right '(a b c d e) 2) => (d e) (drop-right '(a b c d e) 2) => (a b c)
lis は有限リストであればOKです。
(take-right '(1 2 3 . d) 2) => (2 3 . d) (drop-right '(1 2 3 . d) 2) => (1) (take-right '(1 2 3 . d) 0) => d (drop-right '(1 2 3 . d) 0) => (1 2 3)
take-right
の返す値はいつでも lis の共通の末尾を共有します。
drop-right
は、引数が長さが0でないリストなら、必ず新しいリストの
領域を確保します。
k がリスト lis の長さより大きければエラーが発生します。
より寛容なバージョンについては下のtake-right*
, drop-right*
を
参照してください。
take*
及びdrop*
と同じですが、listの右端からカウントします。
これらはlistがk要素より短い場合にもエラーを通知しません。
drop-right*
はその場合は単に空リストを返します。
take-right*
はその場合はデフォルトでlistそのものを
返しますが、fill?が真であればlistの右側に
足りない分だけpaddingを足したものを返します。その場合でも
結果の尾部はlistと共有されます。
[R7RS list]
take
および drop-right
の
その場で更新されるバージョンです。これらの
手続きは lis を破壊的に変更するかもしれません。
lis が循環リストなら、take!
は期待されるものより短いリストを返す
可能性があります。
[R7RS base]
listのk番目のcdrを返します。listは
正規のリストでもドットリストでも循環リストでも構いません。
(listがドットリストの場合、最後のcdr
は無視されます)。
kの値が負であったりlistの長さ以上の場合、 fallback引数が与えられていればそれが返され、 そうでなければエラーが報告されます。
[R7RS+ base] listのk番目の要素を返します。listは 正規のリストでもドットリストでも循環リストでも構いません。
もしkがリストの長さを超えていたり、負数であった場合は通常はエラーが起こります。 しかし、オプショナルな引数fallbackが与えられていた場合は、エラーは起きず fallbackが返されます。これはGaucheの拡張です。
[R7RS base] リストlistのk番目の要素をvへと変更します。 kが0から(- リストの長さ 1)の範囲の正確な整数でない場合は エラーが報告されます。listが変更不可なリストであった場合、 エラーは報告されませんが、その後の振る舞いは不定となります。
[R7RS list] listの最後のペアを返します。listは 正規のリストかドットリストです。
(last-pair '(1 2 3)) ⇒ (3) (last-pair '(1 2 . 3)) ⇒ (2 . 3) (last-pair 1) ⇒ error
[R7RS list]
空ではない有限リスト pair の最後の要素を返します。
これは、(car (last-pair pair))
と同等です。
(last '(1 2 3)) ⇒ 3 (last '(1 2 . 3)) ⇒ 2
[R7RS list]
split-at
はリスト x をインデックス i の
位置で分割し、最初の i 個の要素からなるリストと、残りの末尾とを
返します。
(split-at '(a b c d e) 2) ⇒ (a b) (c d e)
split-at!
はその場で更新されるバージョンです。
これは x を破壊的に更新するかもしれません。
SRFI-1 (R7RS (scheme list)
)のsplit-at
の寛容なバージョンです。
take*
とdrop*
の結果を返します。
(split-at* '(a b c d) 6 #t 'z)
⇒ (a b c d z z) and ()
listを、それぞれの長さがkであるようなサブリスト(スライス)に
分割します。
listの長さがkの整数倍でない場合は、最後のスライスは
take*
と同じ方法で扱われます。つまり、デフォルトではkより
短いもの、あるいはfill?が真ならばpaddingが追加されます。
(slices '(a b c d e f g) 3) ⇒ ((a b c) (d e f) (g)) (slices '(a b c d e f g) 3 #t 'z) ⇒ ((a b c) (d e f) (g z z))
listの要素の間にitemを挿入します。 (引数の順番は、Haskellのintersperseと同じです。)
(intersperse '+ '(1 2 3)) ⇒ (1 + 2 + 3) (intersperse '+ '(1)) ⇒ (1) (intersperse '+ '()) ⇒ ()
Next: 他のリスト手続き, Previous: リストへのアクセスと変更, Up: ペアとリスト [Contents][Index]
[R7RS+ base] 与えられたリストの各要素に対してprocを適用し、その結果をリストにして 返します。R7RSではprocの適用順序が定められていませんが、Gaucheでは 常にprocはリスト内の順番で呼ばれます。 複数のリストが与えられた場合、最も短いリストが終了した時点でprocの適用を 打ち切ります。
(map car '((a b) (c d) (e f))) ⇒ (a c e) (map cons '(a b c) '(d e f)) ⇒ ((a . d) (b . e) (c . f))
gauche.collection
モジュール(gauche.collection
- コレクションフレームワーク参照)
を使うと、map
がリスト以外のコレクション型に対しても動作するようになります。
[R7RS list] 機能的には以下と同等ですが、若干効率が良いです:
(apply append (map f clist1 clist2 …)) (apply append! (map f clist1 clist2 …))
引数のリストのうち少くともひとつは有限でなければなりません。
map
とほぼ同じですが、引数の最後のペアのcdr
にtail-procが
適用され、その返り値が結果のリストの最後のペアのcdr
になります。
この手続きは正規でないリストを引数に取ることができます。
引数のリストがひとつだけの場合、tail-procの引数は必ずペアでないオブジェクトです。
(map* - / '(1 2 3 . 4)) ⇒ (-1 -2 -3 . 1/4) (define (proper lis) (map* values (lambda (p) (if (null? p) '() (list p))) lis)) (proper '(1 2 3)) ⇒ (1 2 3) (proper '(1 2 3 . 4)) ⇒ (1 2 3 4)
二つ以上のリストが与えられた場合は、最も短いリストがtail-procの呼び出しを
決めます。map*
が最も短いリストの最後のペアに達した時点で、
その時のそれぞれのペアのcdrがtail-procへと渡されます。
(map* + vector '(1 2 3 4) '(1 2 . 3)) ⇒ (2 4 . #((3 4) 3))
註: map*
という名前は、正規でないリストを作り得るlist*
/cons*
からの
連想でつけられました
(リストの作成、scheme.list
- R7RSリスト参照)。
[R7RS base]
手続きprocをリストの各エレメントに対して順に適用します。
procの結果は捨てられます。for-each
の戻り値は定義されていません。
複数のリストが与えられた場合、一番短いリストが終了した時点でfor-each
は終了します。
gauche.collection
モジュール(gauche.collection
- コレクションフレームワーク参照)
を使うと、for-each
がリスト以外のコレクション型に対しても動作するようになります。
[R7RS list] 基本的なリスト反復演算子です。単一のリスト clist1 = (e1 e2 … en) を与えられたときには、以下を返します。
(kons en … (kons e2 (kons e1 knil)) … )
n 本のリストが与えられた場合には、kons 関数は n+1 個の引数 をとる関数でなければなりません。それぞれのリストから要素をひとつずつと、 初期値 knil である「種」あるいは畳み込み状態とよばれるものです。 この畳み込み演算は、もっとも短いリストの要素がなくなったところで終了します。 与えられるリストの少くともひとつは有限でなければなりません。
例:
(fold + 0 '(3 1 4 1 5 9)) ⇒ 23 ;sum up the elements (fold cons '() '(a b c d e)) ⇒ (e d c b a) ;reverse (fold cons* '() '(a b c) '(1 2 3 4 5)) ⇒ (c 3 b 2 a 1) ;n-ary case
[R7RS list] 基本的な再帰演算子です。単一のリスト clist1 = (e1 e2 … en) を与えられたときには、以下を返します。
(kons e1 (kons e2 … (kons en knil)))
n 本のリストが与えられた場合には、kons 関数は n+1 個の引数 をとる関数でなければなりません。それぞれのリストから要素をひとつずつと、 初期値 knil である「種」あるいは畳み込み状態とよばれものです。 この畳み込み演算は、もっとも短いリストの要素がなくなったところで終了します。 与えられるリストの少くともひとつは有限でなければなりません。
例:
(fold-right cons '() '(a b c d e)) ⇒ (a b c d e) ;copy list (fold-right cons* '() '(a b c) '(1 2 3 4 5)) ⇒ (a 1 b 2 c 3) ;n-ary case
[R6RS] これは左結合のfoldの別バージョンです。一つのリスト clist1 = (e1 e2 … en) が与えられた場合、次の値を返します。
(snok (… (snok (snok knil e1) e2) …) en)
上のfold
と見比べてみてください。結合の順序は同じですが、
snokに渡される引数の順序が、fold
においてkonsに渡される
引数の順序とは逆になっています。snok
が可換な演算であれば、
fold
とfold-left
の結果は同じになります。
(fold-left + 0 '(1 2 3 4 5) ⇒ 15 (fold-left cons 'z '(a b c d)) ⇒ ((((z . a) . b) . c) . d) (fold-left (^[a b] (cons b a)) 'z '(a b c d)) ⇒ (a b c d z)
二つ以上のリストが渡された場合、snokは現在のknil値および
入力リストclist1
clist2
… の対応する要素を引数として
呼び出されます。
(fold-left list 'z '(a b c) '(A B C)) ⇒ (((z a A) b B) c C)
註:多くの関数型言語で左結合および右結合の畳み込み操作が定義されていますが、
それはfold-left
とfold-right
に相当します。
(例: Haskellのfoldl
とfoldr
。)
Schemeでは、まずSRFI-1によってfold
とfold-right
が
左/右結合の畳み込み演算として導入され、R6RSでfold-left
が定義されました。
(但し、R6RSのfold-left
ではclist1
clist2
…の
長さがすべて同じでない場合の動作は未定義ですが、Gaucheでは
最短のリストが終了した時点でそれまでの結果が返されます。)
[R7RS list]
fold
および fold-right
の変形バージョンです。
f は二項演算子でなければなりません。
また、ridentity は f の入力として許される
あらゆる値 x について以下を満していなければなりません。
(f x ridentity) ≡ x
これらの関数は実質的に fold
や fold-right
と同じことを
行いますが、ridentityには上記の性質があるため、
fはridentityには適用されません。
ridentityが使われるのはlistが空の場合だけです。
[R7RS list] 手続き pred が list の各要素に適用され、 pred が真を返す要素のリストが返されます。
(filter odd? '(3 1 4 5 9 2 6)) ⇒ (3 1 5 9)
filter!
はその場で更新されるバージョンです。結果を生成するために
list を破壊的に変更するかもしれません。
map
と似ていますが、真になる場合の値のみが保存されます。
引数として与えられるリストの少くともひとつは有限でなければなりません。
(filter-map (lambda (x) (and (number? x) (* x x))) '(a 1 b 3 c 7)) ⇒ (1 9 49)
[R7RS list] 手続き pred が list の各要素に適用され、 pred が偽を返す要素のリストが返されます。
(remove odd? '(3 1 4 5 9 2 6)) ⇒ (4 2 6)
remove!
はその場で更新されるバージョンです。結果を生成するために
list を破壊的に更新するかもしれません。
[R7RS list]
clist の各要素に対して左から右に pred を適用し、
pred が真を返す最初の要素を返します。predを満たす要素が
無い場合は#f
を返します。
[R7RS list]
clist の各要素に対して左から右に pred を適用し、pred が
真を返す場合、その car がその要素であるペアを返します。
predを満たす要素が無い場合は#f
を返します。
[R7RS list]
clist の各要素に pred を適用し、predが偽でない
値を返したら直ちにその値を返します。
predが偽でない値を返す前にリストの要素を使いきってしまったら
#f
が返ります。
[R7RS list] clist の各要素に pred を順に適用し、predが 偽を返した場合、直ちに偽を返します。全てのpredの適用が 偽でない値を返した場合は、最後に返された値が返されます。
[R7RS list] n をゼロから与えられたリストのうち最も短いリストの 長さまでとして、pred 手続きを与えられたリストの n 番目の要素に それぞれ適用します。 pred が真を返した数が返ります。
(count even? '(3 1 4 1 5 9 2 5 6)) ⇒ 3 (count < '(1 2 4 8) '(2 4 6 8 10 12 14 16)) ⇒ 3
引数で与えられるリストの少くともひとつは有限でなければなりません。
(count < '(3 1 4 1) (circular-list 1 10)) ⇒ 2
[R7RS list] 以下と同等です。
(remove (lambda (y) (elt= x y)) list) (remove! (lambda (y) (elt= x y)) list)
比較手続き elt= はデフォルトでは equal?
です。
[R7RS list]
list から重複した要素を取り除きます。list 中に等しい要素が
複数ある場合、一番左がわにある最初のものだけが残ります。これらの
生き残った要素間の順番は最初のリストの順番が保存されます。
比較手続き elt= のデフォルト値は、equal?
です。
Next: 連想リスト, Previous: リストをたどる手続き, Up: ペアとリスト [Contents][Index]
[R7RS base] 渡されたリストの要素を繋げたリストを返します。最後の引数の部分以外は新しいセルがアロケート されて使われます。最後の引数は正規のリストである必要がありません。その場合、結果は正規でない リストとなります。
[R7RS list] 渡されたリストの要素を繋げたリストを返します。最後の引数以外のリストのセルは、結果を 作成するために再利用されるかもしれません。 最後の引数は正規のリストである必要はありません。
[R7RS list]
それぞれ、(apply append list-of-lists)
および
(apply append! list-of-lists)
と同等ですが、
apply
のオーバヘッドが無い分、わずかに効率的です。
[R7RS+ base]
listの要素を逆順に並べたリストを返します。
reverse
は常に新たにリストをアロケートしますが、
reverse!
は結果を作るためにlistのセルを再利用するかもしれません。
但し、listの先頭のセルがreverse!
の結果でも先頭になるとは
限らないので、listが変更されることを当てにするのではなく、
reverse!
の戻り値を利用する必要があります。
省略可能引数tailが与えられた場合、それは結果のリストの末尾に 付け足されます (tail部分はコピーされません)。 これは、既にある結果のリストに新たに作った結果を付け足すイディオムで 便利です。
(reverse '(1 2 3 4 5)) ⇒ (5 4 3 2 1) (reverse '(1 2 3) '(a b)) ⇒ (3 2 1 a b)
tail引数はGauche独自の拡張で、
伝統的なSchemeのreverse
にはありませんが、
次の対応を考えると、そう不自然なことではありません。
(reverse xs) ≡ (fold cons xs '()) (reverse xs tail) ≡ (fold cons xs tail)
ポータブルなコードでは2引数のreverse
/reverse!
のかわりに
以下のappend-reverse
/append-reverse!
が使えます。
[R7RS list]
2引数のreverse
およびreverse!
と同等です。
SRFI-1 (R7RS (scheme list)
) との互換のために用意されています。
[R7RS base]
listからobjを探します。もしlistのn番目の要素が
objと同一ならば、(list-tail list n)
を返します。
memq
は同一性の判定にeq?
を、memv
はeqv?
を、
member
はequal?
を使います。
objがlist中に見つからなければ#f
が返されます。
member
に省略可能引数obj=が与えられた場合は、
その手続きがequal?
のかわりにobjと要素を比較するのに使われます。
(memq 'a '(a b c)) ⇒ (a b c) (memq 'b '(a b c)) ⇒ (b c) (memq 'a '(b c d)) ⇒ #f (memq (list 'a) '(b (a) c)) ⇒ #f (memv 101 '(100 101 102)) ⇒ (101 102)
連想リストはペアのリストです。各ペアのcar
がキー、cdr
がそのキーに
結びつけられた値です。これはマッピングを表現する最も初期のデータ構造でした。
値のルックアップにO(N)を要するのであまり大きなデータを扱うには向いていませんが、 不変データ構造を容易に実現できる (同じキーを持つエントリを追加すると既存のキーが「シャドウ」 されるため、既存のデータ構造を破壊することなく内容の変更が可能) ので、今でも広く使われています。
(cons (cons obj1 obj2) obj3)
を返します。
連想リストの先頭に新しいエントリを加えるのに便利です。
(この手続きはSRFI-1 (R7RS (scheme list)
)ではalist-cons
という名前で定義されています。
scheme.list
- R7RSリスト参照)。
(acons 'a 'b '((c . d))) ⇒ ((a . b) (c . d))
[R7RS base]
listの各要素はペアでなければなりません
(Gaucheはlist中のペアでない要素を単に無視しますが、
他のR7RS処理系はエラーを投げるかもしれないので、
ポータブルなコードを書いている時は注意してください)。
これらの手続きは、listの要素であるペアのうち、そのcarが
objと一致するペアを左から探して行きます。もし見付かればそのペアが、
見付からなければ#f
が返されます。
assq
は比較関数にeq?
を、assv
はeqv?
を、
assoc
はequal?
をそれぞれ用います。
assoc
の省略可能引数が与えられた場合は、
その手続きがequal?
のかわりに、
objと各キーとの同一性判定に使われます。
与えられるvalがalistのそれぞれの要素で、carの代わりに
cdrにマッチするような逆になった連想リストです。
両方向の連想リストと理解すると簡単です。
rassoc
は、そのデフォルトがequal?
である、オプションの
比較関数を取ります。rassq
はeq?
、rassv
はeqv?
を
使います。
[R7RS list] alist の新しい複製を返します。alist の背骨の部分、 およびキーと値を指す各セルは複製されます。
(define a (list (cons 'a 'b) (cons 'c 'd))) a ⇒ ((a . b) (c . d)) (define b (alist-copy a)) b ⇒ ((a . b) (c . d)) (set-cdr! (car a) 'z) a ⇒ ((a . z) (c . d)) b ⇒ ((a . b) (c . d))
他の*-ref手続きと対称的な方法で、連想リストalistからkeyを
探します。キーはkey=で比較され、省略時はequal?
が使われます。
これは、一般的な連想リストアクセスのパターンを提供します。
(alist-ref alist key default eq-fn) ≡ (cond [(assoc key alist eq-fn) => cdr] [else default])))
defaultが省略されると、#f
が使われます。
註: この手続きは、旧式のassoc-ref
、assq-ref
、assv-ref
を
置き換えるものです。これらの手続きは、assoc
等と似た名を持つのに
引数順が異なり、また新しい連想リスト操作はalist-
をプレフィクスに持つという
原則からも外れていることから、非推奨となりました。
assoc-ref
を使っているコードをalist-ref
に書き換える際には、
省略可能引数の順序が異なっていることに注意してください。これは、alist-ref
で複数のバリエーションをカバーしやすくするためです。
例えば(assq-ref alist key)
は(alist-ref alist key eq?)
で
置き換えられます。
値がvalに一致するようなキーを連想リストalistから探して返します。
alist-key
とalist-ref
の関係は、
assoc
に対するrassoc
の関係に相当します。
値の等価性はval=で比較されます。デフォルトはequal?
です。
valに一致する値を持つエントリが無かった場合はdefaultが
返されます。そのデフォルトは#f
です。
(alist-key alist val val= default) ≡ (cond ((rassoc val alist val=) => car) (else default))
註: この手続きは旧式のrassoc-ref
、rassq-ref
、rassv-ref
を
置き換えるものです。省略可能引数の順序がrassoc-ref
と異なっていることに
注意してください。
alist
に(key . val)
のペアが追加された連想リストを返します。
alist
がすでにkeyをキーとする要素を持っている場合、
その要素のcdrは破壊的にvalに変更されます。
alistがkeyをキーとする要素を持っていない場合は、
新しいペアが作成され、alistの一番前に追加されます。
したがって、key-valペアが追加されたことを保証するために
その戻り値を使うべきです。
キーはkey=によって比較されます。デフォルトはequal?
です。
元のalistを破壊したくない場合は、下のalist-adjoin
を使ってください。
alist-set!
の非破壊的バージョンです。
alistがkeyをキーとするエントリを持っていた場合、その値をvalに
変えた新たな連想リストを作って返します。alist中のエントリの順序は保存されます。
もしalist中にkeyをキーとするエントリが無い場合は、
(acons key val alist)
が返されます。
元のalistは変更されません。但し、返された連想リストはalistと末尾部分を 共有するかもしれません。
key=引数は2引数を取る手続きで、キーの比較に使われます。
省略された場合はequal?
が使われます。
[R7RS list]
alist から keyと同じキーをもつすべてのセルを削除します。
比較は key= で行います。これのデフォルト値は equal?
です。
その場で更新を行うバージョン alist-delete!
は元の
alist を変更してしまうことがあります。
引数順についての注意: 残念なことに、この手続きはalistを最初の引数に取るという
原則から外れています。この手続きはSRFI-1で定義され、それはassoc
の引数順に合わせました。
alist1 alist2 … に含まれている全てのキーを含むような
新たな連想リストを返します。同じキーを持つエントリが複数の入力連想リストに
含まれていた場合は、その値はreducerを用いて次のとおり計算されます:
入力に含まれる同じキーを持つエントリの値が順にx、y、zであったとすると、
結果の連想リストの値は
(reducer x (reducer y z)
。
(alist-merge + '((a . 1) (b . 2)) '((c . 3) (a . 4)) '((b . 5))) ⇒ ((a . 5) (b . 7) (c . 3)) (alist-merge string=? append '(("a" 1) ("b" 2) ("c" 3)) '(("b" 4) ("d" 5)) '(("c" 6))) ⇒ (("a" 1) ("b" 2 4) ("c" 3 6) ("d" 5))
結果のエントリの順序は、重複キーのエントリが最初のエントリにマージされていること以外は 入力の連想リストの順序を保存します。
註: この手続きは、それぞれのalist内には重複するキーのエントリが無いことを仮定しています。
ひとつのalist内で同じキーを持つエントリを集めるには、
gauche.collection
のgroup-collection->alist
が使えます
(gauche.collection
- コレクションフレームワーク参照)。
この手続きは、ネストした連想リストを更新するのに使えます。 alistはネストした連想リストです。 keysはキーのリストで、procは1引数を取る手続きです。 まず、keysにあるキーを順に使って、ネストした連想リストから 値が取り出され、procに渡されます。 手続きの戻り値は新しいネストした連想リストで、該当するエントリの値が procの戻り値に置き換えられたものとなります。
(assoc-update-in '((a (b . 1) (c . 2))) '(a c) (cut + <> 1)) ⇒ ((a (b . 1) (c . 3))
エントリの順序は保存されます。元のalistは変更を受けませんが、 戻り値と部分構造を共有するかもしれません。
alistがkeysで指定されるエントリを持たない場合は新たなエントリが 追加されます。新たなエントリは、キーが存在しないシーケンスの先頭に付け加えられます。
(assoc-update-in '((a (b . 1) (c . 2))) '(a d e) (^_ 99)) ⇒ ((a (d (e . 99)) (b . 1) (c . 3)))
default引数は、キーに対応するエントリが存在しなかった場合にprocに
渡されます。省略値は#f
です。
key=引数は2引数を取る手続きで、キーの比較に使われます。
省略された場合はequal?
が使われます。
註: ネストした集合データ構造の一部を破壊的に更新するには、
~
のセッタが便利です(万能アクセサ参照)。
例えばハッシュテーブルのベクタのリスト、から一番奥のハッシュテーブルのエントリを
更新することが出来ます。しかし連想リストはちょっと特殊で、リストと型的に区別
できないため、~
が使えません。連想リストはまた、関数的に使われることも
多いため、専用の更新手続きを用意しました。
以下の手続きは、名前の一貫性のために非推奨となったものです。
assocのような歴史的ないくつかの手続き以外は、現在の連想リスト操作は
alist-*
と命名され、原則として連想リストを第一引数に取ります。
Deprecated.
(assoc-ref alist key default key=)
≡
(alist-ref alist key key= default)
.
省略可能引数の順序に注意。
(assq-ref alist key default)
≡
(alist-ref alist key eq? default)
.
(assv-ref alist key default)
≡
(alist-ref alist key eqv? default)
.
Deprecated.
(rassoc-ref alist val default val=)
≡
(alist-key alist val val= default)
.
省略可能引数の順序に注意。
(rassq-ref alist val default)
≡
(alist-key alist val eq? default)
.
(rassv-ref alist key default)
≡
(alist-key alist val eqv? default)
.
Deprecated.
(assoc-set! alist key key=)
≡ (alist-set! alist key key=)
.
(assq-set! alist key)
≡ (alist-set! alist key eq?)
.
(assv-set! alist key)
≡ (alist-set! alist key eqv?)
.
Deprecated.
alist-adjoin
と同じです。
Deprecated.
assoc-update-in alist keys proc default key=
≡
alist-update-in alist keys proc key= default
省略可能引数の順序に注意。
Gaucheは、拡張ペア (extended pair) と呼ばれる特別なペアを持っています。 これは通常のペアと同じように振る舞いますが、属性リストを付加することができます。 例えば、Gaucheはソースコードの位置情報を保持するのに拡張ペアを使っています。
拡張ペアのcar/cdrへのアクセスにオーバーヘッドは全くありません。
set-car!
とset-cdr!
には少しだけオーバヘッドがあります (できるだけ副作用を避けましょう!)。
内部的に、拡張ペアは通常のペアの倍のメモリを使います。
ペア属性は外部表現には書き出されませんし、copy-list
や、出力リストを
入力リストを元に新たに組み立てる多くのリスト手続きでコピーされません。
つまり、ペア属性は簡単に失われます。「あったら良いけど、必須ではない」という
補助的な情報に使うのが良いでしょう。
また、拡張ペアを使ったコードは、弱参照ハッシュテーブルでエミュレートできるとはいえ、 他のScheme処理系には簡単に移植できないことに留意してください。
objが拡張ペアなら#t
を、そうでなければ#f
を返します。
carとcdrからなる拡張ペアを作って返します。 attrs引数が与えられた場合、 それはペア属性の初期値を指定する連想リストでなければなりません。 デフォルトでは作られる拡張ペアのペア属性は空です。
obj obj2 …からなるリストを作って返しますが、 最初のペアが拡張ペアになっています。二番目以降のペアは通常のペアです。
pairのペア属性を連想リストとして返します。 通常のペアを引数に渡すこともできます。その場合は常に空リストが返ります。
(pair-attributes (extended-cons 'a 'b '((c . d) (e . f)))) ⇒ ((c . d) (e . f)) (pair-attributes (cons 'a 'b) ⇒ ()
pairのペア属性のうち、キーkeyに結びつけられた値を返します。
keyは任意のSchemeオブジェクトで、eq?
で比較されます。
キーに結びついた値がない場合、defaultが与えられていればそれが返され、
与えられていなければエラーが投げられます。
通常のペアをpairに渡すこともできます。その場合、ペア属性は空として扱われます。
拡張ペアpairのペア属性keyに値valueをセットします。 keyとvalueは任意のSchemeオブジェクトです。 pairが拡張ペアでなければエラーが投げられます。
この手続きは既存の連想リスト自体は変更せず、必要に応じてコピーします。 ペア属性は頻繁に変更される運用を想定していません。