Tabella di ricerca con matrice fissa

Ho implementato il seguente esercizio:

Implementa un tabella di ricerca con operazioni come find(struct table*, const char*), insert(struct table*, const char*,int) e remove(struct table*, const char*). La rappresentazione della tabella potrebbe essere un array di una coppia struct o una coppia di array (const char*[] e int*); scegli, scegli anche i tipi di ritorno per le tue funzioni.

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define ARR_SIZE 10 struct Pair { const char* word; int val; }; struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; }; struct Pair* make_pair(const char* word, int val) { assert(word); struct Pair* pair = (struct Pair*)malloc(sizeof(struct Pair)); pair->val = val; pair->word = word; return pair; } void table_empty(struct Table* tbl) { assert(tbl); size_t i = 0; for (i = 0; i < tbl->sz; ++i) { free(tbl->pairs[i]); tbl->pairs[i] = NULL; } tbl->sz = 0; } int search_word(struct Table* tbl, const char* word) { assert(tbl); assert(word); size_t i = 0; for (i = 0; i < tbl->sz; ++i) { //printf("%s %i\n", tbl->pairs[i]->word,i); if (strcmp(tbl->pairs[i]->word, word) == 0) { return i; } } return -1; // error } void table_insert(struct Table* tbl, const char* word, int val) { assert(tbl); assert(word); int i = search_word(tbl, word); if (i != -1) { // replace val tbl->pairs[i]->val = val; } else { // add new pair struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position ++tbl->sz; } } int table_find(struct Table* tbl, const char* word, int* return_val) { assert(tbl); assert(word); assert(return_val); int i = search_word(tbl, word); if (i != -1) { *return_val = tbl->pairs[i]->val; return 0; } return -1; // error not found } int table_remove(struct Table* tbl, const char* word) { assert(tbl); assert(word); int i = search_word(tbl, word); if (i == -1) return -1; free(tbl->pairs[i]); // free value at current pos tbl->pairs[i] = tbl->pairs[tbl->sz - 1]; // put address of last word at the pos of the current --tbl->sz; // "erase" last word return 0; } void table_print(struct Table* tbl) { assert(tbl); printf("\n"); printf("table size = %i\n", tbl->sz); for (int i = 0; i < tbl->sz; ++i) printf("%s %i\n", tbl->pairs[i]->word, tbl->pairs[i]->val); fflush(stdout); } void print_search_result(struct Table* tbl, const char* word) { assert(tbl); assert(word); int val = 0; if (table_find(tbl, word, &val) == 0) printf("%s %i\n",word, val); else printf("%s not found in table\n", word); printf("\n"); fflush(stdout); } void print_remove_result(struct Table* tbl, const char* word) { assert(tbl); assert(word); if (table_remove(tbl, word) == -1) printf("%s not deleted\n", word); else printf("%s deleted\n", word); printf("\n"); fflush(stdout); } int main() { struct Table table = { 0 }; int val = 0; table_insert(&table, "Hello", 10); table_insert(&table, "Test", 15); table_insert(&table, "Hello", 18); // testing overrite val table_insert(&table, "What", 5); table_insert(&table, "is", 3); table_insert(&table, "going", 4); table_insert(&table, "on", 77); table_print(&table); print_search_result(&table, "Hello"); print_search_result(&table, "Test"); print_search_result(&table, "keyword"); print_remove_result(&table, "Hello"); print_remove_result(&table, "Hello"); // double delete == false print_remove_result(&table, "What"); print_remove_result(&table, "going"); print_remove_result(&table, "is"); print_remove_result(&table, "on"); print_remove_result(&table, "Test"); table_print(&table); table_insert(&table, "Hello", 10); table_insert(&table, "Test", 15); table_insert(&table, "Hello", 18); // testing overrite val table_insert(&table, "What", 5); table_insert(&table, "is", 3); table_insert(&table, "going", 4); table_insert(&table, "on", 77); table_print(&table); table_empty(&table); table_print(&table); getchar(); } 

Sentiti libero di commentare eventuali miglioramenti. Cè un modo migliore per farlo?

Ho anche una domanda specifica: i miei usi di assert sono appropriati?

Commenti

  • Per scrupolo stilistico, ‘ consiglio * in un puntatore digitare adiacente al nome della variabile (ad esempio const char *word). La sintassi del tipo C è stata progettata per simulare lutilizzo, dove ad esempio ‘ d digita *word per accedere a const char elemento. Tieni presente anche che questo rende più chiaro il tipo di b in int *a, b;.

Risposta

Utilizzo di struct

Personalmente, penso che tu abbia fatto la scelta giusta utilizzando un struct con un char* e int al posto di 2 array di char* e int, rispettivamente. Pair è ununità concettuale di dati nella tua app, quindi ha senso metterli insieme. Se avessi 2 array separati, sarebbe facile per loro perdere la sincronizzazione e difficile eseguire il debug del motivo. Ottimo lavoro!

Preferisci const e le funzioni alle macro

Hai definito la dimensione dellarray utilizzando una macro. Questo inserisce sei in svantaggio durante la programmazione. Hai rimosso le informazioni sul tipo per il valore. In questo modo:

const int ARR_SIZE = 10; 

ottieni lindipendenza dai tipi. Modifica: Quello “è un C ++ – ismo che non funziona” t lavoro in C. Colpa mia! Ma il resto del consiglio nel prossimo paragrafo è corretto per quanto ne so.

Con le macro che accettano parametri corri il rischio che vengano utilizzate in modi inaspettati e causino problemi difficili da eseguire il debug. Fortunatamente, non lhai fatto qui. Ma in generale, se ti ritrovi a cercare una macro, chiediti se staresti meglio con una costante o una funzione. Lo sarai quasi sempre (supponendo che tu possa usare la costante nel modo desiderato).

Errori

Ci sono alcuni errori nel codice. In make_pair(), non controlli per vedere se malloc() è riuscito. Se fallisce, non viene allocata memoria e pair punta a NULL. Quando provi ad assegnare pair->val o pair->word ti arresti in modo anomalo.

Se lhai risolto, table_insert() utilizza il risultato di make_pair() senza controllare se “è NULL per primo. Questo ha vinto” t si blocca immediatamente, perché “stai semplicemente assegnando il puntatore allarray in tbl->pairs[tbl->sz] per avere il valore di pair. Quello che succederà è dopo provi a cercare, stampare o inserire un altro elemento, si verificherà un arresto anomalo quando ripeti quella voce nella tabella e provi a fare qualcosa con essa.

Potresti rendere questi errori non possibili se non allocando dinamicamente le voci dellarray. Rendi semplicemente larray un array di Pair strutture piuttosto che un puntatore ad esse.

Denominazione

Molti dei tuoi nomi sono davvero buoni . Pair e Table sono nomi accettabili e leggibili per questa attività. make_pair(), table_insert() e così via sono informativi. Alcuni potrebbero essere migliorati, però. tbl non ti fa risparmiare molto digitando su table. Usa la parola intera. Stessa cosa con sz e size. i è accettabile come nome di variabile di ciclo, ma sarebbe ancora meglio se fosse più descrittivo. Ad esempio, entry_index o pair_index. Dovrebbe descrivere ciò su cui stai “ripetendo.

Commenti

  • quando cambio la definizione in const int ARR_SIZE = 10; mi dà un errore in struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; }; che ARR_SIZE non è const, quindi come usarlo in questo caso?
  • Scusa, ‘ sa C ++ – ism. Pensavo fosse una domanda C ++. Colpa mia. ‘ aggiornerò la mia risposta.
  • Sono ‘ un convinto sostenitore dei nomi descrittivi delle variabili, ma con la semplicità dei cicli in questo programma penso che il nome della variabile i funziona perfettamente.
  • Se malloc() fallisce, assegnazione a pair->val o pair->word potrebbe arrestarsi in modo anomalo, se sei fortunato . Potrebbe semplicemente continuare e dare risultati sbagliati, o potrebbe fare qualcosa di totalmente inaspettato. Questa ‘ è la gioia di un comportamento indefinito!

Risposta

  • Non eseguire il cast del risultato di malloc.

    malloc restituisce void * e in C è valido per convertire void * in qualsiasi puntatore. Se esegui il cast per sopprimere un avviso relativo alla conversione da numero intero a puntatore, significa che un compilatore non ha un prototipo malloc e il suo valore predefinito restituisce un int (è unantica convenzione in C). Ora se int e il puntatore sono di dimensioni diverse, il valore restituito da malloc verrebbe troncato, con tutte le brutte conseguenze.

  • Non è consigliabile sizeof(type). Preferire sizeof(expression). Nel tuo caso, considera

     struct Pair * pair = malloc(sizeof(*pair)); 
  • table_insert inserisce ciecamente in una tabella completa. Dovrebbe testare tbl->sz < ARR_SIZE e restituisce unindicazione di errore se non lo è.

  • Leffettivo inserimento

     struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position 

    dovrebbe essere davvero una singola riga:

     tbl->pairs[tbl->sz] = make_pair(word, val); 

Commenti

  • perché non è una buona idea eseguire il cast di malloc? In c ++ si otterrebbe anche un errore del compilatore?
  • @Sa ndro4912 La domanda è contrassegnata con C. C ++ è un linguaggio diverso, in particolare sotto questo aspetto. Ora, se il compilatore C si lamenta, significa che manca il prototipo malloc, il che può portare a fastidiosi problemi.
  • sì, lo so è c. Mi chiedevo solo perché qui non è una buona pratica indicare i cambiamenti di tipo con un cast
  • @ Sandro4912 Vedi modifica.
  • @ Sandro4912 pair = malloc(sizeof *pair) è più facile da codificare correttamente la prima volta, più facile da rivedere e mantenere.

Risposta

Abilita tutto avvisi del compilatore

Con un compilatore ben abilitato, for (int i = 0; i < tbl->sz; ++i) avrebbe dovuto avvisare in caso di combinazione di segni di i e tbl->sz così come lintervallo. Risparmia tempo e abilita tutti gli avvisi e utilizza for (size_t i = 0; i < tbl->sz; ++i).

In generale, il codice è instabile quando si utilizza int,size_t quasi in modo intercambiabile . Ho “riorganizzato e utilizzato solo size_t.

Uso misto di copia superficiale e profonda

make_pair(const char* word, int val) alloca e forma un nuovo struct Pair (copia completa), ma non copia ciò a cui punta word.

Forse

// pair->word = word; pair->word = strdup(word); 

Usa const

search_word() non modifica *tbl, quindi utilizza const to convey that. Same for table_find () , table_print () , print_search_result () `.

// int search_word(struct Table* tbl, const char* word) int search_word(const struct Table* tbl, const char* word) 

Denominazione

Codice utilizza const char* word; ma non è una “parola”, ma una stringa come viene utilizzata in strcmp().

—– Aggiunte

Violazione del contratto?

Niente nei requisiti “Implementa una tabella di ricerca con operazioni come …” indica che const char* è un puntatore a una stringa . Quindi chiamare strcmp() è discutibile a meno che non esistano requisiti non dichiarati. Come char *, il codice potrebbe utilizzare un semplice confronto

// if (strcmp(tbl->pairs[i]->word, word) == 0) if (tbl->pairs[i]->word == word) 

Utilizzo di assert()

i miei usi di assert sono appropriati?

Se il char * puntatore per aggiungere / cercare non è specificato come una stringa , assert(word); non è corretto in quanto word == NULL non è noto per essere non valido.

Il assert(return_val) in table_find(struct Table* tbl, const char* word, int* return_val) va bene, ma vorrei riprogettare per consentire return_val == NULL

if (i != -1) { // add if (return_val) { *return_val = tbl->pairs[i]->val; } return 0; } 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *