2009-01-27 36 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个字符)九个字符字符串。如果每个可能的密码都可以在毫秒内测试,那么测试所有可能的密码需要多长时间?

相关问题