6

文字列のリストまたは配列、特に .NET で一致するパターンを見つける方法を探していますが、他の言語のアルゴリズムまたはロジックが役立つでしょう。

3 つの配列 (または、この特定のケースでは List(Of String)) があるとします。

Array1
"Do"
"Re"
"Mi"
"Fa"
"So"
"La"
"Ti"

Array2
"Mi"
"Fa"
"Jim"
"Bob"
"So"

Array3
"Jim"
"Bob"
"So"
"La"
"Ti"

の試合の発生を報告したい

("Mi", "Fa") In Arrays (1,2)
("So") In Arrays (1,2,3)
("Jim", "Bob", "So") in Arrays (2,3)
("So", "La", "Ti") in Arrays (1, 3)

...そしてその他。

私はこれを使用して問題のトラブルシューティングを行っていますが、具体的に商品化するためではなく、手動で行うことは避けたいと考えています (約 100 ~ 200 項目の 110 のリストがあります)。

説明されている結果を見つけるのに役立つアルゴリズム、既存のコード、またはアイデアはありますか?

4

8 に答える 8

3

SuffixTreeモジュールを使用してサブシーケンスを見つけるソリューションは次のとおりです。

#!/usr/bin/env python
from SuffixTree  import SubstringDict
from collections import defaultdict
from itertools   import groupby
from operator    import itemgetter
import sys

def main(stdout=sys.stdout):
    """
    >>> import StringIO
    >>> s = StringIO.StringIO()
    >>> main(stdout=s)
    >>> print s.getvalue()
    [['Mi', 'Fa']] In Arrays (1, 2)
    [['So', 'La', 'Ti']] In Arrays (1, 3)
    [['Jim', 'Bob', 'So']] In Arrays (2, 3)
    [['So']] In Arrays (1, 2, 3)
    <BLANKLINE>
    """
    # array of arrays of strings
    arr = [
        ["Do", "Re", "Mi", "Fa", "So", "La", "Ti",],
        ["Mi", "Fa", "Jim", "Bob", "So",],
        ["Jim", "Bob", "So", "La", "Ti",],
    ]

####    # 28 seconds  (27 seconds without lesser substrs inspection (see below))
####    N, M = 100, 100
####    import random
####    arr = [[random.randrange(100) for _ in range(M)] for _ in range(N)]

    # convert to ASCII alphabet (for SubstringDict)
    letter2item = {}
    item2letter = {}
    c = 1
    for item in (i for a in arr for i in a):
        if item not in item2letter:
            c += 1
            if c == 128:
                raise ValueError("too many unique items; "
                                 "use a less restrictive alphabet for SuffixTree")
            letter = chr(c)
            letter2item[letter] = item
            item2letter[item] = letter
    arr_ascii = [''.join(item2letter[item] for item in a) for a in arr]

    # populate substring dict (based on SuffixTree)
    substring_dict = SubstringDict()
    for i, s in enumerate(arr_ascii):
        substring_dict[s] = i+1

    # enumerate all substrings, save those that occur more than once
    substr2indices = {}
    indices2substr = defaultdict(list)
    for str_ in arr_ascii:
        for start in range(len(str_)):
            for size in reversed(range(1, len(str_) - start + 1)):
                substr = str_[start:start + size]
                if substr not in substr2indices:
                    indices = substring_dict[substr] # O(n) SuffixTree
                    if len(indices) > 1:
                        substr2indices[substr] = indices
                        indices2substr[tuple(indices)].append(substr)
####                        # inspect all lesser substrs
####                        # it could diminish size of indices2substr[ind] list
####                        # but it has no effect for input 100x100x100 (see above)
####                        for i in reversed(range(len(substr))):
####                            s = substr[:i]
####                            if s in substr2indices: continue
####                            ind = substring_dict[s]
####                            if len(ind) > len(indices):
####                                substr2indices[s] = ind
####                                indices2substr[tuple(ind)].append(s)
####                                indices = ind
####                            else:
####                                assert set(ind) == set(indices), (ind, indices)
####                                substr2indices[s] = None
####                        break # all sizes inspected, move to next `start`

    for indices, substrs in indices2substr.iteritems():
        # remove substrs that are substrs of other substrs
        substrs = sorted(substrs, key=len) # sort by size
        substrs = [p for i, p in enumerate(substrs)
                   if not any(p in q  for q in substrs[i+1:])]
        # convert letters to items and print
        items = [map(letter2item.get, substr) for substr in substrs]
        print >>stdout, "%s In Arrays %s" % (items, indices)

