2008年8月30日

split の研究

split 関数の挙動が言語ごとに微妙に異なると知人と話題になったので調べてみました。結果はまとめをどうぞ。

 

Ruby

まずはRubyから調べてみます。irb を使って調べました。

% irb
...
まずは普通に分割
>> "a,b,c".split(",")
=> ["a", "b", "c"]

末尾に空要素があると、省略されてしまう
>> "a,,c,,".split(",")
=> ["a", "", "c"]

末尾の空要素を省略しないためには -1 が必要
>> "a,,c,,".split(",", -1)
=> ["a", "", "c", "", ""]

空文字列だと結果も空
>> "".split(",")
=> []

-1 をつけても同様
>> "".split(",", -1)
=> []

分割パターンを指定しない場合も同様
>> "".split()
=> []

分割パターン省略時は空白で分割(厳密には $; の値が影響)
>> "a b\tc\nd".split()
=> ["a", "b", "c", "d"]

分割パターンは文字列で指定できる
>> "a:b;c.d".split(".")
=> ["a:b;c", "d"]

分割パターンは正規表現でも指定できる
>> "a:b;c.d".split(/[:;]/)
=> ["a", "b", "c.d"]

分割パターンが空の文字列だったら?
>> "abc".split("")
=> ["a", "b", "c"]

