web-dev-qa-db-ja.com

(類似の)文字列のセットからプレフィックスを決定する

文字列のセットがあります。

my_prefix_what_ever
my_prefix_what_so_ever
my_prefix_doesnt_matter

これらの文字列の最も一般的な部分、ここではプレフィックスを見つけたいだけです。上記では、結果は次のようになります。

my_prefix_

ひも

my_prefix_what_ever
my_prefix_what_so_ever
my_doesnt_matter

接頭辞になるはずです

my_

Pythonでプレフィックスを決定するのに比較的苦痛のない方法はありますか(各文字を手動で繰り返す必要なしに)?

PS:Python 2.6.3。

62
Kawu

提供される内容を書き換えないでください: os.path.commonprefix はまさにこれを行います:

リスト内のすべてのパスのプレフィックスである最長パスプレフィックス(文字ごとに取得)を返します。リストが空の場合、空の文字列('')。一度に1つの文字が機能するため、これは無効なパスを返す可能性があることに注意してください。

他の回答と比較するためのコードは次のとおりです。

# Return the longest prefix of all list elements.
def commonprefix(m):
    "Given a list of pathnames, returns the longest common leading component"
    if not m: return ''
    s1 = min(m)
    s2 = max(m)
    for i, c in enumerate(s1):
        if c != s2[i]:
            return s1[:i]
    return s1
116
Ned Batchelder

Ned Batchelder はおそらく正しいです。しかし、その楽しみのために、itertoolsを使用した phimuemue の答えのより効率的なバージョンを以下に示します。

import itertools

strings = ['my_prefix_what_ever', 
           'my_prefix_what_so_ever', 
           'my_prefix_doesnt_matter']

def all_same(x):
    return all(x[0] == y for y in x)

char_tuples = itertools.izip(*strings)
prefix_tuples = itertools.takewhile(all_same, char_tuples)
''.join(x[0] for x in prefix_tuples)

読みやすさのa辱として、ここに1行のバージョンがあります:)

>>> from itertools import takewhile, izip
>>> ''.join(c[0] for c in takewhile(lambda x: all(x[0] == y for y in x), izip(*strings)))
'my_prefix_'
13
senderle

私のソリューションは次のとおりです。

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]

prefix_len = len(a[0])
for x in a[1 : ]:
    prefix_len = min(prefix_len, len(x))
    while not x.startswith(a[0][ : prefix_len]):
        prefix_len -= 1

prefix = a[0][ : prefix_len]
5
MRAB

以下は有効ですが、おそらく非常に非効率的なソリューションです。

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]
b = Zip(*a)
c = [x[0] for x in b if x==(x[0],)*len(x)]
result = "".join(c)

文字列の小さなセットの場合、上記はまったく問題ありません。しかし、より大きなセットの場合、私は個人的に、各文字を次々にチェックし、違いがあるときに停止する別の手動ソリューションをコーディングします。

アルゴリズム的には、これにより同じ手順が得られますが、リストcの作成を回避できる場合があります。

2
phimuemue

好奇心から、これを行う別の方法を見つけました。

def common_prefix(strings):

    if len(strings) == 1:#rule out trivial case
        return strings[0]

    prefix = strings[0]

    for string in strings[1:]:
        while string[:len(prefix)] != prefix and prefix:
            prefix = prefix[:len(prefix)-1]
        if not prefix:
            break

    return prefix

strings = ["my_prefix_what_ever","my_prefix_what_so_ever","my_prefix_doesnt_matter"]

print common_prefix(strings)
#Prints "my_prefix_"

ネッドが指摘したように、おそらくos.path.commonprefix、これは非常にエレガントな関数です。

1
ThePhysicist

この2行目では、入力文字列の各文字に対してreduce関数を使用しています。 N + 1個の要素のリストを返します。Nは最短の入力文字列の長さです。

lotの各要素は、(a)入力文字(all入力文字列がその位置で一致する場合)、または(b ) なし。 lot.index(None)は最初の位置ですNone in lot:共通プレフィックスの長さ。 outはその共通プレフィックスです。

val = ["axc", "abc", "abc"]
lot = [reduce(lambda a, b: a if a == b else None, x) for x in Zip(*val)] + [None]
out = val[0][:lot.index(None)]
1
Mano Bastardo

最小限のコードでOrderedDictを使用してこれを行う別の方法を次に示します。

import collections
import itertools

def commonprefix(instrings):
    """ Common prefix of a list of input strings using OrderedDict """

    d = collections.OrderedDict()

    for instring in instrings:
        for idx,char in enumerate(instring):
            # Make sure index is added into key
            d[(char, idx)] = d.get((char,idx), 0) + 1

    # Return prefix of keys while value == length(instrings)
    return ''.join([k[0] for k in itertools.takewhile(lambda x: d[x] == len(instrings), d)])
0
skeptichacker

シンプルでクリーンなソリューションを紹介します。考えは、Zip()関数を使用して、1番目の文字のリスト、2番目の文字のリスト、... n番目の文字のリストにすべての文字を配置することです。次に、各リストを繰り返して、リストに値が1つだけ含まれているかどうかを確認します。

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]

list = [all(x[i] == x[i+1] for i in range(len(x)-1)) for x in Zip(*a)]

print a[0][:list.index(0) if list.count(0) > 0 else len(list)]

出力:my_prefix_

0
Patmanizer