if __name__=="__main__":
    # test
    import doctest; doctest.testmod()
    # measure performance
    import timeit
    t = timeit.Timer(stmt='main(stdout=s)',
                     setup='from __main__ import main; from cStringIO import StringIO as S; s = S()')
    N = 1000
    milliseconds = min(t.repeat(repeat=3, number=N))
    print("%.3g milliseconds" % (1e3*milliseconds/N))

それぞれ100アイテムの100リストを処理するのに約30秒かかります。SubstringDict上記のコードでは、によってエミュレートされる可能性がありますgrep -F -f

古い解決策:


Pythonの場合(「group_patterns.py」ファイルに保存します):

#!/usr/bin/env python
from collections import defaultdict
from itertools   import groupby

def issubseq(p, q):
    """Return whether `p` is a subsequence of `q`."""
    return any(p == q[i:i + len(p)] for i in range(len(q) - len(p) + 1))

arr = (("Do", "Re", "Mi", "Fa", "So", "La", "Ti",),
       ("Mi", "Fa", "Jim", "Bob", "So",),
       ("Jim", "Bob", "So", "La", "Ti",))

# store all patterns that occure at least twice
d = defaultdict(list) # a map: pattern -> indexes of arrays it's within
for i, a in enumerate(arr[:-1]):
    for j, q in enumerate(arr[i+1:]): 
        for k in range(len(a)):
            for size in range(1, len(a)+1-k):
                p = a[k:k + size] # a pattern
                if issubseq(p, q): # `p` occures at least twice
                    d[p] += [i+1, i+2+j]

# group patterns by arrays they are within
inarrays = lambda pair: sorted(set(pair[1]))
for key, group in groupby(sorted(d.iteritems(), key=inarrays), key=inarrays):
    patterns = sorted((pair[0] for pair in group), key=len) # sort by size
    # remove patterns that are subsequences of other patterns
    patterns = [p for i, p in enumerate(patterns)
                if not any(issubseq(p, q)  for q in patterns[i+1:])]
    print "%s In Arrays %s" % (patterns, key)

次のコマンド:

$ python group_patterns.py

プリント:

[('Mi', 'Fa')] In Arrays [1, 2]
[('So',)] In Arrays [1, 2, 3]
[('So', 'La', 'Ti')] In Arrays [1, 3]
[('Jim', 'Bob', 'So')] In Arrays [2, 3]

このソリューションはひどく非効率的です。

于 2009-01-27T14:40:19.063 に答える
3

コーディングする最も簡単な方法は、ディクショナリを作成してから、各配列の各項目をループすることです。各項目について、次の操作を行います。

アイテムが辞書にあるかどうかを確認し、リストを配列に追加します。アイテムが辞書にない場合は、それとリストを追加します。

あなたが言ったように、これは非生産コードのパフォーマンスは問題ではないので、このアプローチはうまくいくはずです。

于 2009-01-27T13:59:04.747 に答える
2

以下のプログラムを Perl の約 10 分でハッキングしました。これは完全ではありません。グローバル変数を使用し、各リスト内のプログラムで見られるすべての要素の数を出力するだけですが、コード化が非常に簡単な、やりたいことの適切な近似値です。

各配列に共通する要素のすべてのサブセットのすべての組み合わせが実際に必要ですか? 必要に応じて、よりスマートな方法ですべての要素を列挙することもできますが、各配列に少なくとも 1 回存在するすべての要素が必要な場合は、以下の出力で Unix コマンド「grep -v 0」を使用できます。すべての配列に共通するすべての要素の交点。あなたの質問には少し詳細が欠けているため、問題を解決するものを完全に実装することはできません.

プログラミングよりも多くのデータ分析を行う場合、スクリプトは、このようなテキスト データから質問をするのに非常に役立ちます。このようなスクリプト言語でコーディングする方法がわからない場合は、Perl、Python、または Ruby でコーディングする方法について 1 ~ 2 か月かけて読んでください。これらは、このような 1 回限りのハックに最適です。特に、自分が何を望んでいるのか本当にわからない場合に役立ちます。このようなプログラムを作成する時間と頭脳のコストは非常に低いため、(速い人であれば) 質問の定義を調べながら、何度もプログラムを作成して書き直すことができます。

#!/usr/bin/perl -w

use strict;

my @Array1 = ( "Do", "Re", "Mi", "Fa", "So", "La", "Ti");
my @Array2 = ( "Mi", "Fa", "Jim", "Bob", "So" );
my @Array3 = ( "Jim", "Bob", "So", "La", "Ti" );

