Closed41

DartのStringについて調べる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

DartのStringはUTF-16のシーケンス、UTF-16がよくわからないけど日本語のようなマルチバイト主体の文字列の場合は効率が良さそう

文字列のリテラルはシングルクオートかダブルクオートでくくる、この辺りはJavaScriptと同じ

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

エスケープにはバックスラッシュ\を使う。これもC言語など他の言語と同じ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

${expression}を使うことで文字列の中で式を展開できる、これもJavaScriptと同じだがバッククオートを使わずに'"でできるのは嬉しい。

ちなみに式expressionが識別子の場合は{}を省略できる。

'Dart has $s, which is very handy.'
'${s.toUpperCase()} is very handy!'
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

比較するには==を使う。2つの文字列が同一のシーケンスであればメモリ上のアドレスが違ってもtrueとなる。

void main() {
  final a = "123";
  final b = "1" + "2" + "3";
  print(a == b); // true
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

'''"""で括ることで複数行の文字列リテラルも作成できる。

void main() {
  final str = """
    a
    b
    c
  """;
  print(str);
  print(str.length); // 20 = 6 + 6 + 6 + 2
}

最初の改行は含まれないが各行の先頭の空白や最後の改行は含まれるので注意。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

文字列リテラルの前にrを置くことでraw stringを作成できる。

raw stringを理解しようとすると沼にハマりそうなので何かは今は調べないことにする。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

文字列リテラルは定数として扱われる。

${expr}が使われている場合もexprが定数なら文字列リテラルも定数になる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ここまでDart公式ドキュメント、Language tourページ、Stringsセクション、ここからDart APIリファレンス、String classページ

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

String classページの最初の方はLanguage tourページと同様の内容だが、追加の内容もある。

まずはcodeUnitAtとcodeUnits、1つの文字や文字列全体のUTF-16コードを取得できる

void main() {
  const string = 'Dart';
  final firstCodeUnit = string.codeUnitAt(0);
  print(firstCodeUnit); // 68, aka U+0044, the code point for 'D'.
  final allCodeUnits = string.codeUnits;
  print(allCodeUnits); // [68, 97, 114, 116]
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

続いて添字演算子、n番目の文字1つからなる文字列を取得できる。

void main() {
  const string = 'Dart';
  String charAtIndex = string[0];
  print(charAtIndex); // 'D'
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

続いてRune、これGo言語を勉強する時に訳がわからなかったやつだ

RuneとはUTF-16がデコードされたものだろうか

そしてRuneの1つ1つがUnicodeコードポイント(符号点)と呼ばれる、そんな感じ?

void main() {
  const string = 'Dart😄';
  final runes = string.runes.toList();
  print(runes); // [68, 97, 114, 116, 128516]
  print(string.codeUnits); // [68, 97, 114, 116, 55357, 56836]
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

関連リソースには下記がある

  • StringBuffer: 文字列を生成するときに効率が良い
  • RegExp: 正規表現
  • 文字列と正規表現

正規表現の時は文字列にrを前置するらしい、たしかraw stringのことだったよね?

raw stringってエスケープしないってこと?

void main() {
  const string = r'\😄/';
  print (string); // \😄/
}

どうやらそうらしい

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Stringのコンストラクタには次の3つがある

  • String.fromCharCode:文字コードから文字列を作る
  • String.fromCharCodes:文字コードのシーケンスから文字列を作る
  • String.fromEnvironment:環境変数から文字列を作る

文字コードってUnicode符号点のこと?UTF-16コードのこと?多分後者だよね?

void main() {
  final string = String.fromCharCode(128516);
  print(string); // 😄
}

良かった、あってた。よく考えればそうだよね、UTF-16だと2文字必要な時もあるから。

環境変数のコンストラクタはDartPadでは確認できないけど便利そう。

とはいえDartでバックエンドを書くことは無さそうだからあまり使わなそうではある。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Stringのインスタンスには7つのプロパティがある、すべて読み込み専用。

  • codeUnits → List<int>:UTF-16コードのリスト
  • hashCode → int:文字列のハッシュ値
  • isEmpty → bool:空であるか否か
  • isNotEmpty → bool:空ではないか否か、isEmptyの反対
  • length → int:文字列の長さ、多分Unicodeとしての
  • runes → Runes:Unicode符号点のイテラブル
  • runtimeType → Type:ランタイム型、謎

とりあえず全部表示してみよう。

void main() {
  final string = "Dart😄";
  print(string.codeUnits);
  print(string.hashCode);
  print(string.isEmpty);
  print(string.isNotEmpty);
  print(string.length);
  print(string.runes);
  print(string.runtimeType);
}

実行結果

[68, 97, 114, 116, 55357, 56836]
499740029
false
true
6
(68, 97, 114, 116, 128516)
String

なんとlengthが6だ、Unicode符号点ではなくUTF-8のコードの数なんだ。

そしてハッシュコードってSHA-256のようなハッシュとは違うんだね、知らなかった、興味深い。

https://api.flutter.dev/flutter/dart-core/Object/hashCode.html

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

allMatchesメソッドを使って検索できる。引数の方がhaystackになるのはJavaScriptとは反対なので注意が必要。

void main() {
  final string = "Dart";
  final matches = string.allMatches("Dart Dart Dart");
  
  for (final match in matches) {
    print("${match.start} ${match.end} ${match[0]}");
  }
}

実行結果

0 4 Dart
5 9 Dart
10 14 Dart
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

codeUnitAtメソッドを使うとn番目のUTF-16コードユニットを取得できる。

void main() {
  final string = "😄";
  print(string.codeUnitAt(0)); // 55357
  print(string.codeUnitAt(1)); // 56836
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

compareToメソッドを使うと文字列を辞書順で比較できる。

戻り値は下記のようになる。

  • thisが引数より前なら-1
  • thisが引数と同じなら0
  • thisが引数より後なら1
void main() {
  print("a".compareTo("b")); // -1
  print("b".compareTo("b")); // 0
  print("c".compareTo("b")); // 1
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

containsメソッドを使うと引数の文字列が含まれるか否かをチェックできる。

void main() {
  print("Dart".contains("art")); // true
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

endsWithメソッドを使うと引数の文字列で終わるか否かをチェックできる。

void main() {
  print("Dart".endsWith("art")); // true
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

indexOfメソッドを使うと文字列や正規表現が出現する位置を調べることができる。

見つからない場合は戻り値が-1になる。

void main() {
  print("Dart".indexOf(RegExp(r"^[A-Z][a-z]+$"))); // 0
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

lastIndexOfメソッドを使うと文字列や正規表現が最後に出現する位置を調べることができる。

見つからない場合は戻り値が-1になる。

void main() {
  print("Dart Dart".lastIndexOf(RegExp(r"[A-Z][a-z]+"))); // 5
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

matchAsPrefixメソッドを使うと文字列や正規表現で検索できる。

AsPrefixの名前通り、指定位置から開始していない場合は一致しているとは見なさない。

void main() {
  print("Dart".matchAsPrefix("I love Dart")); // null
  print("Dart".matchAsPrefix("I love Dart", 7)); // Instance of 'StringMatch'
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

noSuchMethodはdynamicを使っている時に存在しないプロパティやメソッドにアクセスした時に呼び出されるらしい。

void main() {
  dynamic string = "Dart";
  string.add("😄");
}

実行結果

Uncaught TypeError: B.JSString_methods.add$1 is not a functionError: TypeError: B.JSString_methods.add$1 is not a function
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

padLeftを使うと左側に指定した数の文字列をパディングできる

デフォルトではパディング文字は半角スペース

void main() {
  print("1".padLeft(5));
  print("123".padLeft(5));
  print("12345".padLeft(5));
}

実行結果

    1
  123
12345
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

padRightを使うと右側に指定した数の文字列をパディングできる

デフォルトではパディング文字は半角スペース

void main() {
  print("1".padRight(5, "x"));
  print("123".padRight(5, "x"));
  print("12345".padRight(5, "x"));
}

実行結果

1xxxx
123xx
12345
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

replaceAllメソッドを使うと文字列や正規表現でパターンマッチして置換できる。

void main() {
  print("Dart is an art.".replaceAll("art", "ART")); // DART is an ART.
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

replaceAllMappedを使うとどのように置換するかを関数を通じて指定できる。

void main() {
  print("Dart is an art.".replaceAllMapped("art", (match) {
    return match[0]!.toUpperCase();
  })); // DART is an ART.
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

replaceFirstとreplaceFirstMappedについてはreplaceAllとreplaceAllMappedの単一版で最初に見つかったものだけを置換する。

最後の引数として検索を開始する位置を指定できる。

void main() {
  print("Dart is an art.".replaceFirst("art", "ART")); // DART is an art.
  print("Dart is an art.".replaceFirstMapped("art", (match) {
    return match[0]!.toUpperCase();
  })); // DART is an art.
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

replaceRangeを使うと開始と終了の位置を指定して置換できる

終了位置は省略可

void main() {
  print("Dart is an art.".replaceRange(1, 4, "ART")); // DART is an art.
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

splitメソッドを使うと文字列や正規表現でパターンを指定して文字列を分割できる

戻り値の型は文字列のリスト

void main() {
  print("Dart is an art.".split(" ")); // [Dart, is, an, art.]
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

splitMapJoinメソッドを使うと分割、マップ、結合を一括でできる

void main() {
  print("Dart is an art.".splitMapJoin(
    " ",
    onNonMatch: (str) => str.toUpperCase(),
  )); // DART IS AN ART.
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

startsWithメソッドを使うと指定したパターンから開始しているか否かをチェックできる

第2引数として開始位置を指定できる

void main() {
  print("Dart is an art.".startsWith("Dart")); // true
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

substringメソッドを使うと開始位置と終了位置を指定して部分文字列を取得できる

終了位置は省略可

JavaScriptのsliceと一緒だと思いきやマイナスの位置を指定できないので違う

void main() {
  print("Dart is an art.".substring(0, 4)); // Dart
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

toLowerCaseとtoUpperCaseメソッドは文字列に含まれるすべての英字をそれぞれ小文字と大文字に置換する

void main() {
  print("Dart".toLowerCase()); // dart
  print("Dart".toUpperCase()); // DART
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

trimLeft, trimRightはそれぞれ先頭と末尾にある空白やタブなどを取り除く

trimはtrimLeftとtrimRightの合成関数

void main() {
  print(" Dart ".trim()); // Dart
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

正直に言うとStringなんてどの言語も同じだろうとたかを括っていたが学びが多かった

確かに共通している点も多いが、少数の相違点がハマる原因になる

文字列について学ぶ時間を設けて良かったと思う

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

危ない、演算子を忘れるところだった

*を使うと文字列をリピートできる

void main() {
  print("Dart" * 3); // DartDartDart
}

+を使うと連結できる

void main() {
  print("Dart" + "Dart"); // DartDart
}

=を使うと同一か否かをチェックできる

void main() {
  print("Dart" == "Dart"); // true
}

[]を使うとn番目の文字を文字列として取得できる

void main() {
  print("😄"[0].codeUnits); // [55357]
  print("😄"[1].codeUnits); // [56836]
}

UTF-16コードユニットを無理に文字列にしようとするので絵文字などを使うときは注意

このスクラップは2023/01/10にクローズされました