Jaki jest najskuteczniejszy sposób przechowywania tych danych?

9

Odpowiadam za przepisywanie starego kodu VB. Rozumiem, jak to działa, ale wydaje mi się, że istnieje o wiele bardziej skuteczny sposób na robienie tego, co zrobili. Po prostu nie mogę zrozumieć, co to jest. Oto wymyślony przykład, że pod względem wymagań dotyczących danych jest naprawdę podobny do tego, co muszę zrobić.

Użytkownik musi wybrać producenta, markę, model i kolor swojego samochodu w GUI. Mam duży plik tekstowy, który wygląda mniej więcej tak:

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.

Więc jeśli pierwszym wyborem jest Subaru, drugie pole (marka) nie powinno mieć opcji wyboru Ciężarówka, ponieważ żadne z Subarus nie jest ciężarówkami. Podobnie, jeśli wybiorą Ford, Sedan i Byk, to ostatnie pole (kolor) nie powinno pokazywać opcji wyboru niebieskiego. Lub czarny. Lub cokolwiek innego niż czerwony, zielony lub biały.

Ludzie, którzy napisali kod przede mną, wymyślili to (w 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

Myślę, że jest to po prostu ohydne, zarówno na poziomie algorytmu, jak i na poziomie składni. Po pierwsze, parsuje cały plik, kiedy musi tylko przeczytać kilka wierszy, jeśli zostanie to zrobione poprawnie. Aby uczynić to jeszcze bardziej nieefektywnym, moje rzeczywiste dane mają 6 opcji do wyboru, a nie tylko 4. To także przechowuje więcej danych, niż trzeba, biorąc pod uwagę ilość duplikacji danych.

Szukam innego sposobu przechowywania danych w pliku lub innego sposobu ich analizy, aby getValidOptionsfunkcja była ładniejsza i bardziej wydajna. Czy są na to sposoby?

James
źródło
2
dlaczego nie skorzystać z bazy danych?
Tulains Córdova,

Odpowiedzi:

6

Wszystkie pozostałe odpowiedzi, które czytam, wydają się ignorować dwie bardzo podstawowe zasady tworzenia oprogramowania:

  • najpierw wyjaśnij wymagania (szczególnie wymagania dotyczące wydajności i przechowywania)

  • Keep It Simple, Stupid (patrz KISS )

Napisałeś „plik tekstowy jest duży”, ale nie napisałeś zbyt dużego, więc zakładam, że tak naprawdę nie ma nic złego w jego rozmiarze poza wrażeniami z jelit. Jeśli więc ładowanie pliku nie trwa zbyt długo, a dział IT lub ktoś inny nie narzeka na zmarnowane miejsce na dysku i nikt nie skarży się na problemy z utrzymaniem pliku, niech format pliku pozostanie taki, jaki jest - nie lekceważ wartość prostoty.

Narzekasz również na wydajność algorytmu, który w rzeczywistości nie jest tak wydajny, jak mógłby być, ale ma jedną bardzo dużą zaletę: jest niesamowicie prosty i działa. Tak długo, jak jest wystarczająco wydajny, nie stosuj żadnej przedwczesnej optymalizacji.

Załóżmy więc, że program działa wystarczająco szybko. Najpierw należy zapytać, w jaki sposób można poprawić kod pod względem prostoty i zasady DRY? I to jest rzeczywiście kwestia, którą należy poprawić, ponieważ obecny kod nie jest SUCHY. W twoim przykładzie czterokrotnie pojawia się prawie taki sam test bloku kodu, jeśli opcje na „wyższych poziomach” pasują do bieżącego wiersza, co powoduje czterokrotnie ten sam rodzaj wywołania „append” (w prawdziwym kodzie powtórzenie występuje co najmniej sześć razy, jak napisałeś). Można tego łatwo uniknąć, wprowadzając numeryczny poziom wyboru lub przechodząc już wybrane opcje z uporządkowanej listy. Używając twojego pseudokodu, prowadzi to do czegoś podobnego do

# 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

Jest to zasadniczo ten sam algorytm, bez powtarzającego się kodu.

Ponieważ oczywiste getValidOptionsjest, że należy wywoływać więcej niż jeden raz (przynajmniej raz na poziom), proponuję zastosować tylko jedną optymalizację (jeśli tak nie jest): upewnij się, że getLinefunkcja pobiera dane z pamięci głównej i nie czytaj plik z dysku raz po raz.

Doktor Brown
źródło
Chcesz przenieść „level = selectedOptions.size ()” przed pętlą numRows.
AI Breveleri,
6

Cóż, dane, które masz, mają strukturę drzewiastą, gdzie dla każdego producenta masz drzewo modeli, a dla każdego modelu masz kolor (i tak dalej).

Tak więc proces tych danych można rozdzielić na dwa etapy:

  1. Po każdej aktualizacji pliku tekstowego musisz przetworzyć ten plik i przekonwertować go na strukturę drzewa.
  2. Podczas ładowania aplikacji ładowana jest tylko struktura drzewa.

Struktura drzewa może być implementowana za pomocą tak zwanego skrótu , tablicy asocjacyjnej lub słownika w językach takich jak Java, PHP, JavaScript lub Python. Dzięki tej strukturze masz:

  • Pierwszymi kluczami są producenci.
  • Ich wartości to po prostu kolejne skróty lub słowniki, w których każdy klucz jest modelem.
  • Ich wartości to kolory. Lub inna struktura utrzymująca w swoich kluczach trzeci poziom i jako wartość czwarty poziom.
  • I tak dalej...

W zależności od języka programowania można to zrobić szybciej lub wolniej. Na przykład:

  • C # : możesz zaimplementować strukturę drzewa 1 , a następnie oznaczyć ją jako możliwą do serializacji.
  • VB.Net : możesz wygenerować strukturę drzewa w jednej aplikacji i serializować ją w pliku.
    • Do tego Runtime.Serialization.Formatters.Binary.BinaryFormattermoże być coś podobnego , ale nie jestem ekspertem w serializacji z VB.Net.
  • JavaScript : możesz wygenerować strukturę drzewa w pliku JSON, który musi być ładowany przy każdym ładowaniu aplikacji.
  • PHP : możesz wygenerować zserializowaną wersję struktury danych drzewa lub załadować również JSON.
  • Java : możesz serializować tę strukturę danych, tworząc Classimplementację interfejsu java.io.Serializable.

Referencje :

1: https://dvanderboom.wordpress.com/2008/03/15/treet-implementing-a-non-binary-tree-in-c/
- Pełne wyjaśnienie dotyczące implementacji drzewa w C #.
- Poszukaj komentarza, w którym ktoś pyta o serializację tego obiektu, i odpowiedzi na ten komentarz.

Nicolás
źródło
1
Tak, myślałem o użyciu drzewa, ale nie wiem, czy to najlepszy pomysł, ponieważ nie ma wbudowanej struktury drzewa (o której wiem) w C #, a projekt jest dość mały, więc nie wiem, czy warto poświęcić bardzo dużo czasu na tree with an arbitrary number of nodeswdrożenie.
James
Na razie nie jestem ekspertem w C #, ale przynajmniej w innych językach, takich jak Java i PHP, możesz mieć jakieś słowniki, w których każdy klucz może mieć jako wartość inny słownik.
Nicolás
Zaktualizowałem swoją odpowiedź. Zobacz, co myślisz o skrócie lub słowniku. Dodałem również odniesienie do interesującego artykułu.
Nicolás,
3

Jednym prostym sposobem przechowywania danych jest po prostu umieszczenie ich w bazie danych SQLite. SQLite, w przeciwieństwie do większości baz danych SQL, dobrze nadaje się do użycia jako format pliku aplikacji. Takie podejście ma kilka zalet:

  1. Nie trzeba kodować serializatora ani deserializatora.
  2. Plik jest edytowalny i możliwy do przeszukiwania przez wiele istniejących programów.
  3. Unika się warunkowego bałaganu, o którym wspominasz w pytaniu. Aby ograniczyć listy rozwijane, wygeneruj prostą klauzulę where dla każdej kolumny (np select distinct model where manufacturer='ford' and color = 'red'.).

Zmusza to do nauki SQL, ale pozwala uniknąć potrzeby uczenia się niestandardowego formatu pliku.

Brian
źródło
1

Zakładam, że możesz losowo uzyskiwać dostęp do wierszy w pliku, a zatem możesz traktować plik jako tablicę rekordów. Jeśli nie możesz losowo uzyskać dostępu do linii, algorytm, który masz, jest najlepszy, co możesz zrobić.

Aby uzyskać najszybszy dostęp, przechowuj dane w 6 plikach, gdzie każdy plik jest indeksem do następnego.

Istnieje wiele sposobów tworzenia indeksów plików płaskich. Zwykle używam zakresu indeksu dolnego. Gdy użytkownik dokonuje każdego wyboru, użyj zakresu, aby ograniczyć odczyt następnego pliku.

Oto jak utworzę indeksy dla przykładowych danych, które podałeś.

Oczywiście plik musi zostać posortowany. Numerowałem wiersze dla ilustracji; numery wierszy nie powinny pojawiać się w pliku.

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

Aby utworzyć pierwszy indeks, zrób rekord dla każdej unikalnej kombinacji pierwszych trzech pól w pliku. W każdym rekordzie przechowuj numery pierwszego i ostatniego wiersza, w którym pojawia się ta kombinacja.

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

Drugi indeks jest skonstruowany podobnie, z wykorzystaniem pierwszych dwóch pól pierwszego indeksu.

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

I trzeci, najwyższy poziom w tym przypadku, indeks.

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

Wydaje mi się, że wyjaśniam tę koncepcję nadmiernie, ale generalnie tworzysz indeks, upuszczając ostatnie pole i eliminując duplikaty rekordów.

Możesz dodatkowo zmniejszyć wymagania dotyczące przechowywania plików, eliminując zbędne dane.

Na przykład „pierwszy” indeks dolny w każdym rekordzie indeksu jest zawsze o jeden większy niż „ostatni” indeks dolny poprzedniego rekordu lub zero, jeśli nie ma wcześniejszego rekordu. Nie musisz więc przechowywać „pierwszych” indeksów dolnych. Zostawiam je poniżej dla ilustracji.

Ponieważ do wypełnienia listy wyboru użyjesz tylko ostatniego pola w każdym rekordzie, nie musisz przechowywać innych pól.

Minimalne wyświetlanie kaskady indeksu kończy się tak, że znacznik „wskazuje liczbę, która nie jest faktycznie przechowywana w pliku.

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

Gdy wypełniasz listę wyboru z indeksu, używasz „pierwszego” i „ostatniego” indeksu dolnego z wyboru użytkownika z poprzedniego indeksu, aby ograniczyć odczytane linie.

Przykład:

Wypełniasz pierwszą listę wyboru ze wszystkich file0.dat. (Ford, Subaru)

Użytkownik wybiera „Ford”. Odpowiednie indeksy dolne to 0 i 1.

Wypełniasz drugą listę wyboru z linii od 0 do 1 z file1.dat. (Ciężarówka, sedan)

Użytkownik wybiera „Sedan”. Odpowiednie indeksy dolne to 2 i 2.

Jak widać, zanim użytkownik wybierze, np. „Ford” „Sedan” „Byk”, przekonasz się, że potrzebujesz tylko do odczytu linii od 6 do 8, file3.databy wypełnić czwartą listę wyboru.

Przepraszam za dość długi opis, ale jest już bardzo późno i nie mam czasu na napisanie krótkiego.

DODANO: Po dalszej przemyśleniu pliki można połączyć w jeden.

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

Jest to używane dokładnie tak samo, jak wersja z wieloma plikami, z tym wyjątkiem, że pierwszy fałszywy wiersz musi zawierać pierwszy zakres indeksu dolnego.

AI Breveleri
źródło