文字列内の複数の文字を置き換える最善の方法は?


198

一部の文字を次のように置き換える必要があります:&\&#\#、...

私は次のようにコーディングしましたが、もっと良い方法があるはずだと思います。ヒントはありますか?

strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...

回答:


432

2つの文字を置き換える

私は現在の回答のすべての方法と1つの追加の時間を計った。

入力文字列 abc&def#ghi&-> \&および#-> \#の置き換えて、最速の方法は、次のように置換をチェーン化することtext.replace('&', '\&').replace('#', '\#')でした。

各機能のタイミング:

  • a)1000000ループ、最高3:1.47μs/ループ
  • b)1000000ループ、最高3:ループあたり1.51μs
  • c)100000ループ、3の最高:12.3μs/ループ
  • d)100000ループ、最高3:ループあたり12μs
  • e)100000ループ、3のベスト:3.27μs/ループ
  • f)1000000ループ、最高3:ループあたり0.817μs
  • g)100000ループ、3のベスト:3.64μs/ループ
  • h)1000000ループ、最高3:ループあたり0.927μs
  • i)1000000ループ、最高3:ループあたり0.814μs

ここに関数があります:

def a(text):
    chars = "&#"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['&','#']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([&#])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('&#')
def e(text):
    esc(text)


def f(text):
    text = text.replace('&', '\&').replace('#', '\#')


def g(text):
    replacements = {"&": "\&", "#": "\#"}
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('&', r'\&')
    text = text.replace('#', r'\#')


def i(text):
    text = text.replace('&', r'\&').replace('#', r'\#')

このように時限:

python -mtimeit -s"import time_functions" "time_functions.a('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.b('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.c('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.d('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.e('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.f('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.g('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.h('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.i('abc&def#ghi')"

17文字の置き換え

以下は、同じことを行うための同様のコードですが、エスケープする文字が多くなっています(\ `* _ {}>#+-。!$):

def a(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([\\`*_{}[]()>#+-.!$])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('\\`*_{}[]()>#+-.!$')
def e(text):
    esc(text)


def f(text):
    text = text.replace('\\', '\\\\').replace('`', '\`').replace('*', '\*').replace('_', '\_').replace('{', '\{').replace('}', '\}').replace('[', '\[').replace(']', '\]').replace('(', '\(').replace(')', '\)').replace('>', '\>').replace('#', '\#').replace('+', '\+').replace('-', '\-').replace('.', '\.').replace('!', '\!').replace('$', '\$')


def g(text):
    replacements = {
        "\\": "\\\\",
        "`": "\`",
        "*": "\*",
        "_": "\_",
        "{": "\{",
        "}": "\}",
        "[": "\[",
        "]": "\]",
        "(": "\(",
        ")": "\)",
        ">": "\>",
        "#": "\#",
        "+": "\+",
        "-": "\-",
        ".": "\.",
        "!": "\!",
        "$": "\$",
    }
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('\\', r'\\')
    text = text.replace('`', r'\`')
    text = text.replace('*', r'\*')
    text = text.replace('_', r'\_')
    text = text.replace('{', r'\{')
    text = text.replace('}', r'\}')
    text = text.replace('[', r'\[')
    text = text.replace(']', r'\]')
    text = text.replace('(', r'\(')
    text = text.replace(')', r'\)')
    text = text.replace('>', r'\>')
    text = text.replace('#', r'\#')
    text = text.replace('+', r'\+')
    text = text.replace('-', r'\-')
    text = text.replace('.', r'\.')
    text = text.replace('!', r'\!')
    text = text.replace('$', r'\$')


def i(text):
    text = text.replace('\\', r'\\').replace('`', r'\`').replace('*', r'\*').replace('_', r'\_').replace('{', r'\{').replace('}', r'\}').replace('[', r'\[').replace(']', r'\]').replace('(', r'\(').replace(')', r'\)').replace('>', r'\>').replace('#', r'\#').replace('+', r'\+').replace('-', r'\-').replace('.', r'\.').replace('!', r'\!').replace('$', r'\$')

同じ入力文字列の結果は次のabc&def#ghiとおりです。

  • a)100000ループ、3のベスト:ループあたり6.72μs
  • b)100000ループ、3のうちの最良:ループあたり2.64μs
  • c)100000ループ、最高3:ループあたり11.9μs
  • d)100000ループ、3の最高:4.92μs/ループ
  • e)100000ループ、3の最高:2.96μs/ループ
  • f)100000ループ、3のベスト:4.29μs/ループ
  • g)100000ループ、3のベスト:ループあたり4.68μs
  • h)100000ループ、3のベスト:4.73μs/ループ
  • i)100,000ループ、3のベスト:4.24μs/ループ

