¿Cuál es la forma más eficiente de almacenar estos datos?

9

Estoy a cargo de reescribir algunos viejos códigos VB. Entiendo cómo funciona, pero siento que hay una manera mucho más eficiente de hacer lo que hicieron. Simplemente no puedo entender qué es. Aquí hay un ejemplo artificial que, en términos de requisitos de datos, es realmente similar a lo que necesito hacer.

El usuario debe elegir el fabricante, la marca, el modelo y el color de su automóvil en una GUI. Tengo un archivo de texto grande que se parece a esto:

Ford Truck F150 red
Ford Truck F150 blue
Ford Truck F150 black
Ford Truck F150 silver
Ford Truck F250 red
Ford Truck F250 green
Ford Sedan Taurus red
Ford Sedan Taurus green
Ford Sedan Taurus white
Ford...
...

Subaru SUV Forester blue
Subaru SUV Forester red
Subaru SUV Outback Black
Subaru SUV Outback Green
Subaru SUV Outback Blue
Subaru SUV Outback Red
Subaru...
...

etc.

Entonces, si la primera selección es Subaru, la segunda casilla (marca) no debería tener una opción para seleccionar Camión porque ninguno de los Subarus son camiones. Del mismo modo, si seleccionan Ford, Sedan y Taurus, entonces el último cuadro (color) no debería mostrar una opción para seleccionar azul. O negro. O cualquier otra cosa que no sea rojo, verde o blanco.

A las personas que escribieron el código antes que a mí se les ocurrió esto (en python-y psuedocode):

def getValidOptions():
    items = []
    for i from 0 to numRows:
        options = getLine().split()
        if selectingManufacturer:
            if options[0] not in items:
                items.append(options[0])
        else if selectingMake:
            if selectedManufacturer == options[0] and options[1] not in items:
               items.append(options[1])
        else if selectingModel:
            if selectedManufacturer == options[0] and selectedMake == options[1] and options[2] not in items:
                items.append(options[2])
        else if selectingColor:
            if selectedManufacturer == options[0] and selectedMake == options[1] and selectedModel == options[2] and options[3] not in items:
                items.append(options[3])
    return items

Creo que eso es simplemente horrible, tanto a nivel de algoritmo como a nivel de sintaxis. Por un lado, analiza todo el archivo, cuando solo necesita leer un par de líneas si se hace correctamente. Para hacer esto aún más ineficiente, mis datos reales tienen 6 opciones para seleccionar, en lugar de solo 4. Esto también almacena más datos de los que necesita, dada la cantidad de duplicación de datos.

Estoy buscando una forma diferente de almacenar los datos en el archivo, o una forma diferente de analizarlos para que la getValidOptionsfunción sea más bonita y más eficiente. ¿Hay alguna forma de que pueda hacer esto?

James
fuente
2
¿Por qué no usar una base de datos?
Tulains Córdova

Respuestas:

6

Todas las otras respuestas que leí parecen ignorar dos reglas muy básicas del desarrollo de software:

  • aclarar primero los requisitos (especialmente los requisitos de rendimiento y almacenamiento)

  • Mantenlo simple, estúpido (ver BESO )

Escribiste "el archivo de texto es grande", pero no escribiste demasiado grande, así que supongo que en realidad no hay nada malo con su tamaño, excepto tu instinto. Por lo tanto, si la carga del archivo en realidad no lleva demasiado tiempo, y su departamento de TI u otra persona no se queja del desperdicio de espacio en el disco, y nadie se queja de tener problemas para mantener el archivo, deje el formato del archivo como está, no subestime el valor de la simplicidad

También se queja de la eficiencia del algoritmo, que en realidad no es tan eficiente como podría ser, pero tiene una gran ventaja: es muy simple y funciona. Por lo tanto, siempre que sea lo suficientemente eficiente, no aplique ninguna optimización prematura.

Entonces, supongamos que el programa funciona lo suficientemente rápido, lo que debe preguntar primero es ¿cómo puede mejorar el código en términos de simplicidad y el principio DRY? Y ese es de hecho un punto que debería mejorarse, ya que el código actual no es SECO. En su ejemplo, aparece cuatro veces la misma prueba de bloque de código si las opciones en los "niveles superiores" coinciden con la línea actual, lo que resulta en cuatro veces el mismo tipo de llamada "agregar" (en su código real, la repetición ocurre al menos seis veces, como escribiste). Puede evitarlo fácilmente introduciendo un nivel de selección numérico o pasando las opciones ya seleccionadas en una lista ordenada. Usando su pseudocódigo, esto lleva a algo en la línea de

