OK, así que no sueno como un idiota, voy a exponer el problema / requisitos más explícitamente:
- La aguja (patrón) y el pajar (texto para buscar) son cadenas terminadas en nulo de estilo C. No se proporciona información de longitud; si es necesario, debe calcularse.
- La función debe devolver un puntero a la primera coincidencia, o
NULL
si no se encuentra ninguna coincidencia. - Los casos de falla no están permitidos. Esto significa que cualquier algoritmo con requisitos de almacenamiento no constantes (o grandes constantes) necesitará tener un caso de respaldo para la falla de asignación (y el rendimiento en la atención de respaldo por lo tanto contribuye al rendimiento en el peor de los casos).
- La implementación debe realizarse en C, aunque una buena descripción del algoritmo (o enlace a dicho) sin código también está bien.
... así como lo que quiero decir con "más rápido":
- Determinista
O(n)
donden
= longitud del pajar. (Pero puede ser posible usar ideas de algoritmos que normalmente sonO(nm)
(por ejemplo, hash rodante) si se combinan con un algoritmo más robusto para darO(n)
resultados deterministas ). - Nunca funciona (medible; un par de relojes para
if (!needle[1])
etc. están bien) peor que el ingenuo algoritmo de fuerza bruta, especialmente en agujas muy cortas, que probablemente sean el caso más común. (La sobrecarga de preprocesamiento pesado incondicional es mala, al igual que tratar de mejorar el coeficiente lineal para agujas patológicas a expensas de las agujas probables). - Dada una aguja arbitraria y un pajar, un rendimiento comparable o mejor (no peor que un 50% más de tiempo de búsqueda) en comparación con cualquier otro algoritmo ampliamente implementado.
- Aparte de estas condiciones, estoy dejando la definición de "más rápido" abierta. Una buena respuesta debería explicar por qué considera que el enfoque que sugiere es "más rápido".
Mi implementación actual se ejecuta aproximadamente entre un 10% más lento y 8 veces más rápido (dependiendo de la entrada) que la implementación de Two-Way de glibc.
Actualización: mi algoritmo óptimo actual es el siguiente:
- Para agujas de longitud 1, use
strchr
. - Para agujas de longitud 2-4, use palabras de máquina para comparar 2-4 bytes a la vez de la siguiente manera: precargue la aguja en un entero de 16 o 32 bits con desplazamientos de bits y ciclo de bytes viejos / bytes nuevos desde el pajar en cada iteración . Cada byte del pajar se lee exactamente una vez e incurre en una comprobación contra 0 (final de la cadena) y una comparación de 16 o 32 bits.
- Para agujas de longitud> 4, use el algoritmo de dos vías con una tabla de desplazamiento incorrecta (como Boyer-Moore) que se aplica solo al último byte de la ventana. Para evitar la sobrecarga de inicializar una tabla de 1kb, lo que sería una pérdida neta para muchas agujas de longitud moderada, mantengo una matriz de bits (32 bytes) que marca qué entradas en la tabla de desplazamiento se inicializan. Los bits que no están establecidos corresponden a valores de bytes que nunca aparecen en la aguja, para los cuales es posible un desplazamiento de longitud de aguja completa.
Las grandes preguntas que me quedan en la mente son:
- ¿Hay alguna manera de hacer un mejor uso de la tabla de turnos malos? Boyer-Moore lo aprovecha al escanear hacia atrás (de derecha a izquierda), pero Two-Way requiere un escaneo de izquierda a derecha.
- Los únicos dos algoritmos candidatos viables que he encontrado para el caso general (sin condiciones de falta de memoria o de rendimiento cuadrático) son la coincidencia bidireccional y de cadenas en alfabetos ordenados . ¿Pero hay casos fácilmente detectables en los que diferentes algoritmos serían óptimos? Ciertamente, muchos de los algoritmos
O(m)
(dondem
está la longitud de la aguja) en el espacio podrían usarse para másm<100
o menos. También sería posible usar algoritmos que son el peor de los casos, si hay una prueba fácil para las agujas que probablemente solo requieren tiempo lineal.
Puntos de bonificación por:
- ¿Puedes mejorar el rendimiento asumiendo que la aguja y el pajar son UTF-8 bien formados? (Con caracteres de diferentes longitudes de byte, la buena forma- imposición impone algunos requisitos de alineación de la cuerda entre la aguja y el pajar y permite cambios automáticos de 2-4 bytes cuando se encuentra un byte de cabecera que no coincide. Pero estas restricciones le compran mucho / algo más allá de lo que cálculos de sufijos máximos, buenos cambios de sufijos, etc. ¿ya te dan varios algoritmos?)
Nota: conozco la mayoría de los algoritmos que existen, pero no sé qué tan bien funcionan en la práctica. Aquí hay una buena referencia para que las personas no me sigan dando referencias sobre algoritmos como comentarios / respuestas: http://www-igm.univ-mlv.fr/~lecroq/string/index.html
strstr
como algo para más adelante, por lo que no he podido leer correctamente el documento que vinculaste, pero suena muy prometedor. Gracias y perdón por no volver a usted.