そして、より長い入力文字列(## *Something* and [another] thing in a longer sentence with {more} things to replace$)の場合:

  • a)100000ループ、3のベスト:7.59μs/ループ
  • b)100000ループ、3のベスト:ループあたり6.54μs
  • c)100,000ループ、最高3:ループあたり16.9μs
  • d)100000ループ、3のベスト:7.29μs/ループ
  • e)100,000ループ、3のベスト:12.2μs/ループ
  • f)100000ループ、ベスト3:ループあたり5.38μs
  • g)10000ループ、最高:3ループあたり21.7μs
  • h)100000ループ、3のベスト:ループあたり5.7μs
  • i)100000ループ、3のベスト:5.13μs/ループ

いくつかのバリアントを追加します。

def ab(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        text = text.replace(ch,"\\"+ch)


def ba(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        if c in text:
            text = text.replace(c, "\\" + c)

入力が短い場合:

  • ab)100000ループ、最高3:ループあたり7.05μs
  • ba)100000ループ、3のベスト:ループあたり2.4μs

長い入力の場合:

  • ab)100000ループ、最高3:ループあたり7.71μs
  • ba)100000ループ、最高3:ループあたり6.08μs

だから私はba読みやすさとスピードのために使うつもりです。

補遺

コメント欄でhaccksに促され、間に1つの違いabとは、baあるif c in text:チェック。さらに2つのバリアントに対してテストしてみましょう。

def ab_with_check(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)

def ba_without_check(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)

Python 2.7.14および3.6.3、および以前のセットとは異なるマシンでのループあたりの時間(μs)なので、直接比較することはできません。

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
 Py, input    ab   ab_with_check   ba   ba_without_check 
╞════════════╬══════╪═══════════════╪══════╪══════════════════╡
 Py2, short  8.81     4.22        3.45     8.01          
 Py3, short  5.54     1.34        1.46     5.34          
├────────────╫──────┼───────────────┼──────┼──────────────────┤
 Py2, long   9.3      7.15        6.85     8.55          
 Py3, long   7.43     4.38        4.41     7.02          
└────────────╨──────┴───────────────┴──────┴──────────────────┘

私たちはそれを結論付けることができます:

  • チェックありのものはチェックなしのものより最大4倍高速です。

  • ab_with_checkPython 3でわずかにリードしていますが、ba(チェックあり)Python 2でよりリードしています。

  • ただし、ここでの最大の教訓は、Python 3がPython 2よりも最大3倍高速であることです。Python 3の最も遅いものとPython 2の最も速いものとの間に大きな違いはありません!


4
なぜこれが例外的な答えではないのですか?
チキンスープ

if c in text:必要baですか?
2017年

@haccks必須ではありませんが、2〜3倍高速です。短い文字列:あり:1.45 usec per loopなし:5.3 usec per loop、長い文字列:4.38 usec per loopあり:なし:7.03 usec per loop。(これらは、異なるマシンなどであるため、上記の結果と直接比較することはできません。)
Hugo

1
@Hugo; この時間の違いは、のすべての反復で呼び出されている間、が見つかった場合にreplaceのみ呼び出されるためです。ctextbaab
2017年

2
@haccksおかげで、さらにタイミングを上げて回答を更新しました。チェックを追加することは両方に適していますが、最大の教訓は、Python 3が最大3倍高速であることです。
Hugo

73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi

なぜ二重のバックスラッシュが必要だったのですか?「\」だけでは機能しないのはなぜですか?
axolotl 2016年

3
二重のバックスラッシュはバックスラッシュをエスケープします。そうしないと、Pythonは「\」をまだ開いている文字列内のリテラル引用文字として解釈します。
Riet

なぜする必要があるのstring=string.replace(ch,"\\"+ch)ですか?string.replace(ch,"\\"+ch)十分ではないですか?
MattSom 2017

1
@MattSom replace()は元の文字列を変更しませんが、コピーを返します。そのため、コードが効果を発揮するための割り当てが必要です。
ベンブライアン

3
あなたは本当に必要ですか?とりあえず置換が何をするのかという重複のようです。
ロレンソ

32

replaceこのような機能を単純にチェーンする

strs = "abc&def#ghi"
print strs.replace('&', '\&').replace('#', '\#')
# abc\&def\#ghi

交換品の数が増える場合は、この一般的な方法でこれを行うことができます

strs, replacements = "abc&def#ghi", {"&": "\&", "#": "\#"}
print "".join([replacements.get(c, c) for c in strs])
# abc\&def\#ghi

29

ここにstr.translateand を使用したpython3メソッドがありstr.maketransます:

s = "abc&def#ghi"
print(s.translate(str.maketrans({'&': '\&', '#': '\#'})))

印刷される文字列はabc\&def\#ghiです。


2
これは良い答えですが、実際には、1つを実行.translate()すると3つ連鎖するよりも遅くなるようです.replace()(CPython 3.6.4を使用)。
チャンガコ2018