my %counts;
sub count_array {
    my $array = shift;
    my $name  = shift;
    foreach my $e (@$array) {
        $counts{$e}{$name}++;
    }
}

count_array( \@Array1, "Array1" );
count_array( \@Array2, "Array2" );
count_array( \@Array3, "Array3" );

my @names = qw/ Array1 Array2 Array3 /;
print join ' ', ('element',@names);
print "\n";

my @unique_names = keys %counts;
foreach my $unique_name (@unique_names) {
    my @counts = map {
        if ( exists $counts{$unique_name}{$_} ) {
            $counts{$unique_name}{$_};
        } else {
            0;
        }
    }
    @names;

    print join ' ', ($unique_name,@counts);
    print "\n";
}

プログラムの出力は次のとおりです。

element Array1 Array2 Array3
Ti 1 0 1
La 1 0 1
So 1 1 1
Mi 1 1 0
Fa 1 1 0
Do 1 0 0
Bob 0 1 1
Jim 0 1 1
Re 1 0 0
于 2009-01-27T19:10:37.520 に答える
2

他の人が言ったように、必要な機能は Intersect です。.NET 3.0 を使用している場合は、LINQ の Intersect 関数の使用を検討してください。

詳細については、次の投稿を参照してください

LinqPAD を使用して実験することを検討してください。

www.linqpad.net

于 2009-01-27T14:06:09.680 に答える
1

まず、各項目を数えることから始めます。一時リストを作成します: "Do" = 1、"Mi" = 2、"So" = 3 など。一致するすべてのものを一時リストから削除できます = 1 (例: "Do")。一時リストには、一意でないアイテムのリストが含まれています (どこかに保存してください)。

ここで、一時リストの 1 つから 2 つのリストを作成し、元のリストの次のリストを作成しようとします。"So" + "La" = 2、"Bob" + "So" = 2 など。= 1 のものを削除します。少なくとも 2 回出現するカップルのリストがあります (どこかに保存してください)。

ここで、一時リストからいくつかを取り出し、元のリストから次の項目を取り出して、3 つの項目のリストを作成してみてください。("ミ", "ファ") + "ソ" = 1, ("ミ", "ファ") + "ジム" = 1, ("ソ", "ラ") + "ティ" = 2 with = 1. 少なくとも 2 回表示される 3 つの項目のリストがあります (保存します)。

そして、一時リストが空になるまでそのように続けます。

最後に、保存したすべてのリストを取得して、それらをマージします。

このアルゴリズムは最適ではありません (適切なデータ構造を使えばもっとうまくできると思います) が、実装は簡単です :)

于 2009-01-27T14:19:23.117 に答える
1

データのセットで交差関数を使用したいようです。交差は、両方 (またはそれ以上) のセットに共通する要素を選択します。

この観点の問題は、セットが各要素を複数含むことができないことです。つまり、セットごとに複数の Jim を含めることはできません。また、パターンとしてカウントされる行内の複数の要素を認識できません。ただし、比較関数を変更してさらに調べることはできます。それだけを見るために。

バッグで機能する intersect のような関数があるかもしれません (セットのようなものですが、同一の要素を許容します)。

これらの関数は、ほとんどの言語で標準であるか、自分で簡単に作成できるはずです。

于 2009-01-27T13:56:05.610 に答える
1

もっとエレガントな方法があると確信していますが、...

これは製品コードではないので、ハックして各配列を区切られた文字列に変換し、各文字列で必要なパターンを検索してみませんか? すなわち


        private void button1_Click(object sender, EventArgs e)
        {

            string[] array1 = { "do", "re", "mi", "fa", "so" };
            string[] array2 = { "mi", "fa", "jim", "bob", "so" };
            string[] pattern1 = { "mi", "fa" };
            MessageBox.Show(FindPatternInArray(array1, pattern1).ToString());
            MessageBox.Show(FindPatternInArray(array2, pattern1).ToString());

        }

        private bool FindPatternInArray(string[] AArray, string[] APattern)
        {
            return string.Join("~", AArray).IndexOf(string.Join("~", APattern)) >= 0;
        }
于 2009-01-27T14:08:57.303 に答える
0

パスワードが英語のアルファベットからの9文字の文字列(26文字)で構成されているとします。考えられる各パスワードを1ミリ秒でテストできるとしたら、考えられるすべてのパスワードをテストするのにどのくらい時間がかかりますか?

于 2009-05-28T06:56:20.947 に答える