web-dev-qa-db-ja.com

文字列内のパターンを検索

次の質問にどのようにアプローチしますか。

通常の英数字文字列とパターン文字列の2つの文字列があります。パターン文字列は、英数字の文字と文字「?」で構成できます。および「*」

最初の文字列がパターンと一致するかどうかを確認します。ここで、?すべての文字(英数字)がその位置で許可されることを意味しますが、*は英数字の文字のシーケンスを持つことを許可します。

2

インタビューでこの質問をされた場合、これは私がアルゴリズムを理解し始める方法です。注:最初の2つのケースは、私が最終的な回答にたどり着く方法であり、3番目のケースまで実際には適切な回答ではありません。

「?」がない簡単なケースから始めます。または「*」。パターン文字列の最初の文字に到達するまで、検索文字列のスキャンを実行します。その時点で、パターン文字列の次の文字に移動し、検索文字列の次の文字と一致するかどうかを確認します。含まれている場合は続行し、そうでない場合はパターン文字列の先頭に戻りますが、検索文字列内の現在の位置は保持します。パターン文字列の文字が足りなくなった場合は一致しますが、検索文字列の文字が足りなくなった場合は一致しません。

次に、「?」の場合に移動しますキャラクター。 '?'を押す場合を除いて、以前と同じアルゴリズムを実行します。パターン文字列で、先に進み、検索文字列の現在の位置をスキップして、パターン文字列の次の文字を取得します。ケース1の場合と同じように続行します。

次に、「?」の最後のケースに移動しますおよび「*」が許可されています。他の2つの場合と同じようにしますが、今回は「*」を押したときに、パターン文字列の「*」の次の文字をすぐに見つけます。次に、検索文字列をスキャンして、その次の文字を見つけます。見つかった場合は、ケース1と2を続けてください。パターン文字が一致していない場合は、一致します。検索文字列の文字が足りなくなった場合は、パターン文字列の「*」の直後にある文字の検索文字列の次のインスタンスに移動して、再試行してください。この時点でまだ見つからない場合は、最初の一致で誤警報が発生した可能性があります。したがって、最後の一致チェックを開始した時点から開始し、このプロセス全体をもう一度やり直してください。検索文字列の最後に到達すると、一致するものがありません。

この時点で、問題は解決しました。ほとんどの場合、解はO(n)ですが、最悪の場合はO(n ^ 2)である可能性があります。

5
Jonathan Henson

実際にこの問題を解決する必要がある場合、おそらく*[A-Za-z0-9]*で置き換え、?[A-Za-z0-9]で置き換えることにより、パターンを正規表現パターンに変換し、正規表現を使用して文字列をパターンと照合します。

インタビューのために、私はおそらくジョナサンのような答えを与えるでしょう。それが彼らが探しているものだと思います。

3
jk.

関数を呼び出すだけです。 fnmatch(3) (POSIX.2;他のプラットフォームでは、いくつかのposixエミュレーションライブラリからそれを掘り出します)。 C/C++以外のほとんどの言語にもそのような機能があります。

インタビュアーがその回答に満足しなかった場合は、彼らがレビューを行ったかどうかを決定した会社に対してそれを数えます。

インタビュアーが答えを気に入ったが、その機能の実装について質問した場合は、次のようになります。

  • 単純な再帰的バリアント(任意の擬似コードを使用)(O(mn)最悪):

    bool matches(string:string, pattern:string):
        if pattern[0] == '*':
            # assuming || short-circuits this order is the faster on average
            return matches(string, pattern[1:]) || matches(string[1:], pattern)
        elseif pattern[0] == '?' or pattern[0] == string[0]:
            return matches(string[1:], pattern[1:])
        else:
            return False
    
  • 有限オートマトンをコンパイルする複雑なバリアント(時間のO(n)は、前処理のオートマトン/時間の複雑さのために、より高い空間の複雑さと交換されました。これは、O(m2)、しかし怠惰すぎて今正確に導き出すことはできません)。

2
Jan Hudec

実用的なアプローチ:

  1. パターン文字列を有効な正規表現文字列に変換します。
    • 単に? with \wおよび* with \w*または\w+
  2. 正規表現を使用してパターンを検索する
1
Euphoric

私はこの解決策を試してみるつもりです。ジョナサンはその一般的な要点を持っていますが、私は考えられる問題を見つけ、自分で解決策を試みるという挑戦が好きです。

このソリューションは、もちろん正規表現がオプションではないことを前提としています。正規表現は、このようなトリッキーなパターンに効率的に一致するように明示的に存在します。私は、1つのことと1つのことをうまく行うモジュラーライブラリを作成するプログラマー、およびホイールを再発明するのではなくそのライブラリを使用する他のすべてのプログラマーを強く信じています。 。

私の考えでは、*は、1つだけでなく、多くの解決策が可能になる興味深い状況を生み出します。これらすべての可能性について一致する文字列をチェックするには、これらすべての可能性について文字通り完全にチェックする必要があります。例として、パターンa * bcをazbbcに一致させることができます。zに続くbが*の後のセクションに属していると仮定すると、それ以外の場合は一致しません。同様に、zに続くbが*の後のセクションに属すると想定しなかった場合は、azbcに一致しなくなります。

したがって、アルゴリズムの簡略化されたバージョンは次のとおりです( 実装はここで確認できます )。

