Unicode文字を含む文字列をスライスする 質問する

Unicode文字を含む文字列をスライスする 質問する

バイト長が異なる文字を含むテキストがあります。

let text = "Hello привет";

開始(含む)と終了(含まない)の文字インデックスを指定して文字列の一部を取得する必要があります。これを試しました

let slice = &text[start..end];

そして次のエラーが発生しました

thread 'main' panicked at 'byte index 7 is not a char boundary; it is inside 'п' (bytes 6..8) of `Hello привет`'

キリル文字はマルチバイトであり、[..]表記には文字が使用されるため、このようなことが起こるのだと思います。バイトインデックス。スライスしたい場合はどうすればいいでしょうか?キャラクターPython で行うのと同じように、インデックスを使用します。

slice = text[start:end]?

イテレータを使用して目的の部分文字列を手動で調べることはできますchars()が、もっと簡潔な方法はありますか?

ベストアンサー1

コードポイントスライスの可能な解決策

イテレータを使用して目的の部分文字列を手動で調べることはできますchars()が、もっと簡潔な方法はありますか?

正確なバイトインデックスがわかっている場合は、文字列をスライスできます。

let text = "Hello привет";
println!("{}", &text[2..10]);

これは「llo пр」と出力します。問題は、正確なバイト位置を見つけることです。これは、char_indices()イテレータを使用するとかなり簡単に実行できます (または、 を使用するchars()こともできますchar::len_utf8())。

let text = "Hello привет";
let end = text.char_indices().map(|(i, _)| i).nth(8).unwrap();
println!("{}", &text[2..end]);

別の方法として、最初に文字列を に収集することもできますVec<char>。その後、インデックス付けは簡単ですが、文字列として印刷するには、再度収集するか、独自の関数を作成する必要があります。

let text = "Hello привет";
let text_vec = text.chars().collect::<Vec<_>>();
println!("{}", text_vec[2..8].iter().cloned().collect::<String>());

なぜもっと簡単にならないのでしょうか?

ご覧のとおり、どちらの解決策もそれほど優れているわけではありません。これは意図的なものであり、次の 2 つの理由があります。

str単純な UTF8 バッファと同様に、Unicode コードポイントによるインデックス作成は O(n) 演算です。通常、この[]演算子は O(1) 演算であると予想されます。Rust はこの実行時の複雑さを明示的に示し、隠そうとはしません。上記の両方のソリューションでは、O(1) ではないことがはっきりとわかります。

しかし、もっと重要な理由は次のとおりです。

Unicodeコードポイントは一般的には有用な単位ではない

Pythonができること(そしてあなたが欲しいと思うこと)は、それほど役に立つものではありません。すべては言語の複雑さ、つまりUnicodeの複雑さに帰着します。PythonはUnicodeをスライスしますコードポイントこれが Rustcharが表すものです。これは 32 ビットの大きさです (ビット数がもう少し少なくても十分ですが、2 の累乗に切り上げます)。

しかし、実際にやりたいのはスライスすることですユーザーが認識する文字しかし、これは明確に緩く定義された用語です。異なる文化や言語では、異なるものを「1 つの文字」と見なします。最も近い近似は「書記素クラスター」です。このようなクラスターは、1 つ以上の Unicode コードポイントで構成できます。次の Python 3 コードを検討してください。

>>> s = "Jürgen"
>>> s[0:2]
'Ju'

驚きですよね? なぜなら、上記の文字列は次のようになるからです。

  • 0x004Aラテン大文字 J
  • 0x0075ラテン小文字 U
  • 0x0308分音記号の組み合わせ
  • ...

これは、前の文字の一部としてレンダリングされる結合文字の例です。Python のスライスは、ここでは「間違った」ことを行います。

もう一つの例:

>>> s = "fire"
>>> s[0:2]
'fir'

これも予想通りではありません。今回は、fi実際には が合字であり、これが 1 つのコードポイントです。

Unicode が驚くべき動作をする例は他にもたくさんあります。詳細情報と例については、下部のリンクを参照してください。

したがって、どこでも使える国際文字列を扱う場合は、コードポイントのスライスは行わないでください。文字列を意味的に一連の文字列として見る必要がある場合は、文字グラフィムクラスターを使用します。そのためには、クレートunicode-segmentation非常に便利です。


このトピックに関するその他のリソース:

おすすめ記事