本文へ移動
Hikari 仕様

std:set — 順序付き集合 (OrderedSet)

標準ライブラリモジュール (index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../language-spec.md を参照。

import s := "std:set"値を要素にできる順序付き集合 OrderedSet のコンストラクターを slot に持つ namespace object を s に束縛する。要素は Comparable (language-spec.md §9.4) であればよく、重複は持たない。List.contains の O(n) メンバーシップに対し、O(log n) の包含判定と集合演算 (和・積・差) を提供する。

std:fs / std:term と違い I/O を伴わず、純粋・同期 (Future を返さない)。すべてのコンストラクター・メソッドは即値を返す。設計は map.md と対称 (要素は値のみで compare をキー基準とする)。

import s := "std:set"

a := s.of([3, 1, 2, 1])
a.to_list()         #> [1, 2, 3]            (重複除去・compare 昇順)
a.has(2)            #> true
a.length()          #> 3

a.union(s.of([3, 4])).to_list()         #> [1, 2, 3, 4]
a.intersect(s.of([2, 3, 9])).to_list()  #> [2, 3]
a.difference(s.of([2])).to_list()       #> [1, 3]

以下では import s := "std:set" で束縛したものとして s.of 等で記す。

1. 要素の制約 — Comparable と同一性

map.md §2 のキーと同じ規律が要素に適用される。

  • 要素は Comparable (language-spec.md §9.4)。非 Comparable を渡すと呼び出し位置で Error (バグ層、language-spec.md §16)。
  • 同一性は compare: a.compare(b) == Equal の 2 要素は同一とみなし、集合は一方だけを保持する (== のオーバーロードとは独立)。
  • 1 つの集合の要素は相互に比較可能でなければならない。異型混在 (Int と String 等) は compare の異型比較が panic する (language-spec.md §9.4) ため Error。実用上、1 集合は単一の Comparable 型に揃える。
  • Float 要素の順序は全順序 (total_cmp 相当)。

List を「重複を許す順序付き列」、OrderedSet を「重複を許さない値の集合」として使い分ける。

OrderedSetCopyable である (../language-spec.md の複製の節)。不透明型だが不変な値なので、複製は Int を複製するのと同じ意味を持つ。

2. namespace コンストラクター

import s := "std:set" で得られる closed namespace の slot:

名前 意味
empty s.empty 空の OrderedSet (値スロット。呼び出さず参照する)
of s.of(xs) List xs から構築。重複は除去。xs 非 List、要素が非 Comparable のときは Error

本モジュールは効果を1 つも持たない../language-spec.md §17.9)。each / map / filter / fold は渡したブロックの効果を呼び出し元へ透過させる(EffectConduit)。

OrderedSet 型は std:set型メンバーとして export され、型注釈に書ける (language-spec.md §13time.md §1 と対称)。x: s.OrderedSet(Int) と修飾参照するか、import { OrderedSet } := "std:set" で取り出して x: OrderedSet(Int) と書く。不透明型であり内部表現 (平衡木) は露出しない。

要素型 e は型引数である: OrderedSet(e)List(T) と同じ形の型構築子で、first()Option(e)to_list()List(e) になる (§3.1 のシグネチャ表)。規律は map.md §3.1 と同じ — 型引数は省略できず (x: s.OrderedSettype-arity-mismatch)、要素型を問わない位置は OrderedSet(Any) と明示で書く。要素型は of の引数から決まり、empty[]List(Never) であるのと同じく OrderedSet(Never) で、注釈や最初の insert で確定する。不変な値で要素を差し替える経路が無いので e共変

Comparable は型構築子 OrderedSet(e) の境界ではなく、e を取るシグネチャの境界である — of の引数と、has / contains / insert / remove と集合演算の受け手が (e: Comparable) を持ち (§3.1)、確実に非 Comparable な要素型 (List・Tuple・enum のタグ・compare を持たない不透明型) はその位置で type-mismatch になる (static-analysis.md §3.2 の非充足)。record と関数は幅部分型ゆえ静的には「不明」で素通しし、§1 の実行時 Error が backstop になる。実行時の照合は List(T) と同じく要素を走査する。

3. メソッド

OrderedSeta に生えるメソッド。すべて非破壊で、変更系・集合演算は新しい OrderedSet を返す (元の a は不変)。

