2009-01-27 11 views
6

Estoy buscando formas de encontrar patrones coincidentes en listas o matrices de cadenas, específicamente en .NET, pero los algoritmos o la lógica de otros lenguajes serían útiles.Cómo encontrar patrones similares en listas/matrices de cadenas

Decir que tengo 3 matrices (o en este caso Lista específica (De La Cadena))

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

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

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

quiero informar sobre las ocurrencias de los partidos de

("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) 

... y cualquier otro.

Estoy utilizando esto para solucionar un problema, no para hacer un producto comercial de él específicamente, y preferiría no hacerlo a mano (hay 110 listas de alrededor de 100-200 elementos).

¿Hay algún algoritmo, código existente o ideas que me ayuden a lograr encontrar los resultados descritos?

+0

¿Por qué "So" se imprime dos veces? – jfs

+0

Porque existe en 2 conjuntos de grupos. – StingyJack

+0

Gracias por las respuestas. Tuve otra cosa que surgiré, pero la revisaré en uno o dos días y daré su opinión. – StingyJack

Respuesta

2

Como otros han mencionado, la función que desea es Intersectar. Si usa .NET 3.0, considere usar la función Intersecar de LINQ.

Ver the following post for more information

considerar el uso de LINQPad de experimentar.

www.linqpad.net

+0

La publicación a la que se refiere es engañosa. Intenta comparar 'string a [] = {" a "," b "}; string b [] = {"b", "a"} 'usando el método en la publicación. Las matrices no son iguales (los elementos están en orden diferente) pero las operaciones de conjunto no distinguirán estas matrices. 'Intersecar()' junto no es la respuesta. – jfs

+0

De acuerdo. Ordenar e Intersecar sería obligatorio. Simplemente creo que la solución Linq es la más fácil y reduce la necesidad de realizar cualquier bucle. –

1

Parece que desea utilizar una función de intersección en conjuntos de datos. La intersección selecciona elementos que son comunes en ambos (o más) conjuntos.

El problema con este punto de vista es que los conjuntos no pueden contener más de uno de cada elemento, es decir, no más de un Jim por conjunto, tampoco puede reconocer varios elementos en una fila contando como un patrón, sin embargo puede modificar una comparación función para mirar más allá para ver solo eso.

Hay funciones como intersect que funcionan en bolsas (que son como conjuntos pero toleran elementos idénticos).

Estas funciones deben ser estándar en la mayoría de los idiomas o muy fácil de escribir usted mismo.

3

La forma más simple de codificar sería crear un diccionario y luego recorrer cada elemento de cada conjunto. Para cada elemento haga esto:

Compruebe si el elemento está en el dictonario, de ser así, agregue la lista a la matriz. Si el artículo no está en el diccionario, agrégalo y la lista.

Dado que, como dijiste, esto no es el rendimiento del código de producción, no importa, por lo que este enfoque debería funcionar bien.

+0

Este enfoque funciona solo para patrones que contienen un solo elemento. Pero el OP también necesita encontrar * secuencias * de elementos, por ejemplo, ("Jim", "Bob") es una secuencia (patrón) pero ("Bob", "Jim") es una secuencia * diferente * (patrón) . Ver mi respuesta http://stackoverflow.com/questions/483429/how-to-f/483642#483642 – jfs

+0

Interesante Me perdí esto en la primera pasada ... – JoshBerke

1

Estoy seguro de que hay una manera mucho más elegante, pero ...

Dado que este no es el código de producción, ¿por qué no entrar ilegalmente en él y convertir cada matriz en una cadena delimitada, luego busca cada cadena para el patrón que quieres? es decir


     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; 
     } 
+0

Esto supone que conozco los patrones de antemano, lo cual no es cierto. Gracias, pero no puedo usarlo para este ejercicio. – StingyJack

1

Primero, comience contando cada artículo. Usted hace una lista temporal: "Hacer" = 1, "Mi" = 2, "Entonces" = 3, etc. puede eliminar de la lista temporal todos los que coinciden = 1 (por ejemplo, "Hacer"). La lista temporal contiene la lista de elementos no únicos (guárdela en alguna parte).

Ahora, intenta hacer listas de dos de uno en la lista temporal, y un seguimiento en las listas originales. "So" + "La" = 2, "Bob" + "So" = 2, etc. Quite los que tienen = 1. Tiene las listas de parejas que aparecen al menos dos veces (guárdelas en alguna parte).

Ahora, intente hacer listas de 3 elementos, tomando un par de la lista temporal, y tome lo siguiente de las listas originales. ("Mi", "Fa") + "So" = 1, ("Mi", "Fa") + "Jim" = 1, ("Entonces", "La") + "Ti" = 2 Eliminar los que tienen = 1. Tiene las listas de 3 elementos que aparecen al menos dos veces (guárdelo).

Y continúe así hasta que la lista temporal esté vacía.

Al final, toma todas las listas guardadas y las fusiona.

Este algoritmo no es óptima (creo que podemos hacerlo mejor con las estructuras de datos adecuadas), pero es fácil de implementar :)

2

he aquí una solución utilizando SuffixTree módulo para localizar subsecuencias:

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

Tarda unos 30 segundos en procesar 100 listas de 100 elementos cada una. SubstringDict en el código anterior podría ser emulado por grep -F -f.

solución antigua:


En Python (guardarlo en el archivo '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) 

el siguiente comando:

$ python group_patterns.py 

impresiones:

[('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] 

La solución es terriblemente ineficiente.

2

Hackeé el programa a continuación en unos 10 minutos de Perl. No es perfecto, usa una variable global e imprime los recuentos de cada elemento visto por el programa en cada lista, pero es una buena aproximación a lo que desea hacer que es súper fácil de codificar.

¿Realmente desea todas las combinaciones de todos los subconjuntos de los elementos comunes a cada matriz? Podría enumerar todos los elementos de una manera más inteligente si quisiera, pero si solo quisiera todos los elementos que existen al menos una vez en cada conjunto, podría usar el comando de Unix "grep -v 0" en el resultado a continuación y que mostraría usted la intersección de todos los elementos comunes a todas las matrices. Su pregunta le falta un poco de detalle, por lo que no puedo implementar perfectamente algo que resuelva su problema.

Si realiza más análisis de datos que programación, los scripts pueden ser muy útiles para hacer preguntas a partir de datos textuales como este. Si no sabes cómo codificar en un lenguaje de scripting como este, me gustaría pasar un mes o dos leyendo sobre cómo codificar en Perl, Python o Ruby. Pueden ser maravillosos para hacks únicos como este, especialmente en los casos en que realmente no sabes lo que quieres.El costo de tiempo y cerebro de escribir un programa como este es muy bajo, de modo que (si eres rápido) puedes escribirlo y volver a escribirlo varias veces mientras exploras la definición de tu pregunta.

#!/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"; 
} 

La salida del programa es:

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

Supongamos una contraseña consistía en una serie de nueve caracteres del alfabeto Inglés (26 caracteres). Si cada contraseña posible pudiera probarse en un milisegundo, ¿cuánto tiempo tomaría probar todas las contraseñas posibles?

Cuestiones relacionadas