2009-11-19 10 views
29

Me gustaría un algoritmo (o biblioteca) eficiente que pueda usar en Java para buscar subcadenas en una cadena.Algoritmo rápido para buscar subcadenas en una cadena

Lo que me gustaría hacer es:

Dada una cadena de entrada - INSTR:

"BCDEFGH"

Y un conjunto de cadenas de candidatos - CAND :

"AB", "CDE", "FG", "H", "IJ"

: todas las CAND cadenas que coinciden como subcadenas dentro INSTR

En este ejemplo coincidiría con "CDE", "FG" y "H" (pero no con "AB" y "IJ")

Podría haber miles de cadenas candidatas (en CAND), pero lo más importante es que haré esta búsqueda muchos millones de veces, así que necesito que sea RÁPIDO.

Me gustaría trabajar con matrices de caracteres. Además, no estoy probado en soluciones arquitectónicas, como distribuir la búsqueda, solo la función/algoritmo más eficiente para hacerlo localmente.

Además, todas las cadenas en CAND y INSTR serán relativamente pequeñas (< 50 caracteres), es decir, la cadena de destino INSTR NO es larga en relación con las cadenas candidatas.


actualización debería haber mencionado, el conjunto de cadenas CAND es invariante en todos los valores de INSTR.

Actualización Solo necesito saber que hay una coincidencia, y no necesito saber cuál fue el partido.

Informe final de actualización que optamos por tratar AhoCorsick y Rabin-Karp, debido a la simplicidad de implementación. Como tengo patrones de longitud variable, utilicé un Rabin-Karp modificado que hash los primeros n caracteres de cada patrón, donde n es la longitud del patrón más pequeño, N fue entonces la longitud de la ventana de búsqueda de mi subserie móvil. Para el Aho Corsick que utilizan this

En mi prueba Busqué 1000 patrones en dos documentos de artículos de prensa de papel, promediados entre 1000 iteraciones etc ... tiempos normalizados para completar eran:

AhoCorsick: 1

RabinKarp: 1.8

Naive Buscar (marque cada string.contains patrón de uso &): 50


* Algunos recursos que describen los algos mencionados en las respuestas a continuación:

http://www.seas.gwu.edu/~simhaweb/cs151/lectures/module5/module5.html

http://www.cs.princeton.edu/courses/archive/spr09/cos226/lectures/18SubstringSearch-2x2.pdf

http://www-igm.univ-mlv.fr/~lecroq/string/index.html *

+0

BTW - esto NO es tarea, ¡pero es un problema del mundo real! – Joel

+0

¿Cuánto duran las cadenas de entrada en relación con las cadenas candidatas? –

+0

Son cortos. Las cadenas de entrada suelen tener menos de 40 caracteres, al igual que las cadenas candidatas. – Joel

Respuesta

25

Lea en el Aho-Corasick algorithm y el Rabin-Karp algorithm.

Si la entrada no es demasiado grande, no desea repetir la búsqueda muchas veces y no tiene muchos patrones, puede ser una buena idea usar un algoritmo de patrón único varias veces. El Wikipedia article on search algorithms proporciona muchos algoritmos con tiempos de ejecución y preproceso.

Implementaciones:

Presentaciones:

+0

Se puede encontrar una colección de varios algoritmos (incluido Aho-Corasick) en http://stringsearchalgorithms.amygdalum.net/ – CoronA

11

Convierta el conjunto de cadenas candidatas en un autómata de estado finito determinista y luego ejecute la cadena de entrada en tiempo lineal. La conversión de una sola cadena en un DFS está bien cubierta en los libros estándar. Puede convertir un conjunto de cadenas construyendo primero un autómata no determinista y luego determinándolo. Eso puede crear una explosión exponencial en el peor de los casos en el tamaño del autómata pero la búsqueda posterior es rápida; especialmente si la cadena objetivo es larga y los candidatos cortos van a funcionar bien.

+0

+1 por mencionar los FSM. Absolutamente la solución más rápida. – Anton

+0

¿Es esto tan importante como relevante dado que las cadenas de entrada y las cadenas candidatas son todas muy cortas, p. <50 caracteres? – Joel

+0

@ Joel creo que depende de lo que quiere decir cuando se escribe encima de ese "yo quiero hacer esto en varias ocasiones a través de muchas cadenas de entrada". El DFS no depende de la cadena de entrada, por lo que si el conjunto de cadenas candidatas es constante en múltiples cadenas de entrada, eso equivale a una cadena de entrada larga y, por lo tanto, la solución es definitivamente relevante. Si todas las cadenas son cortas Y los candidatos cambian cada vez, entonces podría no ser la solución óptima. –

2

