2009-01-27 22 views
6

我正在尋找方法來查找列表或字符串數​​組中的匹配模式,特別是在.NET中,但是來自其他語言的算法或邏輯將會有所幫助。如何在列表/字符串數組中找到類似模式

說我有3個數組(或在這種特殊情況下列表(串))

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個約100-200項的110個列表)。

是否有任何算法,現有代碼或想法能夠幫助我完成查找所描述的結果?

+0

爲什麼「So」被打印兩次? – jfs 2009-01-27 14:56:32

+0

因爲它存在於2組中。 – StingyJack 2009-01-27 15:50:06

+0

感謝您的回覆。我還有一件事出現,但會在一兩天內重新審視,然後會給予反饋。 – StingyJack 2009-01-27 18:19:51

回答

1

看起來像你想在數據集上使用交集函數。交集選取兩個(或更多)集合中通用的元素。

這個觀點的問題是集合中不能包含多於一個的每個元素,即每個集合不能包含一個以上的Jim,也不能識別一行中的幾個元素作爲一個模式計算,但是可以修改比較函數來進一步看看這一點。

還有一些功能就像袋子上的相交(這有點像套裝,但容忍相同的元素)。

這些功能在大多數語言中應該是標準的或者很容易編寫自己。

3

最簡單的代碼方法是構建一個字典,然後遍歷每個數組中的每個項目。對於每個項目,請執行以下操作:

如果要將列表添加到數組中,請檢查項目是否在dictonary中。 如果該項目不在字典中,請將其添加到列表中。

由於如你所說這是非生產代碼的性能並不重要,所以這種方法應該可以正常工作。

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; 
     } 
1

首先,開始計數每個項目。 您製作一個臨時列表:「Do」= 1,「Mi」= 2,「So」= 3等。 您可以從臨時列表中刪除所有匹配= 1(例如:「Do」)的列表。 臨時列表包含非唯一項目的列表(保存在某處)。

現在,您嘗試在temp列表中創建兩個列表,並在原始列表中創建一個列表。 「So」+「La」= 2,「Bob」+「So」= 2等 刪除= 1的那些。 您有一對至少出現兩次的對象列表(保存在某處)。

現在,嘗試製作3個項目的列表,方法是從臨時列表中選取一對,然後從原始列表中選取以下內容。 (「Mi」,「Fa」)+「So」= 1,(「Mi」,「Fa」)+「Jim」= 1,(「So」,「La」)+「Ti」= 2 那些= 1。 你有3項出現至少兩次(保存)的列表。

你繼續這樣下去,直到臨時列表爲空。

最後,將所有保存的列表合併起來。

這種算法不是最優的(我認爲我們可以用合適的數據結構做的更好),但它很容易實現:)

2

下面是使用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)) 

大約需要30秒來處理100個100個項目的清單。上述代碼中的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] 

該解決方案非常低效。

2

我在大約10分鐘的Perl中黑掉了下面的程序。它並不完美,它使用全局變量,它只是列出每個列表中程序看到的每個元素的計數,但是它非常接近你想要做的事情,它非常容易編碼。

你是否真的想要每個數組共有元素的所有子集的所有組合?如果你願意,你可以用更聰明的方式枚舉所有元素,但是如果你只想要每個數組中至少存在一次的所有元素,你可以在下面的輸出中使用Unix命令「grep -v 0」你是所有陣列通用的所有元素的交集。你的問題缺少一點細節,所以我不能完美地實現解決你的問題的東西。

如果您進行的數據分析比編程更多,腳本編寫對於像這樣的文本數據提問可能非常有用。如果你不知道如何用這樣的腳本語言編寫代碼,我會花上一兩個月的時間閱讀關於如何用Perl,Python或Ruby編寫代碼。對於像這樣的一次性黑客來說,它們可能會很棒,特別是在你不知道自己想要什麼的情況下。編寫這樣一個程序的時間和大腦成本是非常低的,所以(如果你速度很快),你可以寫幾次並重寫它,同時仍然在探索你的問題的定義。

#!/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 
0

設密碼包括從英文字母(26個字符)九個字符字符串。如果每個可能的密碼都可以在毫秒內測試,那麼測試所有可能的密碼需要多長時間?

相關問題