Saliste a la ligera, probablemente no quieras estar trabajando para un fondo de cobertura donde los quants no entienden los algoritmos básicos :-)
No hay forma de procesar una estructura de datos de tamaño arbitrario O(1)
si, como en este caso, necesita visitar cada elemento al menos una vez. Lo mejor que puede esperar es O(n)
en este caso, donde n
está la longitud de la cadena.
Aunque, en un aparte, un valor nominal O(n)
algoritmo va a ser O(1)
de un tamaño fijo de entrada por lo que, técnicamente, que pueden haber sido correcto en este caso. Sin embargo, no suele ser así como las personas usan el análisis de complejidad.
Me parece que podría haberlos impresionado de varias maneras.
En primer lugar, informándoles que es no es posible hacerlo en O(1)
, a menos que utilice el "sospechoso" razonamiento expuesto anteriormente.
En segundo lugar, al mostrar sus habilidades de élite al proporcionar un código Pythonic como:
inpStr = '123412345123456'
# O(1) array creation.
freq = [0] * 1000
# O(n) string processing.
for val in [int(inpStr[pos:pos+3]) for pos in range(len(inpStr) - 2)]:
freq[val] += 1
# O(1) output of relevant array values.
print ([(num, freq[num]) for num in range(1000) if freq[num] > 1])
Esto produce:
[(123, 3), (234, 3), (345, 2)]
aunque podría, por supuesto, modificar el formato de salida a lo que desee.
Y, finalmente, al decirles que casi con seguridad no hay problema con una O(n)
solución, ya que el código anterior ofrece resultados para una cadena de un millón de dígitos en menos de medio segundo. Parece escalar también de manera bastante lineal, ya que una cadena de 10,000,000 caracteres toma 3.5 segundos y una de 100,000,000 caracteres toma 36 segundos.
Y, si necesitan algo mejor que eso, hay formas de paralelizar este tipo de cosas que pueden acelerarlo enormemente.
No dentro de un solo intérprete de Python, por supuesto, debido a la GIL, pero podría dividir la cadena en algo como ( vv
se requiere una superposición indicada para permitir el procesamiento adecuado de las áreas límite):
vv
123412 vv
123451
5123456
Puede cultivarlos para separar a los trabajadores y luego combinar los resultados.
Es probable que la división de la entrada y la combinación de la salida empañen cualquier ahorro con cadenas pequeñas (y posiblemente cadenas de incluso millones de dígitos) pero, para conjuntos de datos mucho más grandes, bien puede hacer la diferencia. Mi mantra habitual de "medir, no adivinar" se aplica aquí, por supuesto.
Este mantra también se aplica a otras posibilidades, como evitar Python por completo y usar un lenguaje diferente que puede ser más rápido.
Por ejemplo, el siguiente código C, que se ejecuta en el mismo hardware que el código Python anterior, maneja cien millones de dígitos en 0.6 segundos, aproximadamente la misma cantidad de tiempo que el código Python procesó un millón. En otras palabras, mucho más rápido:
#include <stdio.h>
#include <string.h>
int main(void) {
static char inpStr[100000000+1];
static int freq[1000];
// Set up test data.
memset(inpStr, '1', sizeof(inpStr));
inpStr[sizeof(inpStr)-1] = '\0';
// Need at least three digits to do anything useful.
if (strlen(inpStr) <= 2) return 0;
// Get initial feed from first two digits, process others.
int val = (inpStr[0] - '0') * 10 + inpStr[1] - '0';
char *inpPtr = &(inpStr[2]);
while (*inpPtr != '\0') {
// Remove hundreds, add next digit as units, adjust table.
val = (val % 100) * 10 + *inpPtr++ - '0';
freq[val]++;
}
// Output (relevant part of) table.
for (int i = 0; i < 1000; ++i)
if (freq[i] > 1)
printf("%3d -> %d\n", i, freq[i]);
return 0;
}