カテゴリー:文字列処理
対応Nimバージョン:1.0.0以降
公式レポジトリ:https://github.com/nim-lang/Nim
Strutilsとは?
概要
strutils は、Nim の文字列処理で最初に覚えるべき標準モジュールのひとつである。
公式説明でも、system にある基本機能を土台にしつつ、文字列向けの追加機能を proc・iterator・template の形で提供するモジュールだとされている。
実際に、join、splitLines、split、indent、repeat のような日常的な操作が最初から揃っており、ログ整形、CSV風文字列の分解、入力値の整形、簡易パースといった作業を外部依存なしで書ける。
さらに、このモジュールは JavaScript ターゲットでも利用可能である。
このモジュールが便利なのは、単に文字列をいじれるからではない。
文字列の見た目を整える処理だけでなく、parseInt、parseFloat、parseBool、parseEnum のような「文字列を別の型へ変換する処理」や、formatFloat、formatSize のような「値を文字列として見やすくする処理」まで同居している点にある。
つまり strutils は、文字列の前処理、変換、出力整形を一つの場所でかなり広く担当する。
ただし、何でも strutils に任せればよいわけではない。
公式ドキュメントでも、文字列補間なら strformat、Unicode の大小変換なら unicode、より低レベルの字句解析なら parseutils と、役割の分担が明示されている。
ここを雑に混ぜると、初心者は「なぜ日本語が期待どおり小文字化されないのか」「なぜこの処理だけ別モジュールなのか」で詰まりやすい。
strutils はあくまで文字列処理の中核であり、周辺モジュールとの境界を理解して使うのが大事である。
特徴
strutils の強みのひとつは、操作の幅が広いのに覚え始めやすいことである。
検索系なら find / rfind、
分割系なら split / splitLines / splitWhitespace、
置換系なら replace / replaceWord / multiReplace、
整形系なら strip / indent / repeat、
変換系なら parseInt / parseFloat / parseBool、
表示系なら format / formatFloat / formatSize と、必要になりやすい処理が一通り揃っている。
最初のうちは「文字列処理で困ったらまず strutils を見る」でだいたい外さない。
二つ目の強みは、メソッド呼び出し風の書き方と相性がよいことである。
公式ドキュメントでも、"867-5309".split('-').map(parseInt) や "Beetlejuice".indent(1).repeat(3).strip のように、処理を左から右へつないで読める例が示されている。
初心者にとっては、この「前の結果に続けて次の処理を書く」感覚がかなり理解しやすい。
処理の流れが目で追いやすく、途中で何をしているのか見失いにくい。
三つ目の強みは、ASCII 専用の処理と Unicode 対応の処理の境界が比較的明確なことである。
たとえば toLowerAscii は A-Z にしか効かないと明記されており、Unicode 全般には unicode.toLower を使うべきだと公式に案内されている。
つまり、strutils は「とりあえず何でも文字列処理」ではなく、どこまでを軽量な ASCII 処理として担当し、どこからを別モジュールに委ねるかが整理されている。
これは一見不便に見えて、実際には誤用を減らす設計である。
使いどころ
一番わかりやすい使いどころは、ユーザー入力やテキストデータの前処理である。
たとえば前後の空白を削るなら strip、末尾の改行だけ落とすなら stripLineEnd、末尾の特定文字列だけ削るなら removeSuffix、空白区切りで単語を取り出すなら splitWhitespace が使える。
設定ファイルの1行、CLI入力、ログ行、フォーム値など、「まだ信用してはいけない文字列」をまず整える段階で非常に役に立つ。
次に多いのは、区切り文字を含むデータの分解である。
カンマ区切り、スペース区切り、複数行テキストの行分割などは split や splitLines で扱いやすい。
特に splitLines は CR、LF、CR-LF の各改行を扱えるので、OS差を強く意識せずに複数行文字列を処理しやすい。
短いテキスト処理なら、わざわざ重い仕組みを持ち込まず strutils だけで済む場面が多い。
三つ目は、文字列から型への変換である。
"42" を int にしたいなら parseInt、"3.14" を float にしたいなら parseFloat、"on" や "yes" を bool にしたいなら parseBool が使える。
小さなアプリ、教材コード、設定値読み込みでは、入力が最初は文字列で届くことが多いので、この系統はかなり出番が多い。
ここを押さえておくと、「入力を受け取るだけのコード」から一歩進んで、「受け取った文字列を意味ある値にするコード」が書けるようになる。
四つ目は、表示用の整形である。
小数の表示桁を調整する formatFloat、容量を KiB や MiB のように見やすくする formatSize、書式文字列へ値を埋め込む format や % は、結果を人間に見せる場面で便利である。
内部計算と表示形式は分けて考えるべきなので、出力直前に strutils を使って見せ方を整える、という使い方はかなり筋がよい。
五つ目は、識別子や名称の扱いである。
validIdentifier は文字列が識別子として妥当かを確認するために使え、nimIdentNormalize は Nim 識別子向けの正規化、normalize は一般文字列向けの簡易正規化に使える。
ただし normalize は Nim 識別子の正規化には使うべきではないと明記されているので、ここは使い分けが必要である。
名前に見える文字列をどう扱うかは、地味だが後で効いてくる。
使い方
まずは、最小限の使い方からである。
現在の公式ドキュメントでは import std/strutils の形が使われている。
これを書いたうえで、文字列を整形したり分割したりする。
import std/strutils
let raw = " Nim,Python,Rust "
let cleaned = raw.strip()
let langs = cleaned.split(',')
let title = langs.join(" / ")
echo cleaned
echo title
echo "HELLO".toLowerAscii()
echo "ab".repeat(3)
このコードの見方は順番に追えばよい。raw は元の文字列で、前後に空白が入っている。
strip() は前後の空白を削った新しい文字列を返す。
次の split(',') はカンマで区切って seq[string] を返す。
さらに join(" / ") で、その配列を " / " でつなぎ直している。
最後の toLowerAscii() は ASCII 範囲の英大文字を小文字にし、repeat(3) は同じ文字列を3回繰り返す。
つまり「整える → 分ける → つなぐ → 表示する」という、文字列処理の基本の流れをそのまま書いている。
次は、文字列を数値や真偽値へ変換する例である。
この系統は初心者がかなりつまずきやすい。
理由は単純で、見た目が数字でも、型としてはただの文字列だからである。
変換に失敗すると ValueError が出るので、最初のうちは try / except も一緒に覚えたほうが安全である。
import std/strutils
let ageText = "42"
let enabledText = "on"
let rateText = "3.14"
try:
let age = parseInt(ageText)
let enabled = parseBool(enabledText)
let rate = parseFloat(rateText)
echo age
echo enabled
echo rate
except ValueError:
echo "変換できない値が含まれている"
ここでは parseInt が整数、parseBool が真偽値、parseFloat が小数へ変換している。
parseBool は y, yes, true, 1, on を真、n, no, false, 0, off を偽として扱い、それ以外なら失敗する。
したがって、ユーザー入力や設定ファイルを読むときは、「変換できる前提」で書かないほうがよい。
入力値はいつでも壊れている可能性がある、と考えておいたほうが堅い。
次は、結果の見せ方を整える例である。
内部では数値のまま持ち、表示するときだけ整形する。これは実務でも教材でも重要な考え方である。
import std/strutils
let x = 123.456
let bytes = 4096'i64
echo formatFloat(x, ffDecimal, 2)
echo formatFloat(x, ffScientific, 3)
echo formatSize(bytes)
echo formatSize(bytes, includeSpace = true)
echo format("Name: $1, Score: $2", "Nim", 95)
formatFloat は浮動小数点を文字列化する関数で、ffDecimal なら小数点以下の桁数、ffScientific なら指数表記を選べる。formatSize はバイト数を KiB などへ丸めて見やすくする。
format は % と同系統の書式埋め込みで、引数は自動的に文字列化される。
画面出力やログ整形で見た目を整えたいときに向いている。
最後に、strutils はメソッド呼び出し風に連結して書ける。
これを使うと、途中変数を増やさずに流れを書ける。
もちろん、長くなりすぎるなら分割したほうが読みやすいが、2〜3段くらいならかなり便利である。
import std/strutils
let slug = " HELLO WORLD ".strip().toLowerAscii().replace(" ", "-")
echo slug
この1行は、前後空白を削り、小文字化し、空白をハイフンへ置き換えている。
読み方は左から右でよい。こういう書き方に慣れると、文字列処理の見通しがかなり良くなる。
注意点
最初の注意点は、strutils の関数は自動では入ってこないことである。
Nim で暗黙に入るのは system モジュールであり、strutils は別モジュールとして明示的に import する必要がある。
したがって split や parseInt を使うのに import std/strutils を書いていないと、識別子未定義の形で止まるのは自然である。
ここは「文字列だから標準で全部使える」と思わないほうがよい。
二つ目は、ASCII 専用関数を Unicode 文字列へそのまま期待しないことである。
toLowerAscii や isUpperAscii は、名前の通り ASCII 前提である。
日本語やアクセント付き文字まで含めて大小変換したいなら、unicode 側の関数を使う必要がある。
英字だけを扱うつもりで書いたのに、後で多言語文字列が来て壊れる、というのはありがちな事故である。
三つ目は、parseInt、parseFloat、parseBool、parseHexInt などの parse 系は、入力が不正なら ValueError を投げることである。
逆に言えば、変換前に入力を信じ切ってはいけない。
設定ファイル、CLI引数、フォーム入力、ネットワーク経由の文字列は、必ず失敗しうる前提で扱うべきである。
特に parseBool は許容語が決まっているので、"enabled" のような自然語をそのまま食べてくれるわけではない。
四つ目は、似た関数でも挙動が違うことである。
たとえば strip は新しい文字列を返すが、removeSuffix や stripLineEnd は var string を直接書き換える。
ここを混同すると、「代入しなくても変わる関数」と「代入しないと結果を失う関数」が混ざって混乱する。
また multiReplace は左から右へ走査し、置換の順番が結果に影響する。
文字列処理は見た目以上に仕様差が効くので、似た名前でも雑に同一視しないほうがよい。
五つ目は、normalize の用途を取り違えないことである。
これは文字列を小文字化し、_ を除去する簡易正規化だが、公式には Nim 識別子名の正規化に使うべきではないと明記されている。
識別子として扱いたいなら nimIdentNormalize や validIdentifier のほうを見るべきである。
名前が似ているので危ないが、ここはきっちり分ける。
型・関数・API
strutils は文字列を扱うためのモジュールであるため、最初に覚えるべきなのは「どんな型があるか」よりも、「どんな場面でどの関数を使うか」である。
実際、このモジュールは文字列の検索、分割、置換、整形、型変換といった処理を幅広く担当している。
そのため、関数を一つずつばらばらに覚えるよりも、役割ごとに整理して見るほうが理解しやすい。
まず、strutils には表示形式を切り替えるための列挙型がいくつかある。
これらは日常的に毎回直接書くというより、formatFloat や formatSize などの関数を使うときに一緒に出てくる型である。
初心者の段階では、「文字列をどう見せるかを決めるための補助的な型」と考えておけば十分である。
- BinaryPrefixMode:容量の単位をどの方式で表示するかを決めるための列挙型である。
- FloatFormatMode:小数を通常表記・固定小数点・指数表記など、どの形式で文字列化するかを決めるための列挙型である。
- SkipTable:文字列検索を効率よく行うための内部寄りの型であり、find 系の処理の土台に関わる。
次に重要なのは、文字列を探したり区切ったりする関数群である。
文字列処理では、まず必要な部分を見つけるか、区切り記号で分解することが多い。
たとえば、カンマ区切りのデータを分けたり、複数行テキストを1行ずつ処理したり、ある単語が含まれているか調べたりするときに使う。
strutils の中でも特に出番が多い部分である。
- find:指定した文字や文字列が最初に現れる位置を探すために使う。
- rfind:指定した文字や文字列を後ろ側から探したいときに使う。
- split:指定した区切り文字で文字列を分割し、複数の文字列に分けるために使う。
- rsplit:後ろ側を意識して文字列を分割したいときに使う。
- splitLines:複数行の文字列を1行ずつに分けたいときに使う。
- splitWhitespace:空白ごとに単語を分けたいときに使う。
- tokenize:区切り文字も含めて細かくトークンに分けたいときに使う。
その次に覚えたいのは、文字列を整えたり置き換えたりする関数群である。
入力された文字列には、前後に余計な空白が入っていたり、大文字小文字が不揃いだったり、特定の語句を別の語句に置き換えたかったりすることが多い。
こうした「見た目を整える処理」は、アプリケーションを作るうえで非常に頻繁に使う。
- strip:文字列の前後にある空白や指定文字を取り除くために使う。
- stripLineEnd:行末の改行文字を取り除きたいときに使う。
- removePrefix:文字列の先頭にある不要な接頭辞を削るために使う。
- removeSuffix:文字列の末尾にある不要な接尾辞を削るために使う。
- replace:文字列の一部を別の文字列へ置き換えるために使う。
- replaceWord:単語単位で安全に置き換えたいときに使う。
- multiReplace:複数の置換ルールをまとめて適用したいときに使う。
- indent:各行の先頭に空白を入れて字下げしたいときに使う。
- unindent:そろって入っている字下げを取り除きたいときに使う。
- repeat:同じ文字列や文字を何回も繰り返したいときに使う。
- toLowerAscii:ASCII の英大文字を小文字へ変換したいときに使う。
- toUpperAscii:ASCII の英小文字を大文字へ変換したいときに使う。
- normalize:一般的な文字列を比較しやすい形へ整えたいときに使う。
- nimIdentNormalize:Nim の識別子として比較しやすい形へ正規化したいときに使う。
- validIdentifier:その文字列が Nim の識別子として妥当か確かめたいときに使う。
さらに strutils は、文字列を別の型へ変換する用途でも重要である。
初心者は「見た目が数字ならそのまま計算できる」と思いがちだが、実際には "123" はただの文字列である。
そのため、数値や真偽値として使う前に、適切な型へ変換しなければならない。ここで使うのが parse 系の関数である。
- parseInt:10進数の文字列を int に変換したいときに使う。
- parseFloat:小数を表す文字列を float に変換したいときに使う。
- parseBool:yes、no、true、false、on、off などの文字列を bool に変換したいときに使う。
- parseEnum:文字列を列挙型の値へ変換したいときに使う。
- parseHexInt:16進数の文字列を整数に変換したいときに使う。
- parseHexStr:16進表現された文字列を元の文字列へ戻したいときに使う。
- fromBin:2進数の文字列を整数へ変換したいときに使う。
- fromHex:16進数の文字列を整数へ変換したいときに使う。
- fromOct:8進数の文字列を整数へ変換したいときに使う。
- toBin:整数や文字列を2進数表現に変換したいときに使う。
- toHex:整数や文字列を16進数表現に変換したいときに使う。
- toOct:整数や文字列を8進数表現に変換したいときに使う。
最後に、値を見やすい文字列へ整形して出力するときの関数群も重要である。
内部では数値として扱っていても、画面に見せるときやログとして残すときには、人が読みやすい形式へ直したいことが多い。
そうした場面では、書式指定や連結を担当する関数が役に立つ。
- format:書式文字列の中に値を埋め込んで、見やすい文字列を作るために使う。
- %:format と同じように、簡潔な書き方で値を埋め込みたいときに使う。
- formatFloat:小数点以下の桁数や表記方法を指定して小数を文字列化したいときに使う。
- formatBiggestFloat:より大きな浮動小数点型を整形して文字列化したいときに使う。
- formatEng:数値を工学表記で見やすく表示したいときに使う。
- formatSize:バイト数を KiB や MiB のような人間にわかりやすい単位へ変換したいときに使う。
- join:複数の文字列をつなげて一つの文字列にまとめたいときに使う。
このように strutils は、文字列をそのまま扱うだけのモジュールではない。
文字列を探す、切る、整える、置き換える、別の型へ変換する、見やすく表示するという一連の流れをまとめて支えるモジュールである。
初心者のうちはすべてを暗記する必要はないが、「分割なら split」「空白除去なら strip」「数値化なら parseInt」「文字列化なら formatFloat や join」というように、用途ごとに代表的な関数を覚えていくと理解しやすい。
参考文献
以下は本記事を執筆するにあたって参考にした資料である。