カテゴリー:標準ライブラリ
対応Nimバージョン:1.6.0以降
公式レポジトリ:https://github.com/nim-lang/Nim/blob/devel/lib/pure/collections/sequtils.nim
sequtilsとは?
概要
sequtils は、Nimでよく使う seq、配列、文字列などの連続したデータを扱いやすくするための標準ライブラリである。
Nimの system モジュールには、newSeq、@、add、&、in など、基本的なシーケンス操作が最初から用意されている。
sequtils はその上に、より便利な変換、抽出、判定、集計、結合、分割などの機能を追加するモジュールである。
初心者にとって sequtils が重要なのは、複数の値をまとめて処理するコードを短く、読みやすく書けるようになるからである。
たとえば、「数値の一覧から偶数だけ取り出す」「文字列の一覧を全部大文字に変換する」「条件に合うデータが1つでもあるか調べる」「一覧の合計を出す」といった処理は、プログラムの中で何度も出てくる。
これを毎回 for 文だけで書いてもよいが、処理の意図が少し読みにくくなることがある。
sequtils を使うと、map、filter、all、any、foldl などの関数によって、「何をしたいのか」をコード上に表しやすくなる。
特に mapIt、filterIt、allIt、anyIt のような It 系テンプレートは、初心者にも扱いやすい。
これらは it という一時的な変数を使い、短い条件式や変換式を書ける。
たとえば numbers.filterIt(it mod 2 == 0) と書けば、「numbers の中から偶数だけを取り出す」という意味になる。
it はその場で処理中の要素を表す特別な名前であり、自分で宣言する必要はない。
ただし、sequtils は何でも短く書くための魔法ではない。
処理を短くしすぎると、逆に読みにくくなることがある。
最初は for 文で書ける処理を理解し、その後で mapIt や filterIt に置き換えるのがよい。
そうすれば、裏側で何が起きているかを見失わずに使える。
sequtils の使用にNimbleは不要である。
ただし、プロジェクト全体をNimbleで管理している場合でも、標準ライブラリとして普通に import std/sequtils すればよい。
.nimble ファイルの依存関係に sequtils を書く必要はない。
ここを間違えて requires "sequtils" のように書く必要はない。
標準ライブラリはNim本体に含まれるからである。
特徴
sequtils の第一の特徴は、コレクション処理を「目的」で書けることである。
通常の for 文では、「空の seq を作る」「各要素を調べる」「条件に合えば追加する」という手順を順番に書く。
しかし filterIt を使えば、「条件に合うものだけ取り出す」と直接書ける。
これは単に文字数を減らすだけではない。コードを読む人が、処理の目的を理解しやすくなる。
第二の特徴は、seq だけでなく、配列や文字列にも使える関数があることだ。
公式ドキュメントでは、sequtils は名前に seq を含むが、openArray の仕組みにより、シーケンス、文字列、配列にも対応する処理があると説明されている。
たとえば、文字列から特定の文字だけを取り出す処理も filterIt で書ける。
第三の特徴は、関数型プログラミング風の書き方ができることである。
map は変換、filter は抽出、foldl は集約、all と any は条件判定を表す。
これらは他の言語でもよく見られる考え方であり、Nim以外の言語に触れるときにも役立つ。
ただし、Nimでは型が静的に決まるため、変換前と変換後の型、破壊的変更をするかどうか、var が必要かどうかを意識する必要がある。
第四の特徴は、mapIt や filterIt のようなテンプレートにより、短い式で直感的に書ける点である。
map に普通の proc を渡す書き方もできるが、初心者には少し長い。
mapIt(it * 2) のように書けば、「それぞれの要素を2倍にする」という意味がかなり見えやすい。
ただし、it はテンプレート内でだけ使える名前である。通常の場所でいきなり echo it と書いても動かない。
第五の特徴は、メソッド呼び出し構文と相性がよいことである。
Nimでは filter(numbers, 条件) のような書き方だけでなく、numbers.filterIt(条件) のような書き方もできる。
このため、toSeq(1..10).mapIt(it * 2).filterIt(it mod 3 != 0) のように処理をつなげて書ける。
公式ドキュメントでも、メソッド呼び出し構文によって関数の連鎖が可能であると説明されている。
使いどころ
sequtils の代表的な使いどころは、一覧データの変換である。
たとえば、数値の一覧をすべて2倍にしたい、文字列の一覧から長さだけを取り出したい、オブジェクトの一覧から名前だけを取り出したい、といった処理で mapIt が使える。
mapIt は元のデータを変更せず、新しい seq を返すため、「元データを残したまま別の形に変換する」場面に向いている。
次に、条件による抽出である。
ユーザー一覧から有効なユーザーだけを取り出す、商品一覧から在庫があるものだけを取り出す、数値一覧から偶数だけを取り出す、といった処理には filterIt が合う。
filterIt も元のデータを変更せず、新しい seq を返す。元の一覧をそのまま保持しておきたいときに便利である。
条件に合うかどうかを調べるだけなら、allIt や anyIt が使える。
allIt はすべての要素が条件を満たすかを調べる。たとえば、すべての点数が60点以上なら合格、すべてのファイル名が空でないなら処理続行、という判定に使える。
anyIt は1つでも条件を満たす要素があるかを調べる。
たとえば、エラー状態のデータが1つでもあるか、空文字列が混じっていないか、といった確認に使える。
件数を数えたい場合は count や countIt が使える。
count は特定の値が何回出てくるかを数える。
countIt は条件に合う要素の数を数える。たとえば、scores.countIt(it >= 80) と書けば、80点以上の点数がいくつあるかを数えられる。
元の seq を直接変更したい場合は、applyIt や keepItIf が使える。
applyIt は各要素を変換して元の seq に反映する。
keepItIf は条件に合う要素だけを残し、それ以外を削除する。
これらは新しい seq を作らず、元の変数を書き換えるため、var で宣言された変数に対して使う必要がある。ここで let と var の違いに注意すべきである。
データの結合や分割にも使える。
複数の seq をつなげるなら concat、データを複数グループに分けるなら distribute、2つの一覧をペアにするなら zip、ペアの一覧を2つの一覧に戻すなら unzip が使える。
特に zip は、名前一覧と点数一覧を組み合わせるような場面で便利である。
ただし、片方が短い場合は長いほうの余った要素が捨てられるため、データ数が一致しているかは事前に確認したほうがよい。
使い方
まず、もっとも基本的な使い方は import std/sequtils で読み込むことである。
sequtils の関数やテンプレートは、読み込まなければ使えない。
初心者がよく見る undeclared identifier: 'mapIt' や undeclared identifier: 'filterIt' は、多くの場合、インポート忘れである。
import std/sequtils
let numbers = @[1, 2, 3, 4, 5]
let doubled = numbers.mapIt(it * 2)
echo doubled
このコードでは、numbers が元の一覧である。
mapIt(it * 2) は、numbers の各要素を順番に取り出し、その要素を it として扱い、it * 2 の結果を新しい seq に入れる。
つまり、@[1, 2, 3, 4, 5] は @[2, 4, 6, 8, 10] に変換される。
numbers 自体は変更されない。let doubled = ... としているため、変換結果が doubled に入る。
次に、条件に合うものだけを取り出す例である。
import std/sequtils
let numbers = @[1, 2, 3, 4, 5, 6, 7, 8]
let evens = numbers.filterIt(it mod 2 == 0)
echo evens
filterIt は、条件式が true になる要素だけを残す。
it mod 2 == 0 は、「2で割った余りが0」という意味であり、偶数を判定している。
この結果、evens には @[2, 4, 6, 8] が入る。ここでも元の numbers は変更されない。
mapIt と filterIt は組み合わせて使える。
たとえば、1から10までの数を2倍し、その中から3で割り切れないものだけを残すなら次のように書ける。
import std/sequtils
let result = toSeq(1..10)
.mapIt(it * 2)
.filterIt(it mod 3 != 0)
echo result
toSeq(1..10) は、範囲 1..10 を seq に変換する。
範囲はそのままでも for 文で回せるが、mapIt や filterIt で処理をつなげたいときは seq にしておくと扱いやすい。
公式ドキュメントでも、toSeq は for 文で反復できるものをシーケンスに変換するテンプレートとして説明されている。
普通の proc を渡して map や filter を使うこともできる。
import std/sequtils
let numbers = @[1, 2, 3, 4]
let strings = numbers.map(proc (x: int): string =
result = $x
)
echo strings
このコードでは、seq[int] の一覧を seq[string] の一覧に変換している。
$x は、値を文字列に変換するNimの書き方である。
map は入力と出力の型が違ってもよい。
ここでは seq[int] から seq[string] になっている。
mapIt を使うなら、次のようにもっと短く書ける。
import std/sequtils
let numbers = @[1, 2, 3, 4]
let strings = numbers.mapIt($it)
echo strings
ただし、短ければ常に良いわけではない。
複雑な処理を mapIt の中に詰め込みすぎると読みにくくなる。処理が長くなる場合は、先に名前付きの proc を作ってから map に渡すほうがよい。
import std/sequtils
proc toLabel(score: int): string =
if score >= 80:
result = "good"
else:
result = "normal"
let scores = @[95, 70, 82, 60]
let labels = scores.map(toLabel)
echo labels
このコードでは、点数を "good" または "normal" に変換している。処理内容に名前をつけることで、コードの意図が明確になる。
初心者のうちは、無理に短い式へ詰め込まず、「処理に名前をつける」ことを優先したほうがよい。
次に、条件判定の例である。
import std/sequtils
let scores = @[70, 85, 90, 66]
let allPassed = scores.allIt(it >= 60)
let hasExcellent = scores.anyIt(it >= 90)
echo allPassed
echo hasExcellent
allIt は、すべての要素が条件を満たすかを調べる。
この例ではすべて60点以上なので true になる。
anyIt は、1つでも条件を満たす要素があるかを調べる。
この例では90点以上の点数があるので true になる。
allIt と anyIt は、if 文の条件としてもよく使える。
import std/sequtils
let names = @["Alice", "Bob", "Carol"]
if names.allIt(it.len > 0):
echo "すべての名前が入力されている"
else:
echo "空の名前がある"
件数を数えるときは countIt を使う。
import std/sequtils
let scores = @[55, 60, 75, 90, 40, 88]
let passedCount = scores.countIt(it >= 60)
echo passedCount
countIt(it >= 60) は、60点以上の要素を数える。
この例では 4 になる。単純に特定の値が何回あるかを数えるなら count を使う。
import std/sequtils
let numbers = @[1, 2, 2, 3, 2, 4]
echo numbers.count(2)
この結果は 3 である。
count は「この値そのものが何個あるか」を数える。countIt は「この条件を満たすものが何個あるか」を数える。
両者を混同しないこと。
最初に条件を満たす位置を知りたい場合は findIt を使う。
import std/sequtils
let names = @["Alice", "Bob", "Carol", "Dave"]
let index = names.findIt(it.len == 3)
echo index
この場合、長さが3の最初の名前は "Bob" なので、インデックスは 1 になる。Nimのインデックスは0から始まる。
条件に合う要素が見つからない場合、findIt は -1 を返す。
公式ドキュメントでも、findIt は条件を満たす最初の要素のインデックス、または -1 を返すと説明されている。
元の seq を直接変更したい場合は applyIt を使う。
import std/sequtils
var numbers = @[1, 2, 3, 4]
numbers.applyIt(it * 10)
echo numbers
このコードでは、numbers 自体が @[10, 20, 30, 40] に変わる。
ここで numbers は var でなければならない。
let numbers = ... と書くと、変更できない値になるためエラーになる。
また、applyIt は元の要素と同じ型を返す必要がある。
seq[int] に対して applyIt($it) のように string を返す処理はできない。
型を変えたい場合は mapIt を使うべきである。
import std/sequtils
let numbers = @[1, 2, 3, 4]
let strings = numbers.mapIt($it)
echo strings
条件に合うものだけを元の seq に残したい場合は keepItIf を使う。
import std/sequtils
var numbers = @[1, 2, 3, 4, 5, 6]
numbers.keepItIf(it mod 2 == 0)
echo numbers
このコードでは、numbers 自体が @[2, 4, 6] に変わる。
filterIt は新しい seq を返すが、keepItIf は元の seq を変更する。
この違いは重要である。元のデータを残したいなら filterIt、元のデータを減らしてよいなら keepItIf を使う。
重複を取り除くなら deduplicate を使う。
import std/sequtils
let numbers = @[1, 1, 3, 2, 3, 4, 2]
let uniqueNumbers = numbers.deduplicate()
echo uniqueNumbers
deduplicate は、重複を取り除いた新しい seq を返す。
公式ドキュメントでは、isSorted = true を指定するとソート済みデータ向けの速い重複削除を使えると説明されている。
逆に言えば、ソートされていないデータに安易に isSorted = true を指定すべきではない。
2つの一覧をペアにしたい場合は zip を使う。
import std/sequtils
let names = @["Alice", "Bob", "Carol"]
let scores = @[90, 75, 88]
let pairs = zip(names, scores)
echo pairs
この結果は、名前と点数のペアになる。pairs の型は、おおまかに言えば seq[(string, int)] である。
Nim 1.1.x以降では zip は名前なしタプルの seq を返す。
必要なら、次のように型注釈をつけて名前付きタプルとして受けることもできる。
import std/sequtils
let names = @["Alice", "Bob", "Carol"]
let scores = @[90, 75, 88]
let pairs: seq[tuple[name: string, score: int]] = zip(names, scores)
for p in pairs:
echo p.name, ": ", p.score
ペアの一覧を2つの一覧に戻すなら unzip を使う。
import std/sequtils
let pairs = @[(1, "one"), (2, "two"), (3, "three")]
let (numbers, words) = pairs.unzip()
echo numbers
echo words
unzip は、ペアの一覧を2つの一覧に分ける。
この例では、numbers が @[1, 2, 3] になり、words が @["one", "two", "three"] になる。
公式ドキュメントでは、unzip はペアのシーケンスを2つのシーケンスに分割するテンプレートとして説明されている。
複数の seq をまとめるなら concat を使う。
import std/sequtils
let a = @[1, 2]
let b = @[3, 4]
let c = @[5, 6]
let result = concat(a, b, c)
echo result
concat では、すべての seq の要素型が同じでなければならない。
たとえば、seq[int] と seq[string] をそのまま結合することはできない。
型が違うものを一緒に扱いたい場合は、先に文字列へ変換する、共通のオブジェクト型を作る、タプルにするなど、設計を考える必要がある。
データを複数グループに分けたい場合は distribute を使う。
import std/sequtils
let numbers = @[1, 2, 3, 4, 5, 6, 7]
let groups = numbers.distribute(3)
echo groups
distribute(3) は、要素を3つのグループに分ける。
公式ドキュメントでは、spread = true の場合、余りをできるだけ均等に分配し、スレッドプールのように作業単位を分ける場面に向くと説明されている。
2次元の seq を作りたい場合は newSeqWith が便利である。
import std/sequtils
var grid = newSeqWith(3, newSeq)
grid[0][0] = 10
grid[2][3] = 99
echo grid
このコードでは、3行4列のような構造を作っている。newSeqWith(3, newSeq) は、「長さ4の seq[int] を3個持つ seq」を作る。
ゲーム開発でマップを作る、表形式のデータを扱う、簡単な盤面を作る、といった場面で使える。
公式ドキュメントでも、newSeqWith は2次元 seq の作成に便利だと説明されている。
合計や連結のような集約処理には foldl を使える。
import std/sequtils
let numbers = @[5, 9, 11]
let total = foldl(numbers, a + b)
echo total
foldl では、a がこれまでの累積結果、b が現在の要素である。
この例では、5 + 9 + 11 が計算され、結果は 25 になる。
ただし、foldl(sequence, operation) の形では、空の seq に対して使うべきではない。
公式ドキュメントでは、空シーケンスではデバッグビルドでアサートされると説明されている。
空の可能性があるなら、初期値付きの foldl(sequence, operation, first) を使うべきである。
import std/sequtils
let numbers: seq[int] = @[]
let total = foldl(numbers, a + b, 0)
echo total
このように初期値 0 を指定すれば、空の seq でも安全に合計を出せる。
初心者は、foldl を覚えるときに「空かもしれないなら初期値をつける」と覚えておくとよい。
注意点
sequtils で最も多いミスは、インポート忘れである。
mapIt、filterIt、allIt、toSeq などを使っているのに import std/sequtils がない場合、Nimコンパイラはその名前を知らない。
その結果、次のようなエラーが出ることがある。
Error: undeclared identifier: 'mapIt'
これは「mapIt という名前がわからない」という意味である。
sequtils をインポートしていないため、Nimコンパイラは mapIt が何であるかを知らない。
これを解決するには、ファイルの先頭に import std/sequtils を追加すればよい。
次に多いのは、=> を使うのに import std/sugar を読み込んでいないミスである。
sequtils の公式ドキュメントでは、匿名関数を短く書く方法として std/sugar の => マクロも紹介されている。
たとえば次のようなコードを書くなら、import std/sugar が必要である。
import std/sequtils
import std/sugar
let numbers = @[1, 2, 3]
let doubled = numbers.map(x => x * 2)
echo doubled
ここで import std/sugar を忘れると、次のようなエラーが出る。
Error: undeclared identifier: '=>'
import std/sugar がないと、=> が使えずエラーになる。
初心者のうちは、=> を無理に使わず、mapIt や filterIt から始めるほうがよい。
it の使い方にも注意が必要である。
it は mapIt、filterIt、allIt、anyIt、countIt、findIt、applyIt、keepItIf などのテンプレート内で使える特別な名前である。
通常の変数としてどこでも使えるわけではない。
import std/sequtils
let numbers = @[1, 2, 3]
let doubled = numbers.mapIt(it * 2)
echo doubled
上のコードは正しい。mapIt の中で it を使っている。
しかし、次のコードはエラーになる。
import std/sequtils
echo it
この場合、it は宣言されていないため、未宣言識別子のエラーになる。
it は「今処理している要素」を表すための名前であり、It 系テンプレートの引数式の中でだけ意味を持つ。
mapIt と applyIt の違いも重要である。
mapIt は新しい seq を作る。元の seq は変わらない。
一方、applyIt は元の seq を直接書き換える。そのため、applyIt を使う対象は var でなければならない。
import std/sequtils
let numbers = @[1, 2, 3]
numbers.applyIt(it * 2)
このコードは、numbers が let で宣言されているため失敗する。
修正するなら次のようにする。
import std/sequtils
var numbers = @[1, 2, 3]
numbers.applyIt(it * 2)
echo numbers
また、applyIt は元の要素と同じ型を返す必要がある。seq[int] に対して文字列を返そうとすると型エラーになる。
import std/sequtils
var numbers = @[1, 2, 3]
numbers.applyIt($it)
これは、seq[int] の場所に string を入れようとしているため不適切である。
型を変えたいなら、次のように mapIt を使う。
import std/sequtils
let numbers = @[1, 2, 3]
let strings = numbers.mapIt($it)
echo strings
filterIt と keepItIf の違いも同じ考え方である。
filterIt は新しい seq を返し、keepItIf は元の seq を変更する。
元のデータを残したい場合に keepItIf を使うと、あとから元データが必要になったときに困る。
初心者は、まず filterIt を使い、破壊的変更が必要な場面だけ keepItIf を選ぶとよい。
delete の使い方にも注意が必要である。
古い形式の delete(s, first, last) は非推奨であり、現在は delete(s, first..last) のようにスライスを渡す形式を使うべきである。
公式ドキュメントでも、delete[T](s: var seq[T]; first, last: Natural) は非推奨で、delete(s, first..last) を使うよう示されている。
import std/sequtils
var numbers = @[10, 11, 12, 13, 14]
numbers.delete(1..2)
echo numbers
この結果は @[10, 13, 14] になる。範囲外のスライスを指定すると IndexDefect が発生することがある。
公式ドキュメントにも、範囲外を含むスライスでは IndexDefect が発生すると記載されている。
配列や seq のインデックスは0から始まるため、最後の要素のインデックスは len - 1 である。
len そのものをインデックスに使うと範囲外になる。
zip は、2つの入力の長さが違う場合、短いほうに合わせる。
長いほうの余った要素は結果に入らない。公式ドキュメントでも、一方のコンテナが短い場合、長いほうの残りは捨てられると説明されている。
たとえば、名前が3つ、点数が2つしかない場合、3人目の名前は結果に入らない。
import std/sequtils
let names = @["Alice", "Bob", "Carol"]
let scores = @[90, 80]
echo zip(names, scores)
この結果は @[("Alice", 90), ("Bob", 80)] になる。Carol は結果に入らない。
データの数が一致しているかは、事前に len を使って確認したほうがよい。
if names.len != scores.len:
echo "名前と点数の数が一致していない"
else:
echo zip(names, scores)
deduplicate の isSorted = true は、ソート済みのデータに対して使う指定である。
ソートされていないデータに対して高速化だけを期待して指定すると、意図した重複削除にならない可能性がある。
データがソート済みだと確信できないなら、初期値の false のまま使うのが安全である。
foldl と foldr は、初心者にはやや難しい。
特に空の seq に対して初期値なしで使うのは避けるべきである。
foldl(numbers, a + b) は、最初の要素を初期値のように使うため、要素が1つもない場合に困る。
空の可能性がある場合は、foldl(numbers, a + b, 0) のように初期値を指定する。
mapIt や filterIt を長くつなげすぎるのも注意点である。
たとえば、変換、抽出、さらに変換、さらに集計という処理を1行に詰め込むと、読み手が追いにくくなる。
自分以外も読むコードを書くときは、Nimでは中間結果に名前をつけられるため、無理に1行にまとめないほうがよい。
import std/sequtils
let numbers = toSeq(1..20)
let doubled = numbers.mapIt(it * 2)
let notMultipleOfThree = doubled.filterIt(it mod 3 != 0)
let total = foldl(notMultipleOfThree, a + b, 0)
echo total
このように段階ごとに名前をつけると、処理の流れが見えやすくなる。慣れてからチェーンにすればよい。
最後に、sequtils は便利だが、巨大なデータを処理する場合は新しい seq を何度も作る点に注意が必要である。
mapIt や filterIt は基本的に新しい seq を返す。大量データを何段階も変換すると、メモリ使用量が増えることがある。
大規模処理では、単純な for 文、イテレータ、必要最小限の中間データを使う設計も検討するべきである。
型・関数・API
sequtils のAPIは、大きく分けると、
判定系、変換系、抽出系、破壊的変更系、集約系、結合・分割系、テンプレート系、イテレータ系、マクロ系に分けられる。
公式ドキュメント上では、addUnique、all、any、apply、concat、count、cycle、deduplicate、delete、distribute、filter、insert、keepIf、map、max、maxIndex、min、minIndex、minmax、repeat、unzip、zip などのprocが掲載されている。
さらに、filter と items のイテレータ、
mapLiterals などのマクロ、
allIt、anyIt、applyIt、countIt、filterIt、findIt、foldl、foldr、keepItIf、mapIt、newSeqWith、toSeq などのテンプレートが用意されている。
基本の型
sequtils そのものが新しい中心的なデータ型を提供するというより、既存の seq[T]、array、string、openArray[T] に対して便利な操作を提供するモジュールである。
openArray[T] は、固定長配列や seq を同じように受け取るためによく使われる型である。
初心者は、まず seq[T] を主な対象として理解すればよい。
- seq[T]:同じ型の値を複数並べて持つ、可変長のシーケンスである。
- array[N, T]:長さがコンパイル時に決まる固定長配列である。
- string:文字の並びであり、一部のsequtils関数では文字の集まりとして扱える。
- openArray[T]:
seqやarrayを共通して受け取るための引数型である。 - tuple:
zipやunzipでよく使われる、複数の値をひとまとめにする型である。
判定系API
判定系APIは、「条件を満たすか」「条件を満たすものがあるか」「条件に合う位置はどこか」を調べるために使う。
all や any は proc を渡して使い、allIt や anyIt は it を使って短く書ける。
- all(s, pred):すべての要素が条件を満たすかを調べる。
- any(s, pred):1つでも条件を満たす要素があるかを調べる。
- allIt(s, pred):
itを使って、すべての要素が条件を満たすかを短く書ける。 - anyIt(s, pred):
itを使って、1つでも条件を満たすかを短く書ける。 - findIt(s, predicate):条件を満たす最初の要素のインデックスを返し、見つからない場合は
-1を返す。
件数カウント系API
件数カウント系APIは、特定の値や条件に合う要素数を数えるために使う。
完全一致なら count、条件指定なら countIt がわかりやすい。
- count(s, x):コンテナ内に値
xが何回出てくるかを数える。 - countIt(s, pred):
itを使って、条件に合う要素の数を数える。
変換系API
変換系APIは、各要素を別の値へ変えるために使う。map と mapIt は元のデータを変更せず、新しい seq を返す。
型を変えたいときにも使える。
- map(s, op):各要素に関数
opを適用し、結果を新しいseqとして返す。 - mapIt(s, op):
itを使って、各要素の変換を短く書ける。 - toSeq(iter):範囲やイテレータなど、反復可能なものを
seqに変換する。 - newSeqWith(len, init):指定した長さの
seqを作り、各要素をinitで初期化する。 - mapLiterals(constructor, op, nested = true):リテラルを含む構造に対して、コンパイル時に変換処理を適用するマクロである。
抽出系API
抽出系APIは、条件に合う要素だけを取り出すために使う。
filter と filterIt は新しい seq を返す。
元の seq を変更したい場合は keepIf や keepItIf を使う。
- filter(s, pred):条件を満たす要素だけを集めた新しい
seqを返す。 - filterIt(s, pred):
itを使って、条件抽出を短く書ける。 - filter iterator:条件を満たす要素を順番に
yieldするイテレータである。 - keepIf(s, pred):条件を満たす要素だけを元の
seqに残す。 - keepItIf(varSeq, pred):
itを使って、元のseqに残す条件を短く書ける。
破壊的変更系API
破壊的変更系APIは、元の seq を直接変更する。let ではなく var が必要になることが多い。
元データを残したい場合は、これらではなく mapIt や filterIt を使うべきである。
- apply(s, op):各要素に処理を適用し、場合によっては元のコンテナを直接変更する。
- applyIt(varSeq, op):
itを使って、元のseqの各要素を直接変換する。 - insert(dest, src, pos = 0):
srcの要素をdestの指定位置に挿入する。 - delete(s, slice):指定した範囲の要素を元の
seqから削除する。 - addUnique(s, x):値
xがまだ存在しない場合だけseqに追加する。
結合・分割系APIは、複数の seq をまとめたり、1つの seq を複数に分けたり、2つの一覧をペアにしたりするために使う。
- concat(seqs):複数の
seqを結合し、新しいseqを返す。 - distribute(s, num, spread = true):1つの
seqを指定数のグループに分ける。 - zip(s1, s2):2つのコンテナを対応する要素同士のタプルにまとめる。
- unzip(s):2要素タプルの
seqを、2つのseqに分解する。
重複・繰り返し系API
重複や繰り返しに関するAPIは、テストデータ作成や一覧データの整形で役立つ。
- deduplicate(s, isSorted = false):重複を取り除いた新しい
seqを返す。 - repeat(x, n):値
xをn回繰り返したseqを作る。 - cycle(s, n):コンテナ
sの要素列をn回繰り返したseqを作る。
最小値・最大値系API
最小値や最大値を扱うAPIは、数値の一覧だけでなく、比較関数を渡すことで文字列長や独自基準による比較にも使える。
- min(x, cmp):比較関数
cmpに基づいて最小の要素を返す。 - max(x, cmp):比較関数
cmpに基づいて最大の要素を返す。 - minIndex(s):最小値のインデックスを返す。
- maxIndex(s):最大値のインデックスを返す。
- minIndex(s, cmp):比較関数に基づいて最小要素のインデックスを返す。
- maxIndex(s, cmp):比較関数に基づいて最大要素のインデックスを返す。
- minmax(x):最小値と最大値をタプルで返す。
- minmax(x, cmp):比較関数に基づいて最小値と最大値をタプルで返す。
集約系API
集約系APIは、複数の要素を1つの値にまとめるために使う。
合計、文字列連結、独自の集計処理などに使えるが、初心者には少し難しいため、最初は初期値付きの foldl から覚えるとよい。
- foldl(sequence, operation):左から右へ要素を畳み込み、1つの値にまとめる。
- foldl(sequence, operation, first):初期値
firstを指定して、左から右へ畳み込む。 - foldr(sequence, operation):右から左へ要素を畳み込み、1つの値にまとめる。
イテレータ補助API
イテレータ系APIは、値を一度にすべて seq にせず、順番に処理したいときに関係する。
初心者は最初から深く使い込む必要はないが、for 文と相性がよい処理だと理解しておけばよい。
- filter iterator:条件を満たす要素を順番に返すイテレータである。
- items(xs: iterator):クロージャイテレータが返す要素を反復できるようにする。
参考文献
以下は本記事を執筆するにあたり参考にした文献や資料である。
- Nim公式ドキュメント std/sequtils:https://nim-lang.org/docs/sequtils.html
- Nim標準ライブラリ一覧:https://nim-lang.org/docs/lib.html
- Nim公式GitHubリポジトリ:https://github.com/nim-lang/Nim
- sequtils ソースコード:https://github.com/nim-lang/Nim/blob/devel/lib/pure/collections/sequtils.nim
- Nim公式ドキュメント std/sugar:https://nim-lang.org/docs/sugar.html
- Nim公式ドキュメント std/algorithm:https://nim-lang.org/docs/algorithm.html