# selectedOptions is a list, containing either nothing, or "selectedManufacturer"
# or [selectedManufacturer, selectedMake], ..., and so on
def getValidOptions(selectedOptions):
    items = []
    level = selectedOptions.size()
    for i from 0 to numRows:
        options = getLine().split()
        if selectedOptions == options[0:level-1] and options[level] not in item:
            items.append(options[level])
    return items

Entonces, este es esencialmente el mismo algoritmo sin código repetido.

Como es obvio getValidOptionsque debe llamarse más de una vez (al menos una vez por nivel), sugiero aplicar solo una optimización (si este no es el caso): asegúrese de que la getLinefunción extraiga sus datos de la memoria principal y no lea el archivo del disco una y otra vez.

Doc Brown
fuente
Desea mover "level = selectedOptions.size ()" antes del bucle numRows.
AI Breveleri
6

Bueno, los datos que tiene tienen una estructura similar a un árbol, donde para cada fabricante tiene un árbol de modelos, y para cada modelo tiene un color (y así sucesivamente).

Por lo tanto, puede separar el proceso de estos datos de dos maneras:

  1. Después de cualquier actualización del archivo de texto, debe procesar ese archivo de archivo y convertirlo en una estructura de árbol.
  2. Al cargar la aplicación, solo carga la estructura de árbol.

La estructura de árbol se puede implementar con lo que se llama un hash , una matriz asociativa o un diccionario en lenguajes como Java, PHP, Javascript o Python. Con esta estructura, tienes:

  • Las primeras claves son los fabricantes.
  • Sus valores son solo otros hashes o diccionarios donde cada clave es el modelo.
  • Sus valores son los colores. O otra estructura que mantiene en sus llaves el tercer nivel y como valor el cuarto nivel.
  • Y así...

Dependiendo de su lenguaje de programación, esto puede implementarse más rápido o más lento. Por ejemplo:

  • C # : puede implementar una estructura de árbol 1 y luego marcarla como serializable.
  • VB.Net : puede generar la estructura de árbol en una aplicación y serializarla en un archivo.
    • Para esto, algo así Runtime.Serialization.Formatters.Binary.BinaryFormatterpodría ser útil, pero no soy un experto en serializar con VB.Net.
  • Javascript : puede generar la estructura de árbol en un archivo JSON, que debe cargarse cada vez que se carga la aplicación.
  • PHP : puede generar una versión serializada de la estructura de datos del árbol, o también puede cargar un JSON.
  • Java : puede serializar esa estructura de datos, creando una Classque implemente la interfaz java.io.Serializable.

referencias :

1: https://dvanderboom.wordpress.com/2008/03/15/treet-implementing-a-non-binary-tree-in-c/
- Una explicación completa sobre la implementación de un árbol en C #.
- Busque un comentario donde alguien pregunte sobre la serialización de ese objeto, y la respuesta para ese comentario.

Nicolás
fuente
1
Sí, había pensado en usar un árbol, pero no sé si es la mejor idea porque no hay una estructura de árbol incorporada (que yo sepa) en C #, y el proyecto es bastante pequeño, así que no sé si valdría la pena pasar mucho tiempo trabajando en una tree with an arbitrary number of nodesimplementación.
James
Hasta el momento no soy un experto en C #, pero al menos en otros lenguajes como Java y PHP puede tener algún tipo de diccionario, donde cada clave puede tener como valor otro diccionario.
Nicolás
Actualicé mi respuesta. Vea lo que piensa sobre el hash o la alternativa del diccionario. También agregué una referencia con un artículo interesante.
Nicolás
3

Una forma sencilla de almacenar los datos es simplemente introducirlos en una base de datos SQLite. SQLite, a diferencia de la mayoría de las bases de datos SQL, es adecuado para su uso como formato de archivo de aplicación. Este enfoque tiene varios beneficios:

  1. No es necesario codificar un serializador o un deserializador.
  2. El archivo es editable y consultable por numerosos programas existentes.
  3. Se evita el desorden condicional que mencionas en la pregunta. Para limitar los menús desplegables, genere una cláusula where simple para cada columna (por ejemplo, select distinct model where manufacturer='ford' and color = 'red').

Esto lo obliga a aprender SQL, pero evita la necesidad de aprender un formato de archivo personalizado.

Brian
fuente
1

Supongo que puede acceder aleatoriamente a las líneas en el archivo y, por lo tanto, puede tratar el archivo como una matriz de registros. Si no puede acceder aleatoriamente a las líneas, entonces el algoritmo que tiene es lo mejor que puede hacer.

Para un acceso más rápido, almacene los datos en 6 archivos, donde cada archivo es un índice del siguiente.

Hay muchas formas de crear índices de archivos planos. Usualmente uso un rango de subíndice. A medida que el usuario realiza cada selección, use el rango para limitar la lectura del siguiente archivo.

