Pythonでの通常の(または非常に普通ではない)文字変換

キリル文字からラテン文字まで-Pythonで音訳を記述する必要があった。 単語「sith」から「sith」が得られ、「srusting」から「shelest」が得られます。



まったく何を書くためにそこにあるのかと思われる-タスクは「Hello world」を印刷するよりも難しいことはほとんどありません。 そして、これは部分的に真実です-しかし、完全ではありません。



実際、ロシア語の音訳中の一部の文字は1つに変換されず、複数のラテン文字が一度に変換されます。これらはZh、Ts、Ch、Sh、Sh、UおよびYです。実際、音訳の規則が1つに変換することになっている場合ラテン文字、ロシア語から英語への音訳は、その非常に単純なプログラムよりもそれほど複雑ではありません。



しかし、音訳の規則を変更することは絶対にしないので、通常の音訳を使用した場合に何が起こるかがわかります。



たとえば、フレーズ「HAT and Julia」は、「SHAPKA and YUlya」または「ShAPKA and Yulya」に変換されます。これは、「Sh」および「Yu」の音訳テーブルで指定されている内容(「SH」 「YU」、時には「Sh」と「Yu」)。



つまり、標準の音訳機能の次の文字の大文字と小文字は考慮されず、大文字のすべての文字は一般的な規則に従って置き換えられます。 したがって、「ボウル」と「キャベツスープ」という単語の音訳の過程で、実際には「チャシャ」と「スキ」を取得したいのに、「チャシャ」または「SCHi」のようなものを取得するのは簡単です。



それにも関わらず、Pythonでキリル文字からラテン文字への音訳実装が見つかったため、結局のところ、この機能は考慮されていませんでした。 これらは、 フォーラム提供される多数のソリューションと、モジュールの1つで音訳を実装するpytilsライブラリです。



そこで、ブラックジャックと^ W ^ W ^ W ^ H ^ Hを使用して、音訳機能を記述します。 :)





したがって、 最初のオプションは、文字ごとに行を調べることです。 この場合、次の文字が常にわかっています(これが文字列の最後の文字でない場合)。



原則は次のとおりです。



  1. 各文字について、それが辞書lower_case_lettersのキーに含まれているかどうかがチェックされます。
  2. 存在する場合、lower_case_lettersの指定されたキーの値に置き換えられます。
  3. そうでない場合、この文字がcapital_letters辞書のキーに含まれているかどうかを確認します。
  4. ある場合、これが文字列の最後の文字であるかどうかがチェックされます(文字列が現在の文字の位置+ 1よりも長い場合、位置がゼロからカウントされるという条件で、これは最後の文字ではありません)。
  5. 文字が最後ではない場合、次の文字が辞書lower_case_lettersのキーの中にあるかどうかがチェックされます。
  6. そうでない場合、またはこれが文字列の最後の文字である場合、現在の文字はcapital_letters辞書の指定されたキーの値に置き換えられ、値にはupper()メソッドが使用されます。つまり、「SH」は「Sh」から取得されます。
  7. 次の文字が辞書lower_case_lettersのキーに含まれている場合、upper()メソッドは使用されません。


このオプションの利点は、次の4つ(実際には数行)よりもわずかに短いことです。さらに興味深いことに、非常に短い行でも非常に速く動作します。 しかし、反対に、大きな文字列では非常にゆっくりと動作します-つまり、文字列を音訳するのに必要な時間は、文字列の文字数に強く依存します-グラフでは、文字数が異なると音訳時間がどのように変化するかを確認できます。



精度を高めるために、実行時間(このグラフと他のグラフの両方)は、1回の音訳操作ではなく500で示されています。コンピューターにインストールされているプロセッサーはIntel Core 2 Quad CPU Q9400 @ 2.66GHzです。



グラフ



さて、長い行の処理を高速化するには、行全体で個々の文字の置換をすぐに使用する必要があることが明らかになりました(置換)。 しかし実際には、1つの文字を置き換えることはできませんが、一度に文字のグループを置き換えることができます。 そこで、2番目のオプションが登場しました。



一般的に、頭に浮かんだのはシンボルグループの置換だけではないことを言わなければなりません。 最初に、たとえば、文字列を単語に分割するというアイデアが生まれ、次に単語ごとに大文字と小文字を確認します(すべての文字が大文字に変換される単語が元の単語と等しい場合、そのような単語は大文字になります)、そして単語が大文字の場合は、特に大文字の単語については、個別の音訳テーブルを適用します。 したがって、この場合、単語「Chess」の文字「Sh」は「Sh」として音訳されますが、単語「CHESS」ではすでに「SH」として翻字されます。



確かに、大文字と小文字が混在する単語(「CHESS」など)では、どのような場合でも、通常の音訳テーブルが使用されます(つまり、ShAHmatYになります)。 しかし、これは一般的にはそれほど重要ではありません。



または、オプションとして、より普遍的にそれを行うことができます-小文字で書かれた単語に置換を適用し、文字ごとに残りの単語を処理します。 しかし、これはおそらくもっと遅いでしょう。なぜなら、多くのそのような言葉がある可能性が高いからです。