名前 意味
has / contains a.has(x) 要素 x が存在すれば truex 非 Comparable は Errorcontains は別名 (List の contains と語を合わせる)
insert a.insert(x) x を加えた新 OrderedSet。既存なら内容同一の新値
remove a.remove(x) x を除いた新 OrderedSet。無ければ内容同一の新値
length a.length() 要素数 (Int)
is_empty a.is_empty() 空なら true
union a.union(b) 和集合 a ∪ b (新 OrderedSet)
intersect a.intersect(b) 積集合 a ∩ b
difference a.difference(b) 差集合 a − b (a にあり b に無い要素)
is_subset a.is_subset(b) a ⊆ b なら true
first a.first() 最小要素を Some(x)、空なら None
last a.last() 最大要素を Some(x)、空なら None
each a.each {x | …} compare 昇順に各要素へ適用 (戻り ())。break/continue 不可
map a.map {x | x'} 各要素を変換した新 OrderedSet。結果は重複除去・再ソートされる
filter a.filter {x | Bool} blocktrue を返す要素だけの新 OrderedSet
fold a.fold(init) {acc, x | …} compare 昇順の左畳み込み。空なら init
to_list a.to_list() 要素を compare 昇順の List で返す (Sequenceprelude.md §6.6 と整合)

length / is_empty / first / last / to_list は zero-arg のため a.length! のように ! 形でも書ける。

3.1 静的なシグネチャ

受け手 OrderedSet(e) に対する各メソッドの静的型 (hover に出る形。static-analysis.md §2.2 のとおり実引数の照合はこの型に対して行う)。e は受け手から確定し、fmap のブロックの結果型から確定する。

名前 シグネチャ
empty OrderedSet(Never) ([] と同じ規律。注釈や最初の insert で確定)
of { List((e: Comparable)) | OrderedSet(e) }
has / contains { (e: Comparable) | Bool }
insert / remove { (e: Comparable) | OrderedSet(e) }
length { | Int }
is_empty { | Bool }
union / intersect / difference { OrderedSet(e) | OrderedSet(e) }
is_subset { OrderedSet(e) | Bool }
first / last { | Option(e) }
each { { e | Any } | Unit }
map { { e | f } | OrderedSet(f) }
filter { { e | Bool } | OrderedSet(e) }
fold { acc, { acc, e | acc } | acc }
to_list { | List(e) }
compare { OrderedSet(e) | Ordering }

(e: Comparable) の境界は受け手の型引数にもかかる — e を引数に取るメソッド (has / contains / insert / remove、集合演算、is_subsetcompare) は受け手の e が確実に非 Comparable なら type-mismatch: receiver of insert expects OrderedSet((e: Comparable)), got OrderedSet(List(Int)) で落ちる。要素を比べない length / is_empty / first / last / each / map / filter / fold / to_list は鳴らない。

a := s.of [1, 2, 3, 4]

(a.filter { x | x > 2 }).to_list()  #> [3, 4]
(a.map { x | x * 10 }).to_list()  #> [10, 20, 30, 40]
a.fold 0 { acc, x | acc + x }  #> 10
a.is_subset (s.of [1, 2, 3, 4, 5])  #> true

4. 集合演算

  • union / intersect / difference / is_subset は両辺 OrderedSet を要求し、非 OrderedSet は Error
  • 演算子 (+ / -) はオーバーロードしない (time.md §1 と同方針 — 不透明値型は演算子を増やさず名前付きメソッドで操作する)。和・積・差はそれぞれ union / intersect / difference メソッドで行う。
a := s.of [1, 2, 3]
b := s.of [3, 4]
(a.union b).to_list()  #> [1, 2, 3, 4]
(a.difference b).to_list()  #> [1, 2]

5. 等価と順序 (== / compare)

OrderedSet値等価を持ち、要素同一性を compare で定める (map.md §6 と対称)。

  • ==: a == b は要素集合が一致 (compare 同一) するとき true。構築順に依らない。
  • compare: 要素を compare 昇順に並べた列の辞書式順序Ordering を返す。< <= > >= がこれを経由して効き、OrderedSet別の OrderedSet/SortedMap の要素/キーにネストできる (集合の集合が作れる)。
s.of [1, 2, 3] == s.of [3, 2, 1, 1]  #> true

6. エラーモデル

map.md §7 と同じ 2 層。

  1. Option を返す: first / last。空なら None
  2. Error (バグ層、language-spec.md §16):
    • 要素が 非 Comparable
    • 1 つの集合で 異型要素を混在 (compare の異型比較が panic)
    • of非 List を渡す
    • union / intersect / difference / is_subset の相手が OrderedSet でない

7. 計算量

compare 昇順に保った不変 slice で実装する (コピーオンライト、map.md §8 と対称)。要素数 n に対し:

  • has / first / last: O(log n) (二分探索)
  • insert / remove: O(n) (不変ゆえ slice をコピー)
  • union / intersect / difference / is_subset: O(m log n + (n+m)) 程度 (m は相手の要素数)
  • length / each / map / filter / fold / to_list: O(n)

ハッシュではなく順序 (compare) を採る理由は map.md §8 と同じ (compare が既にあり Hashable が無い・反復順が決定的・参考言語の主流)。平衡木への置き換えは内部実装の最適化余地。