@Changacoタイミングを教えてくれてありがとうpractice実際にはreplace()自分で使いますが、完全を期すためにこの回答を追加しました。
tommy.carstensen

大きな文字列と多くの置換の場合、これはより高速になるはずですが、いくつかのテストは良いでしょう...
Graipher

まあ、それは私のマシンにはありません(2と17の交換でも同じです)。
Graipher、2018年

どのように'\#'有効ですか?それはそうではありませんr'\#''\\#'?コードブロックのフォーマットの問題である可能性があります。
パリティ

16

常にバックスラッシュを付加しますか?もしそうなら、

import re
rx = re.compile('([&#])')
#                  ^^ fill in the characters here.
strs = rx.sub('\\\\\\1', strs)

それは最も効率的な方法ではないかもしれませんが、私はそれが最も簡単だと思います。


15
aarrgghh試してくださいr'\\\1'
ジョン・マシン

10

パーティーに遅れましたが、答えが見つかるまでこの問題で多くの時間を失いました。

短くて甘い、translateより優れていreplaceます。時間の経過に伴う機能性にさらに関心がある場合は、を使用しないでくださいreplace

またtranslate、置換する文字のセットが、置換に使用する文字のセットと重なっているかどうかわからない場合にも使用します。

適例:

replace単純にスニペット"1234".replace("1", "2").replace("2", "3").replace("3", "4")が戻ることを期待して使用すると"2344"、実際には戻り"4444"ます。

翻訳は本来OPが望んでいたものを実行するようです。


6

あなたは一般的なエスケープ関数を書くことを考えるかもしれません:

def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])

>>> esc = mk_esc('&#')
>>> print esc('Learn & be #1')
Learn \& be \#1

このようにして、エスケープする必要がある文字のリストを使用して関数を構成可能にすることができます。


3

参考までに、これはOPにはほとんどまたはまったく役に立ちませんが、他の読者には役立つかもしれません(ダウンボットしないでください。私はこれを知っています)。

やや馬鹿げているが興味深い演習として、複数の文字を置き換えるためにpython関数型プログラミングを使用できるかどうかを確認したかった。これがreplace()を2回呼び出すだけでは勝るとは思いません。そして、パフォーマンスが問題である場合、rust、C、julia、perl、java、javascript、そしておそらくawkでもこれを簡単に克服できます。cythonを介して高速化されたpytoolzと呼ばれる外部「ヘルパー」パッケージを使用します(cytoolz、これはpypiパッケージです)。

from cytoolz.functoolz import compose
from cytoolz.itertoolz import chain,sliding_window
from itertools import starmap,imap,ifilter
from operator import itemgetter,contains
text='&hello#hi&yo&'
char_index_iter=compose(partial(imap, itemgetter(0)), partial(ifilter, compose(partial(contains, '#&'), itemgetter(1))), enumerate)
print '\\'.join(imap(text.__getitem__, starmap(slice, sliding_window(2, chain((0,), char_index_iter(text), (len(text),))))))

これについて説明するつもりはありません。複数の置換を行うためにこれを使用する人は誰もいないからです。それにもかかわらず、私はこれを行うことである程度達成したと感じ、それが他の読者を刺激したり、コード難読化コンテストに勝ったりするかもしれないと思いました。


1
「関数型プログラミング」は、「できるだけ多くの関数を使用する」という意味ではありません。
クレイグアンドリュース

1
これは、完全に優れた、純粋で機能的な複数文字の置き換えです。gist.github.com / anonymous / 4577424f586173fc6b91a215ea2ce89e 割り当てなし、変更なし、副作用なし。読みやすいです。
クレイグアンドリュース

1

python2.7とpython3。*で利用可能なreduceを使用すると、クリーンでpythonicな方法で複数の部分文字列を簡単に置き換えることができます。

# Lets define a helper method to make it easy to use
def replacer(text, replacements):
    return reduce(
        lambda text, ptuple: text.replace(ptuple[0], ptuple[1]), 
        replacements, text
    )

if __name__ == '__main__':
    uncleaned_str = "abc&def#ghi"
    cleaned_str = replacer(uncleaned_str, [("&","\&"),("#","\#")])
    print(cleaned_str) # "abc\&def\#ghi"

python2.7では、reduceをインポートする必要はありませんが、python3。*では、functoolsモジュールからインポートする必要があります。



1

これはどう?

def replace_all(dict, str):
    for key in dict:
        str = str.replace(key, dict[key])
    return str

その後

print(replace_all({"&":"\&", "#":"\#"}, "&#"))

出力

\&\#

答えに似ています


0
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>> 

生の文字列はバックスラッシュを特別に扱わないため、「生の」文字列(置換文字列の前に「r」が付いている)を使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.