分割パターンが空の正規表現だったら?
>> "abc".split(//)
=> ["a", "b", "c"]

分割パターンの正規表現が . だったら?
>> "abc".split(/./)
=> []

Perl

次は Perl です。Perl -d -e1 でデバッガを起動して調べました。-MData::Dumper はデータの表示用、 -MTerm::ReadLine はコマンドライン編集用のモジュールです。

% perl -MData::Dumper -MTerm::ReadLine -d -e1
...
まずは普通に分割
  DB<1> p Dumper(split(",", "a,b,c"))
$VAR1 = 'a';
$VAR2 = 'b';
$VAR3 = 'c';

末尾に空要素があると、省略されてしまう
  DB<2> p Dumper(split(",", "a,,c,,"))
$VAR1 = 'a';
$VAR2 = '';
$VAR3 = 'c';

末尾の空要素を省略しないためには -1 が必要
  DB<3> p Dumper(split(",", "a,,c,,", -1))
$VAR1 = 'a';
$VAR2 = '';
$VAR3 = 'c';
$VAR4 = '';
$VAR5 = '';

空文字列だと結果も空
  DB<4> p Dumper(split(",", ""))

-1 をつけても同様
  DB<5> p Dumper(split(",", "", -1))

分割パターンは正規表現で指定できる
  DB<6> p Dumper(split(/[:;]/, "a:b;c.d"))
$VAR1 = 'a';
$VAR2 = 'b';
$VAR3 = 'c.d';

分割パターンは文字列では指定できない(正規表現として解釈される)
  DB<7> p Dumper(split("[:;]", "a:b;c.d"))
$VAR1 = 'a';
$VAR2 = 'b';
$VAR3 = 'c.d';

分割パターンが空の正規表現だったら?
  DB<8> p Dumper(split(//, "abc"))
$VAR1 = 'a';
$VAR2 = 'b';
$VAR3 = 'c';

分割パターンの正規表現が . だったら?
  DB<9> p Dumper(split(/./, "abc"))

分割パターンおよび入力文字列を省略時は $_ を空白で分割
  DB<10>  $_="a b\tc\nd"

  DB<11>  p Dumper(split)
$VAR1 = 'a';
$VAR2 = 'b';
$VAR3 = 'c';
$VAR4 = 'd';

というわけで、 Perl の挙動は分割パターンが文字列で指定できない(文字列で指定しても正規表現として解釈されてしまう)以外は Ruby と同じです。Ruby が Perl の挙動と揃えたのかもしれません。

Python

次は Python です。python コマンドを使って調べました。

% python
...
まずは普通に分割
>>> "a,b,c".split(",")
['a', 'b', 'c']

末尾の空要素は無視されない
>>> "a,,c,,".split(",")
['a', '', 'c', '', '']

空文字列を分割すると配列に空文字列がひとつ入る
>>> "".split(",")
['']

しかし、分割パターンを指定しないとなぜか空になる!
>>> "".split()
[]

分割パターン省略時は空白で分割
>>> "a b\tc\nd".split()
['a', 'b', 'c', 'd']

分割パターンは文字列で指定できる
>>> "a:b;c.d".split(".")
['a:b;c', 'd']

分割パターンが空の文字列だったら?
>>> "abc".split("")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: empty separator

Python の挙動は Ruby/Perl 組とは異なります。最後の「分割パターンを指定しないとなぜか空になる」は不思議です。分割パターンが空のときにエラーになるのも特徴です。

JavaScript

次は JavaScript です。 js コマンドを使って調べました。

% js
...
まずは普通に分割
js> "a,b,c".split(",").toSource()
["a", "b", "c"]

末尾の空要素は無視されない
js> "a,,c,,".split(",").toSource()
["a", "", "c", "", ""]

空文字列を分割すると配列に空文字列がひとつ入る
js> "".split(",").toSource()
[""]

分割パターンを指定しない場合も空文字列がひとつ入る
js>  "".split().toSource()
[""]

分割パターン省略時は分割しない
js> "a b\tc\nd".split()
a b     c
d

分割パターンは正規表現で指定できる
js> "a:b;c.d".split(/[:;]/).toSource()
["a", "b", "c.d"]

分割パターンは文字列でも指定できる
js> "a:b;c.d".split(".").toSource()
["a:b;c", "d"]

分割パターンが空の文字列だったら?
js> "abc".split("").toSource()
["a", "b", "c"]

分割パターンが空の正規表現だったら?
js> "abc".split(new RegExp("")).toSource()
["a", "b", "c"]

分割パターンの正規表現が . だったら?
js> "abc".split(/./).toSource()
["", "", "", ""]

正規表現 /./ で分割したときの挙動が Ruby/Perl と異なります。

Emacs

最後は Emacs です。*scratch* バッファで C-u C-x で式を評価&表示して調べました。

まずは普通に分割
(split-string "a,b,c" ",") => ("a" "b" "c")

末尾の空要素は無視されない
(split-string "a,,c,," ",") => ("a" "" "c" "" "")

空文字列を分割すると配列に空文字列がひとつ入る
(split-string "" ",") => ("")

しかし、分割文字列を指定しないとなぜか空になる
(split-string "") => nil

分割パターン省略時は空白で分割
(split-string "a b\tc\nd") => ("a" "b" "c" "d")

分割パターンは正規表現で指定できる
(split-string "a:b;c.d" "[:;]") => ("a" "b" "c.d")

分割パターンが空の正規表現だったら?
(split-string "abc" "") => ("" "a" "b" "c" "")

分割パターンの正規表現が . だったら?
(split-string "abc" ".") => ("" "" "" "")

分割パターンが空の正規表現のときの挙動が Ruby, Perl, JavaScript のいずれとも異なります。

まとめ

結果をまとめて表にしてみました。

言語 分割パターンの型 末尾の空要素 分割パターン省略時 入力が空文字列かつ
分割パターン指定あり
入力が空文字列かつ
分割パターン指定なし
分割パターンが空文字列 分割パターンが空正規表現 分割パターンが正規表現の .
Ruby 文字列または正規表現 省略される (-1で抑制) 空白で分割 空の配列 空の配列 文字ごとに分割 文字ごとに分割 空の配列
Perl 正規表現 省略される (-1で抑制) $_ を空白で分割 空の配列 文字ごとに分割 空の配列
Python 文字列 省略されない 空白で分割 空文字列入りの配列 空の配列 エラー
JavaScript 文字列または正規表現 省略されない 分割しない 空文字列入りの配列 空文字列入りの配列 文字ごとに分割 文字ごとに分割 文字数 + 1 の空文字列
Emacs 正規表現 省略されない 空白で分割 空文字列入りの配列 空の配列 文字ごとに分割+前後に空文字列 文字数 + 1 の空文字列

ここでは、「空白で分割」の「空白」とは「スペース、タブ、改行など」を意味します。個人的に妙だと思った挙動に色をつけておきました。

Ruby, Perl の末尾のから要素がデフォルトで省略されるという挙動は親切というよりは、落とし穴という感じがします。一方、 Python などの入力が空文字のときは空文字列入りの配列が返るという挙動も、これはこれではまりそうです。簡単そうに見える split 関数でも注意して使う必要があるようです。

以上、チマチマした研究でした。

追記: Perl の split は引き数なしで実行すると $_ の内容を空白で分割するとのこと、てきとうなメモさんにご指摘いただきました。