Qual é a maneira mais eficiente de armazenar esses dados?


9

Estou encarregado de reescrever algum código VB antigo. Entendo como funciona, mas sinto que há uma maneira muito mais eficiente de fazer o que eles fizeram. Eu simplesmente não consigo descobrir o que é. Aqui está um exemplo artificial que, em termos de requisitos de dados, é realmente semelhante ao que eu preciso fazer.

O usuário deve escolher o fabricante, marca, modelo e cor do seu carro em uma GUI. Eu tenho um arquivo de texto grande que se parece com isso:

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.

Portanto, se a primeira seleção for Subaru, a segunda caixa (marca) não deverá ter a opção de selecionar Caminhão porque nenhum dos Subarus é caminhão. Da mesma forma, se eles selecionam Ford, Sedan e Taurus, a última caixa (cor) não deve mostrar uma opção para selecionar azul. Ou preto. Ou qualquer coisa que não seja vermelho, verde ou branco.

As pessoas que escreveram o código antes de mim vieram com isso (em 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

Eu acho isso hediondo, tanto no nível de algoritmos quanto no nível de sintaxe. Por um lado, ele analisa o arquivo inteiro, quando só precisa ler algumas linhas, se bem. Para tornar isso ainda mais ineficiente, meus dados reais têm 6 opções para selecionar, em vez de apenas 4. Isso também está armazenando mais dados do que é necessário, dada a quantidade de duplicação de dados.

Estou procurando uma maneira diferente de armazenar os dados no arquivo ou uma maneira diferente de analisá-los para tornar a getValidOptionsfunção mais bonita e mais eficiente. Há alguma maneira de eu fazer isso?


2
por que não usar um banco de dados?
Tulains Córdova

Respostas:


6

Todas as outras respostas que li parecem ignorar duas regras muito básicas de desenvolvimento de software:

  • esclareça primeiro os requisitos (especialmente os requisitos de desempenho e armazenamento)

  • Mantenha as coisas simples, estúpidas (veja KISS )

Você escreveu "o arquivo de texto é grande", mas não escreveu muito grande, portanto, presumo que não há nada de errado com seu tamanho, exceto seu pressentimento. Portanto, se o carregamento do arquivo não demorar, o departamento de TI ou alguém não reclamar do espaço em disco desperdiçado e ninguém reclamar de problemas para manter o arquivo, deixe o formato do arquivo como está - não subestime valor da simplicidade.

Você também está reclamando da eficiência do algoritmo, que na verdade não é tão eficiente quanto poderia ser, mas tem uma grande vantagem: é simples, com morte encefálica e funciona. Portanto, desde que seja eficiente o suficiente, não aplique nenhuma otimização prematura.

Então, vamos supor que o programa funcione rápido o suficiente, o que você deve perguntar primeiro é como você pode melhorar o código em termos de simplicidade e do princípio DRY? E esse é realmente um ponto que deve ser aprimorado, pois o código atual não é SECO. No seu exemplo, aparece quase quatro vezes o mesmo teste de bloco de código se as opções nos "níveis mais altos" corresponderem à linha atual, o que resulta em quatro vezes o mesmo tipo de chamada "anexar" (no seu código real, a repetição ocorre pelo menos seis vezes, como você escreveu). Você pode evitar isso facilmente introduzindo um nível de seleção numérica ou passando as opções já selecionadas em uma lista ordenada. Usando seu pseudo-código, isso leva a algo ao longo das linhas 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

Portanto, este é essencialmente o mesmo algoritmo sem código repetido.

Como é óbvio getValidOptionsque deve ser chamado mais de uma vez (pelo menos uma vez por nível), sugiro aplicar apenas uma otimização (se esse ainda não for o caso): verifique se a getLinefunção extrai seus dados da memória principal e não leia o arquivo do disco novamente e novamente.


Você deseja mover "level = selectedOptions.size ()" antes do loop numRows.
AI Breveleri

6

Bem, os dados que você possui têm uma estrutura de árvore, onde para cada fabricante você tem uma árvore de modelos e para cada modelo você tem uma cor (e assim por diante).

Portanto, você pode separar o processo desses dados de duas etapas:

  1. Após qualquer atualização no arquivo de texto, você deve processar esse arquivo e convertê-lo em uma estrutura em árvore.
  2. Ao carregar o aplicativo, você carrega apenas a estrutura em árvore.

A estrutura em árvore pode ser implementada com o que é chamado de hash , uma matriz associativa ou um dicionário em linguagens como Java, PHP, Javascript ou Python. Com essa estrutura, você tem:

  • As primeiras chaves são os fabricantes.
  • Seus valores são apenas outros hashes ou dicionários em que cada chave é o modelo.
  • Seus valores são as cores. Ou outra estrutura mantendo em suas chaves o terceiro nível e, como valor, o quarto nível.
  • E assim por diante...

Dependendo da sua linguagem de programação, isso pode ser implementado mais rápido ou mais devagar. Por exemplo:

  • C # : você pode implementar uma estrutura em árvore 1 e marcá-la como serializável.
  • VB.Net : você pode gerar a estrutura em árvore em um aplicativo e serializá-la em um arquivo.
    • Para isso, algo como Runtime.Serialization.Formatters.Binary.BinaryFormatterpoderia ser útil, mas não sou especialista em serializar com o VB.Net.
  • Javascript : você pode gerar a estrutura em árvore em um arquivo JSON, que deve ser carregado toda vez que o aplicativo for carregado.
  • PHP : você pode gerar uma versão serializada da estrutura de dados da árvore ou também pode carregar um JSON.
  • Java : você pode serializar essa estrutura de dados, criando uma Classque implemente a interface java.io.Serializable.

Referências :

1: https://dvanderboom.wordpress.com/2008/03/15/treet-implementing-a-non-binary-tree-in-c/
- Uma explicação completa sobre a implementação de uma árvore em C #.
- Procure um comentário em que alguém pergunte sobre serializar esse objeto e a resposta para esse comentário.


11
Sim, eu pensei em usar uma árvore, mas não sei se é a melhor ideia, porque não há estrutura de árvore incorporada (que eu saiba) em C #, e o projeto é bem pequeno, então não sei se valeria a pena gastar muito tempo trabalhando em uma tree with an arbitrary number of nodesimplementação.
James

Até o momento, não sou especialista em C #, mas pelo menos em outras linguagens como Java e PHP, você pode ter algum tipo de dicionário, onde cada chave pode ter como valor outro dicionário.
Nicolás

Eu atualizei minha resposta. Veja o que você pensa sobre a alternativa de hash ou dicionário. Eu também adicionei uma referência com um artigo interessante.
Nicolás

3

Uma maneira simples de armazenar os dados é inseri-los em um banco de dados SQLite. O SQLite, diferentemente da maioria dos bancos de dados SQL, é adequado para uso como um formato de arquivo de aplicativo. Essa abordagem tem vários benefícios:

  1. Não há necessidade de codificar um serializador ou um desserializador.
  2. O arquivo é editável e pode ser consultado por vários programas existentes.
  3. A confusão condicional que você menciona na pergunta é evitada. Para limitar as listas suspensas, gere uma cláusula where simples em cada coluna (por exemplo, select distinct model where manufacturer='ford' and color = 'red').

Isso força você a aprender SQL, mas evita a necessidade de aprender um formato de arquivo personalizado.


1

Presumo que você possa acessar linhas aleatoriamente no arquivo e, portanto, possa tratá-lo como uma matriz de registros. Se você não puder acessar as linhas aleatoriamente, o algoritmo que você possui é o melhor que você pode fazer.

Para acesso mais rápido, armazene os dados em 6 arquivos, onde cada arquivo é um índice para o próximo.

Existem várias maneiras de criar índices flatfile. Eu costumo usar um intervalo de subscritos. À medida que o usuário faz cada seleção, use o intervalo para limitar a leitura do próximo arquivo.

Aqui está como eu criaria os índices para os dados de amostra que você forneceu.

Claro que o arquivo deve ser classificado. Eu numerei as linhas para ilustração; os números das linhas não devem aparecer no arquivo.

--| 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 criar o primeiro índice, faça um registro para cada combinação exclusiva dos três primeiros campos no arquivo. Em cada registro, armazene o primeiro e o último número de linha em que essa combinação aparece.

--| 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

O segundo índice é construído da mesma forma, usando os dois primeiros campos do primeiro índice.

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

E o terceiro, o nível superior, neste caso, índice.

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

Acho que estou superexplicando o conceito, mas em geral você cria um índice eliminando o último campo e eliminando registros duplicados.

Você pode reduzir ainda mais os requisitos de armazenamento de arquivos, eliminando alguns dados redundantes.

Por exemplo, o "primeiro" subscrito em cada registro de índice é sempre um a mais que o "último" subscrito do registro anterior, ou zero se não houver registro anterior. Portanto, você não precisa armazenar os "primeiros" subscritos. Estou deixando-os no lugar abaixo para ilustração.

Além disso, como você usará apenas o último campo em cada registro para preencher a lista de seleção, não será necessário armazenar os outros campos.

A renderização mínima da cascata de índices acaba sendo assim, onde o tick 'indica um número realmente não armazenado no arquivo.

--| 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

Ao preencher uma lista de seleção de um índice, você usa os subscritos "primeiro" e "último" da seleção do usuário no índice anterior para limitar as linhas lidas.

Exemplo:

Você preenche a primeira lista de seleção de todos file0.dat. (Ford, Subaru)

O usuário seleciona "Ford". Os subscritos correspondentes são 0 e 1.

Você preenche a segunda lista de seleção das linhas 0 a 1 de file1.dat. (Caminhão, Sedan)

O usuário seleciona "Sedan". Os subscritos correspondentes são 2 e 2.

Como você pode ver, no momento em que o usuário selecionou, por exemplo, "Ford" "Sedan" "Taurus", você encontrará que precisa ler apenas as linhas 6 a 8 file3.datpara preencher a quarta lista de seleção.

Peço desculpas pela descrição bastante longa, mas é muito tarde aqui e não tenho tempo para escrever uma breve.

ADICIONADO: Pensando melhor, os arquivos podem ser concatenados em um.

--| 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          -'   -

Isso é usado exatamente como a versão de vários arquivos, exceto que você precisa da primeira linha fictícia para conter o primeiro intervalo de subscritos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.