024号文書

主にプログラミング

PythonistaがRustはじめました#006 -- 文字列を扱う

有休を取得するも、雨で一日中引きこもっていました。 一部の競プロerで評判のよい映画「響け!ユーフォニアム」を観に行って、その帰りに久々に北極の火山を食べるムーブをしたかったのですがねぇ...。 しかし、その分いろいろと精進する時間があり、(質素ですけど)ポートフォリオを作ったりしてました: https://wotsushi.github.io/portfolio/ GitHub Pages便利だね。

本題。Rustで少しくせのある文字列の扱いについてです。

tenka1-2019 B

  • 入力: 文字列S、整数K
  • 出力: SのK文字目と異なる文字を * に置換した文字列

https://atcoder.jp/contests/tenka1-2019-beginner/tasks/tenka1_2019_b

RustのStringはランダムアクセスできない

C++, Java, Pythonなど多くの言語では文字列型の値に対して、そのi文字目を取得することは容易です。 例えば、Pythonでは S[i] と書けば (0-indexedで) それはi文字目を表します。 しかしながら、RustではStringにそのようなメソッドは用意されていません。

参考: 文字列型 - The Rust Programming Language -- 文字列に添え字アクセスする

そのようなメソッドが存在しないことの詳しい理由は上記リンクに譲りますが、ざっくり言うと、RustのStringはUTF-8によるエンコード列を1バイトごとに区切ったシーケンスであるためです。 つまり、ひらがなのように2バイト文字が文字列に含まれる可能性を考慮すると、Stringのi番目の要素はi文字目を意味しないため、メソッドとして提供するのはいかがなものか?という考えです。

とはいえ、競プロにおいては基本的に1バイト文字から成る文字列しか扱いません。 やはりPythonのように、i文字目にアクセスしたくなります。

文字列をVecとして扱う

1バイト文字しか扱わないならば、char型のVec(シーケンス)に変換する手があります。 流れは以下の通りです。

  1. Stringのcharsメソッド を使いcharsに変換する
  2. charsのcollectメソッド を使い Vec<char> に変換する。なお、collectは何型にするかを指定する必要があるが、それには Vec<_> と指定すればよい(いちいちVec<char> と書く必要はなし)

得られたVecに対して、i番目の要素にアクセスすることでi文字目を得ることができます。 それだけではなく、以前取り上げた操作 も適用できます。 つまり、into_iterメソッドを使い、Iteratorのメソッドで遊び倒せるのです! 遊び終わったら、collectメソッドを使ってStringに戻して出力すればよいです。

map

さて、Iteratorに変換後にできる楽しい遊びとして、お馴染みのmapがあります。 これは、引数の関数をIteratorの各要素に適用した新たなIteratorを得るメソッドです。

mapの引数に関数を指定する際、無名関数をしばしば指定しますが、Rustでもそのような指定が可能です。 記法はRubyに近く、例えば以下のように書きます(iterをi64のIteratorとします)。

iter.map(|e| 2 * e)

コード

これまでの新しい道具を用いることで、tenka1-2019 Bを解けます。 コードは以下の通りです。

fn main() {
    // 入力
    let N = get!(i64);
    let S = get!(String).chars().collect::<Vec<_>>();
    let K = get!(usize);

    let p = S[K - 1];
    let ans = S.into_iter().map(|c| if c != p { '*' } else { c })
        .collect::<String>();

    // 出力
    println!("{}", ans);
}

型変換が忙しいけど、メソッドチェーンが楽しいね! https://atcoder.jp/contests/tenka1-2019-beginner/submissions/5788747

そして、明日はバチャコン。そろそろRustでリベンジしたい。