function search(matchString, pattern) {  
    var i = 0;

    // For each character in matchString and pattern
    for(; i<matchString.length && i<pattern.length; i++) {
        if(pattern[i] === '*') {
            // Star!  If pattern has been valid up to now, then
            // only one match must be valid after the *, so pass responsibility
            // to searchStar.
            var subMatchString = matchString.substring(i, matchString.length);
            var subPattern = pattern.substring(i+1, pattern.length);

            return searchStar(subMatchString, subPattern);
        } else if (pattern[i] !== '?') {
            // If it isn't * and it isn't ?, then compare characters.
            // If characters don't match, we know it isn't valid.
            if(matchString[i] !== pattern[i]) {
                return failure(matchString, pattern);
            }
        } // else if (pattern[patternIndex] === '?') {
            // Ignore '?'.. move along
        //}
    }
    if(i === pattern.length) {
        return success(matchString, pattern);  // pattern exhausted
    } else {  
        return failure(matchString, pattern);  // matchString ended with unmatched pattern
    }
}

// This function is called when star is encountered in pattern and
// pattern thus far is correct.  This applies search on remaining
// match string and returns true if even one proves true.
function searchStar(matchString, pattern) {
    while(matchString.length > 0) {  
        if(search(matchString, pattern)) {
            return true;
        }
        matchString = matchString.substring(1, matchString.length);          
    }
    return failure(matchString, pattern);
}

重複する「*」などを無視するなど、ちょっとしたことで効率が上がるかもしれませんが、読者が一般的な考え方を理解できるように、できるだけシンプルにしようと思いました。

お役に立てば幸いです。

0
Neil

これは素朴ですが、再帰を使用してテストされ機能する実装です。アスタリスクの3つのケースのため、O(n ^ 3)だと思います。この問題には、かなりの数のコーナーケースがあります。

ドブス博士はこの問題についてまともな記述があります: http://www.drdobbs.com/architecture-and-design/matching-wildcards-an-algorithm/210200888

コメントの1つはBoyer-Mooreの使用を示唆していましたが、通常のBoyer-Mooreがワイルドカードをサポートしているようには見えません。 KMP文字列検索にワイルドカードを追加したブログ投稿があります- http://0x7fffffff.blogspot.com/2012/07/kmp-supporting-wild-card.html

from collections import namedtuple
import string
import unittest

LITERAL_CHARS = set(string.ascii_letters + string.digits)

def simple_matcher(pattern, string):
    if pattern == "":
        if string == "":
            return True
        else:
            return False

    # We have a non-empty pattern
    else:

        if string == "":
            # If we only have * in the pattern, we can match the empty string.
            if all(p == "*" for p in pattern):
                return True
            else:
                return False

        # We have a non-empty pattern and string
        else:
            if pattern[0] in LITERAL_CHARS:
                return (pattern [0] == string [0]
                        and simple_matcher(pattern[1:], string[1:]))
            Elif pattern[0] == "?":
                return simple_matcher(pattern[1:], string[1:])
            Elif pattern[0] == "*":
                return (
                    # Match one char with * and keep going
                    simple_matcher(pattern[0:], string[1:])
                    or
                    # Don't use the *
                    simple_matcher(pattern[1:], string[0:])
                    or
                    # Only match one char with *
                    simple_matcher(pattern[1:], string[1:]))



Result = namedtuple('Result', ['pattern', 'string', 'expected'])

class TestSimpleMatcher(unittest.TestCase):


    def test_empty_string(self):
        results = [Result("", "", True),
                  Result("a", "", False),
                  Result("?", "", False),
                  Result("*", "", True),
                  Result("**", "", True),
                  Result("***", "", True)]
        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)

    def test_empty_pattern(self):
        results = [Result("", "", True),
                  Result("", "a", False),
                  Result("", "1", False),
                  Result("", "A", False),
                  Result("", "aaasdfasdf", False)]
        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)

    def test_literals_against_one_char_string(self):
        results = [
            Result("a", "a", True),
            Result("A", "A", True),
            Result("1", "1", True),

            Result("2", "a", False),
            Result("4", "A", False),
            Result("B", "1", False)
        ]

        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)

    def test_question_marks_against_one_char_string(self):
        results = [
            Result("?", "a", True),
            Result("?", "A", True),
            Result("?", "1", True),

            Result("??", "a", False),
            Result("??", "A", False),
            Result("??", "1", False),
        ]
        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)

    def test_asteriks_against_one_char_string(self):
        results = [
            Result("*", "a", True),
            Result("*", "A", True),
            Result("*", "1", True),
            Result("**", "a", True),
            Result("***", "a", True),
            Result("*****", "a", True),
        ]
        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)

    def test_combinations_against_one_char_string(self):
        results = [
            Result("*?", "a", True),
            Result("*A", "A", True),
            Result("*?*", "a", True),
            Result("*A*", "A", True),

            Result("*?a", "a", False),
            Result("*B", "A", False),
            Result("*??*", "a", False),
            Result("*A*?", "A", False),
        ]
        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)


    def test_combinations_against_three_char_string(self):
        results = [
            Result("*?", "aaa", True),
            Result("???", "aaa", True),
            Result("?*A", "BAA", True),
            Result("*E*", "1EF", True),
            Result("*", "123", True),
            Result("****", "123", True),

            Result("*?a*", "abc", False),
            Result("*B", "Bod", False),
            Result("????", "joe", False),
        ]
        for pattern, string, expected in results:
            self.assertEqual(simple_matcher(pattern, string), expected)

if __name__ == '__main__':
    unittest.main()
0
Joe