Así es como crearía los índices para los datos de muestra que proporcionó.

Por supuesto, el archivo debe estar ordenado. He numerado las líneas para ilustración; los números de línea no deberían aparecer en el archivo.

--| file3.dat |--
 0 Ford Truck F150 red
 1 Ford Truck F150 blue
 2 Ford Truck F150 black
 3 Ford Truck F150 silver
 4 Ford Truck F250 red
 5 Ford Truck F250 green
 6 Ford Sedan Taurus red
 7 Ford Sedan Taurus green
 8 Ford Sedan Taurus white
 9 Subaru SUV Forester blue
10 Subaru SUV Forester red
11 Subaru SUV Outback Black
12 Subaru SUV Outback Green
13 Subaru SUV Outback Blue
14 Subaru SUV Outback Red

Para crear el primer índice, haga un registro para cada combinación única de los primeros tres campos en el archivo. En cada registro, almacene los números de primera y última línea en los que aparece esa combinación.

--| file2.dat |--
 0 Ford Truck F150       0   3
 1 Ford Truck F250       4   5
 2 Ford Sedan Taurus     6   8
 3 Subaru SUV Forester   9  10
 4 Subaru SUV Outback   11  14

El segundo índice se construye de manera similar, utilizando los dos primeros campos del primer índice.

--| file1.dat |--
 0 Ford Truck        0   1
 1 Ford Sedan        2   2
 2 Subaru SUV        3   4

Y el tercero, el nivel superior en este caso, el índice.

--| file0.dat |--
 0 Ford          0   1
 1 Subaru        2   2

Creo que estoy sobreexplicando el concepto, pero en general se crea un índice al soltar el último campo y eliminar registros duplicados.

Puede reducir aún más el requisito de almacenamiento de archivos eliminando algunos datos redundantes.

Por ejemplo, el "primer" subíndice en cada registro de índice es siempre uno más que el "último" subíndice del registro anterior, o cero si no hay ningún registro anterior. Por lo tanto, no necesita almacenar los "primeros" subíndices. Los dejo en su lugar a continuación para ilustración.

Además, dado que usará solo el último campo en cada registro para completar la lista de selección, no necesita almacenar los otros campos.

La reproducción mínima de la cascada de índice termina pareciéndose a esto, donde la marca 'indica un número que no está realmente almacenado en el archivo.

--| file0.dat |--
 0' Ford         0'   1
 1' Subaru       2'   2

--| file1.dat |--
 0' Truck        0'   1
 1' Sedan        2'   2
 2' SUV          3'   4

--| file2.dat |--
 0' F150         0'   3
 1' F250         4'   5
 2' Taurus       6'   8
 3' Forester     9'  10
 4' Outback     11'  14

--| file3.dat |--
 0' red
 1' blue
 2' black
 3' silver
 4' red
 5' green
 6' red
 7' green
 8' white
 9' blue
10' red
11' Black
12' Green
13' Blue
14' Red

Cuando completa una lista de selección de un índice, utiliza los subíndices "primero" y "último" de la selección del usuario en el índice anterior para limitar las líneas leídas.

Ejemplo:

Rellena la primera lista de selección de todos file0.dat. (Ford, Subaru)

El usuario selecciona "Ford". Los subíndices correspondientes son 0 y 1.

Rellena la segunda lista de selección de las líneas 0 a 1 de file1.dat. (Camión, sedán)

El usuario selecciona "Sedan". Los subíndices correspondientes son 2 y 2.

Como puede ver, cuando el usuario haya seleccionado, por ejemplo, "Ford" "Sedan" "Taurus", encontrará que necesita leer solo las líneas 6 a 8 file3.datpara completar la cuarta lista de selección.

Pido disculpas por la descripción bastante larga, pero es muy tarde aquí y no tengo tiempo para escribir una breve.

AGREGADO: Pensándolo mejor, los archivos se pueden concatenar en uno.

--| file.dat |--
 0' -            1'   2
 1' Ford         3'   4
 2' Subaru       5'   5
 3' Truck        6'   7
 4' Sedan        8'   8
 5' SUV          9'  10
 6' F150        11'  14
 7' F250        15'  16
 8' Taurus      17'  19
 9' Forester    20'  21
10' Outback     22'  25
11' red          -'   -
12' blue         -'   -
13' black        -'   -
14' silver       -'   -
15' red          -'   -
16' green        -'   -
17' red          -'   -
18' green        -'   -
19' white        -'   -
20' blue         -'   -
21' red          -'   -
22' Black        -'   -
23' Green        -'   -
24' Blue         -'   -
25' Red          -'   -

Esto se usa exactamente como la versión de múltiples archivos, excepto que necesita la primera línea ficticia para contener el primer rango de subíndice.

AI Breveleri
fuente