Memorizza i dati csv come righe o colonne in vista dell'elaborazione necessaria?

3

Supponiamo di avere alcuni dati in file CSV come

ObjectName, PropertyA, PropertyB, PropertyC
"Name1", 3, 1, 4
"Name2", 1, 5, 9
"Name3", 2, 6, 5
...

e una domanda tipica a cui vorrei rispondere sarebbe

For which Object is PropertyX maximal?

Ho due approcci per questo e sarei grato per alcuni Input.

Approccio 1

Definisco una classe come

struct Object {
    std::String name;
    int a;
    int b;
    int c;
};

Archivia i dati in una classe come

class ObjectCollection {
    std::vector<Object> collection;
}

E fornire due funzioni

size_t ObjectCollection::getMaxIndexOfA()
size_t ObjectCollection::getMaxIndexOfB()
size_t ObjectCollection::getMaxIndexOfC()

Ora queste funzioni sarebbero essenzialmente uguali e assomigliano a

size_t maxIndex = -1;
int max = std::numeric_limits<int>::min();
for (size_t i = 0; i < collection.size(); ++i) {
    if (collection[i].a > max) {
        maxIndex = i;
        max = collection[i].a; 
    }
}
return maxIndex;

Mi dà fastidio che dovrei scrivere e mantenere lo stesso codice due volte.

Approccio 2

Archivia i dati in una classe come

class ObjectCollection {
    std::vector<String> names;
    std::vector<int> a;
    std::vector<int> b;
    std::vector<int> c;
}

Quindi potrei fornire metodi come

const std::vector<int>& ObjectCollection::getA() const;
const std::vector<int>& ObjectCollection::getB() const;
const std::vector<int>& ObjectCollection::getC() const;

E usa una singola funzione per trovare il massimo che devo chiamare come

getMaxIndex( collection.getA() );

dove size_t getMaxIndex(const std::vector<int>&) sarebbe essenzialmente uguale all'approccio 1.

Penso di preferire il secondo approccio, ma mi dà fastidio che in questo caso non ci sia una classe che rappresenta un singolo oggetto.

È strano / cattivo design per archiviare i dati come nel secondo approccio? C'è un altro approccio intelligente a cui non ho pensato?

A proposito, sono più interessato alla scelta tra questi due approcci che al fatto che probabilmente dovrei usare std::max_element per trovare l'indice.

    
posta Elvorfirilmathredia 13.12.2017 - 10:22
fonte

4 risposte

3

Is there another smart approach I didn't think about?

Se le tue proprietà sono dello stesso tipo e si comportano in modo simile, perché non indirizzarle con un indice?

enum PropIndex{PropertyA=0, PropertyB=1, PropertyC=2};

struct Object {
    std::String name;
    std::vector<int> property(3); // use PropIndex to access the value you want

    // if that helps for convenience, you can also add getters and setters like this one 
    int getA(){ return property(PropertyA); }
    //  ... but then you should consider to make the public members 
    //  ... all private, and provide proper public getters/setter for all of them
};

Ora devi implementare la tua funzione massima una sola volta (con un parametro aggiuntivo quale proprietà vuoi).

    
risposta data 13.12.2017 - 12:12
fonte
1

L'approccio 1 è indiscutibilmente il miglior design - rappresenta la "realtà" dei dati molto meglio, il che renderà molto più facile la comprensione per gli sviluppatori futuri.

    
risposta data 13.12.2017 - 10:32
fonte
1

Quindi entrambi. Non esiste una regola scritta che tu debba rispettare il modello che hai scelto. Mantieni la classe ObjectCollection e un metodo come getObject(int index) restituisce un'istanza Object per tutti i valori di tale riga.

Il tuo metodo getObject dovrebbe semplicemente cercare i valori lateralmente su tutti i vettori e mettere i loro riferimenti in Object class.

Se pensi che potresti dover chiamare molto questo metodo, considera la possibilità di rappresentare i dati in entrambi modi. Sì, vale a dire, rendere ObjectCollection class tenere i dati delle colonne vettoriali, nonché un singolo vettore con Object istanze. Ovviamente useresti più memoria, ma questo è il vecchio compromesso tra memoria e prestazioni. Se ritieni che le prestazioni siano più importanti qui, non esitare a farlo in questo modo!

    
risposta data 13.12.2017 - 10:52
fonte
1

Il dominio va prima!

Il design dovrebbe mirare a rappresentare correttamente il dominio, prima di entrare in considerazioni tattiche.

Quindi l'approccio 1 è la strada da percorrere. Riflette perfettamente la struttura reale dei tuoi dati e applica la separazione delle preoccupazioni tra Collection e Object . Ad esempio, puoi facilmente modificare il contenitore della raccolta (ad esempio, sostituendo l'array con una mappa o un elenco collegato) o le proprietà degli oggetti (ad esempio collection[i].average_abc() ).

DRY che utilizza i puntatori al membro

Puoi scrivere una funzione di helper privata che funzionerebbe con un puntatore al membro invece di essere associato a un membro specifico:

class ObjectCollection {
    size_t getMaxIndexOf(int Object::* o); // private helper function 
public:
    std::vector<Object> collection;
    size_t getMaxIndexOfA();
    size_t getMaxIndexOfB();
    size_t getMaxIndexOfC();
};

Un puntatore al membro è una bestia molto speciale: è relativa a un oggetto, in modo che tu possa utilizzare lo stesso puntatore e combinarlo con tutti gli elementi della tua raccolta:

size_t ObjectCollection::getMaxIndexOf(int Object::* o) {
    size_t maxIndex = -1;
    int max = std::numeric_limits<int>::min();
    for (size_t i = 0; i < collection.size(); ++i) {
       if (collection[i].*o > max) {  // <---- use of the pointer to member
           maxIndex = i;
           max = collection[i].*o;  // <---- use of the pointer to member
       }
    }
    return maxIndex;
}

Puoi quindi riutilizzare la tua funzione di aiuto come segue:

size_t ObjectCollection::getMaxIndexOfA() {
    return getMaxIndexOf(&Object::a);
}
size_t ObjectCollection::getMaxIndexOfB() {
    return getMaxIndexOf(&Object::b);
}
size_t ObjectCollection::getMaxIndexOfC() {
    return getMaxIndexOf(&Object::c);
}

Ecco una demo online

DRY con algoritmi standard

Come suggerito da Amon nei commenti, potresti usare std::max_element() :

size_t ObjectCollection::getMaxIndexOfA() {
    auto it = std::max_element(collection.begin(), collection.end(), 
                            [](auto &o1, auto &o2){ return o1.a<o2.a; });
    return std::distance(collection.begin(), it);
}

Questo algoritmo standard restituisce un iteratore, ma come mostrato, puoi convertirlo in un indice.

Demo online

    
risposta data 13.12.2017 - 21:58
fonte

Leggi altre domande sui tag