Es posible que desee ver en Aho-Corasick algorithm y algoritmos relacionados. No conozco ninguna biblioteca que implemente esto, de manera espontánea, pero esta es la forma clásica de resolver este problema.

+0

Thx. Implementación de Java aquí: http://hkn.eecs.berkeley.edu/~dyoo/java/index.html – Joel

6

Esto es para lo que son las expresiones regulares. Como se indicó anteriormente, los autómatas de estados finitos son lo que necesita, pero así es exactamente como se implementa un regexp-matcher estándar.

en Java podría escribir algo como:

StringBuilder sb = new StringBuilder(); 
bool first = true; 
for (String subStr : substrings) { 
    if (first) 
     first = false; 
    else 
     sb.append('|'); 
    sb.append(escape(subStr)); 
} 
Pattern p = Pattern.compile(sb.toString()); 

el método escape debe escapar los caracteres que tienen un significado especial en una expresión regular.

+0

No puedo decir por qué no fue votado, pero puedo decir que debido a la forma en que se implementa Java Regex, esta expresión regular puede ser mucho menos eficiente que buscar cada subcadena individualmente. – PSpeed

+0

Me gusta esta solución y la subí. Sin embargo, me gustaría señalar dos posibles problemas: (1) Dadas unas mil cadenas de búsqueda, es posible que el compilador de patrones explote. Me preocupa que el uso de la memoria crezca exponencialmente con la complejidad de la expresión del partido. (2) Creo que el FSM/DFS creado por el compilador de patrones en ocasiones hará una copia de seguridad. Si lo hace, entonces uno de los algoritmos especializados que avanza estrictamente puede terminar siendo más rápido. –

+0

No pretendo que mi solución sea perfecta. Aunque puede ser adecuado. YMMV. –

0

Otra solución es utilizar un suffix array para el INSTR.
Desde el INSTR es pequeño se puede ordenar con ordenamiento de burbuja.

Después se puede buscar una cadena específica CAND en O (logN),
donde N = longitud (suffix_array) = longitud (INSTR).

2

Podemos aprovechar el tamaño pequeño (< 50 caracteres) de las cadenas para crear un algoritmo súper rápido para este caso, a costa de la memoria.

Nos puede dispersar todas las posibles subcadenas de INSTR en un hash una vez que va a costar un tiempo O (n^2). Entonces, independientemente del número de cadenas CAND, la búsqueda será O (1). Vale la pena por una gran cantidad de cadenas de CAND.

Si INSTR es grande, podemos construir una matriz de sufijos y no ordenarla, de modo que el elemento superior sea el más largo (= N) y el último elemento sea el último carácter de INSTR. Ahora, para cada cadena CAND, solo busque desde la parte superior siempre que tenga longitud (CAND) < = longitud (sufijo). Cada una de esas comparaciones será O (n).

+0

Soy un poco confuso en esto, así que podría estar fuera de base aquí, pero ¿sería el tiempo hash O (n + 1) (n/2) en lugar de O (n^2) ya que es cuántas subcadenas diferentes que hay ¿debiera ser? –

+0

Big-O ignora los coeficientes. Suelta el '1' y' 2' de tu expresión y te queda ''O ((n) (n))' que es lo mismo que 'O (n^2)'. –

0

Here son algunos de Cuerda implementación de algoritmos de búsqueda rápida en Java.

+0

¿dónde? ¿Olvidaste copiar pegar el enlace? –

+0

Si hace clic en el texto "Aquí", se le redirigirá al sitio web con los algoritmos. – Mike

0
import java.util.Scanner; 

public class StringMatch 
{ 
    static int temp,i=0,j=0; static boolean flag=true,matcher=false; 

    static String str=null,mstr=null;static char astr[],amstr[]; 

    static void getter(){ 
     Scanner sc = new Scanner(System.in); 
     str = sc.nextLine(); 
     //String str="today is Monday"; 
     astr=str.toCharArray(); 
     mstr = sc.nextLine(); 
     //String mstr="is"; 
     amstr=mstr.toCharArray(); 
    } 

    static void stringMatch(){ 
     while(i<astr.length){ 
      if(astr[i]==amstr[j]){ 
      while((j!=amstr.length)&&flag){temp=i; 
       if(astr[i]!=amstr[j]) {flag=false;matcher=false;} 
       else{matcher=true;} 
       i++;j++; 
       //System.out.println(i+"\t"+j); 
      }if(matcher==true)break;i=temp;}i++;j=0;flag=true; 

     } 
     if(matcher==true) {System.out.println("true");} 
     else {System.out.println("false");} 
    } 

    public static void main(String[] args) { 

    StringMatch.getter(); 
    StringMatch.stringMatch(); 

    } 
} 
Cuestiones relacionadas