それでも、 2番目に書かれたオプションに戻ります 。 彼の原則は、複数のラテン文字として一度に表されるロシア語の文字を個別の辞書に選択し、文字のペアから辞書(音訳表)を作成することです。 、H、W、U、Y、Y)には33の異なるオプションがあり、ロシア語のアルファベットの小文字がそれぞれあります。 したがって、そのような文字のペアを最初に置き換えてから、音訳されていない他のすべての文字を置き換えます。



しかし、まさにそのようなオプションは非常に遅いことが判明しました(実際、実装は遅いことが判明しましたが、それについては以下でさらに詳しく説明します)。



より正確には、文字数が非常に多い場合にのみ、最初のオプションよりも高速です(約1万文字)。



また、100〜1000文字の行を処理する場合、最初のオプションは約10倍高速です。



まあ、7 * 33 =231。つまり、231の追加の置換。 もちろん、辞書ではこれらの文字の組み合わせの多くに出会うことはありません。つまり、理論的には、より少ない数の置換操作を行うことができます。 しかし、一方で、テキストは辞書にある単語だけで構成することはできません。実際、もう一度このような制限をしたくありません。



実際、判明したように、ポイントは多数の置換操作が実行されることではなく、文字の置換がどのように正確に実装されるかです(4番目のオプションを参照)。



ただし、最初に3番目のオプションを検討してください。



3番目のオプションは 2 番目のオプションに多少似ていますが 、小文字のZh、Ts、Ch、Sh、Sh、U、Zの可能な組み合わせごとに231の個別の置換はありません。 代わりに、正規表現を使用します。これは、7つの置換のみに適用されます。 小文字([a])が後に続くいくつかのラテン文字に対応する各文字は、その文字のラテン表現に置き換えられ、その後に同じ小文字(音訳なし)が続きます。 その後、残りの小文字はそれぞれ大文字になりますが、それぞれ個別に置き換えられます。 いくつかのラテン文字に対応する大文字を置き換える場合、文字のラテン表現にupper()メソッドが使用されます。



そして、このオプションはすでに2番目と比較して非常に高速であることが証明されました。



100文字の行では、最初のオプションよりも遅いことに注意する必要があります。 しかし、1000文字の行では、最初のオプションの2倍、2番目のオプションの19.5倍の速度です。



原則として、これは終了する可能性がありますが、追加できるものと追加すべきものが本当にあります。



import thisを書くと、よく知られているテキスト、Python言語のZenを取得します。 とりわけ、「これを行うための明白な方法は1つ、できれば1つだけであるべきです」というフレーズがあります。 ただし、Pythonを長い間プログラミングしてきた人なら誰でも、同じことを行う方法が非常に多く、それらのいずれかを使用する場合、結果はまったく同じになる可能性があることを知っていますが、操作を完了するのにかかる時間は非常に異なる。



したがって、たとえば、u "Your name is%s"%usernameは、u "Your name is" +ユーザー名よりもはるかに高速です。 これは、記事「Pythonでの効率的な文字列連結」に詳細に記述されています。



同様に、re.subを使用して文字列を置き換えるか、文字列メソッドreplaceを使用できます。 さらに、置換に正規表現を使用しない場合は、2番目の方法を使用することを強くお勧めします。2番目の方法は、さらに高速に動作します。 ちなみに、同じpytilでstringメソッドreplaceが使用されます。



したがって、 4番目のオプション (編集のある2番目のオプション)。 編集されたのは3行だけですが、生産性は飛躍的に向上しました。



このオプションは、1000行と10000文字の行を持つ他のオプションよりも優れています。 100文字の行では、以前の(2番目のオプション)と比較した違いも非常に大きくなります。



しかし、3番目のオプションの編集はどうでしょうか? 結局、文字が置換される3つのサイクルのうち、正規表現は1つだけで使用されます。 さて、3番目のオプションも編集しましょう。



5番目のオプションは、正規表現が置換に使用されない2サイクルで、文字列の置換方法を使用して置換操作が実行されるという点でのみ3番目と異なります



間違いなく、これはパフォーマンスに大きな影響を与えたはずです。5番目のオプションの速度は、3番目のオプションの平均1.5倍です。 そして、100文字の行で、5番目のオプションは最初のオプションよりもさらに高速に(つまり、文字ごとの処理よりも高速に)動作することが判明しました。



グラフ



置換アルゴリズムは、100、1000、10000文字の文字列の文字ごとの処理よりも高速であることが判明しました。



それでも、各オプションには長所と短所があります。 たとえば、第5の実施形態では、100文字の文字列は、行の文字ごとのトラバースを使用するよりも高速に処理されますが、非常に小さな行(数文字—場合によっては)では、文字ごとの処理が依然として最速のオプションであることがわかります。



おそらく、最も正しいソリューションは、短い文字列を処理するための最適なアルゴリズムと長い文字列を処理するための最適なアルゴリズムの組み合わせです。 つまり、まず文字列の長さをチェックし、次にこれに応じて、いずれかのアルゴリズムを使用します。



結論として、GNU Grepが非常に高速である理由に関するfreebsd-currentメーリングリストのメッセージから、Mike Hertel(Mike Haertel-GNU Grepの著者)を引用したいと思います。



プログラムを高速にするための鍵は、プログラムを実質的に何もしないようにすることです。 ;-)


「プログラムを高速にするための鍵は、実質的に何もしないようにすることです。」



他に考えがある場合は、追加、補足してください。 他の改善点があれば、多くの人にとって興味深いものになると思います。




All Articles