Custom sort by value using tokyo cabinet in C - c

I'm implementing a btree using tokyo cabinet, but I'd like to know if it's possible to keep the values sorted. I know I can use a tcbdbsetcmpfunc to set the custom comparison function for the keys, but not sure about the values?
I ask this because most of the time I only need the first 1000 records assuming my values are sorted. Otherwise I will have to loop over millions of records sort them and get the first 1000, which can be slow.
For instance:
#include <tcutil.h>
#include <tcbdb.h>
#include <stdbool.h>
#include <stdint.h>
struct foo {
int one;
double two;
char *three;
};
// sort by three field
static int val_cmp(const char *aptr, int asiz, const char *bptr, int bsiz, void *op) {
return 1;
}
int main() {
int ecode;
TCBDB *db;
db = tcbdbnew();
struct foo *f;
tcbdbsetcmpfunc(db, val_cmp, f); // sort by struct->three?
// open the database
if(!tcbdbopen(db, "struct.tcb", BDBOWRITER | BDBOCREAT)){
ecode = tcbdbecode(db);
fprintf(stderr, "open error: %s\n", tcbdberrmsg(ecode));
}
f = malloc(sizeof(struct foo));
f->one = 100;
f->two = 1.1111;
f->three = "Hello World";
printf("put: %d\n", tcbdbput(db, "foo", 3, f, sizeof(struct foo)));
f = malloc(sizeof(struct foo));
f->one = 100;
f->two = 1.1111;
f->three = "Hello Planet";
printf("put: %d\n", tcbdbput(db, "bar", 3, f, sizeof(struct foo)));
char *key;
BDBCUR *cursor;
cursor = tcbdbcurnew(db);
tcbdbcurfirst(cursor);
while ((key = tcbdbcurkey2(cursor)) != NULL) {
struct foo *val;
int size;
val = tcbdbcurval(cursor, &size);
printf("%s: one=%d\n", key, val->one);
printf("%s: two=%f\n", key, val->two);
tcbdbcurnext(cursor);
}
tcbdbdel(db);
return 0;
}

I think you cannot define an order for values.
I recommend to have a second tokyocabinet db, mapping
from values to keys. I expect that with this one indirection
you still get nice performance.

The tokyo cabinet have not the embed sort mechanism. You can like to use the list and it self custom ordering

Related

How do i create multiple distinguishable members in a struct

I need to store multiple distinguishable members in a struct for a personal diary to show different records for each day. I want multiple members like e1 without writing it 365 times. I want a solution that works e1 on a loop or on an array.
struct add
{
char day[365];
char date[365];
char time[365];
char place[365];
char data[365];
};
int main()
{
int i=1;
struct add e1;
while(i!=0)
{
printf("Enter Day:");
gets(e1.day);
printf("Enter Date:");
gets(e1.date);
printf("Enter Time:");
gets(e1.time);
printf("Enter Place:");
gets(e1.place);
printf("Tell me about your day:");
gets(e1.data);
printf("\n\n\n");
printf("Day: %s\n",e1.day);
printf("Day: %s\n",e1.date);
printf("Day: %s\n",e1.time);
printf("Day: %s\n",e1.place);
printf("Day: %s\n\n\n",e1.data);
}
return 0;
}
I don't think its possible to create different variable with each loop, you can refer to this question for info. Instead of making different instances, make your struct in a way so that it stores data for all (365) days. Something like array of strings so that it stores for all days, currently even if you write something like char day[365] it creates separate day for each instance. make something like:
struct add
{
char day[NUMBER_OF_STRING][MAX_STRING_SIZE]; // syntax
// MAX_STRING_SIZE does not need to be 365, i just used it because you used that
// otherwise it does not make sense, date , time etc won't take more than 10 char
char date[365][365];
char time[365][365];
char place[365][365];
char data[365][365];
};
Now keep on adding to a single instance of struct add using for loop, this should be easy.
Also Better approach can be to create a array of struct add itself with your original struct code.
This example does what you want, and you can create as many instance of add as you want.
I limit your loop to run once and have tested that the code works with visual studio 2019:
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#define DAY_SIZ 20
#define DATE_SIZ 40
#define TIME_SIZ 20
#define PLACE_SIZ 120
#define DATA_SIZ (BUFSIZ - 1)
struct add
{
char day[DAY_SIZ];
char date[DATE_SIZ];
char time[TIME_SIZ];
char place[PLACE_SIZ];
char data[DATA_SIZ];
};
struct add* create_add_struct() {
struct add* rec;
rec = (struct add*)malloc(sizeof(struct add));
return rec;
}
void destroy_add_struct(struct add* rec) {
free(rec);
}
void clear_add_struct(struct add* rec) {
memset(rec->day, 0, sizeof(rec->day));
memset(rec->date, 0, sizeof(rec->date));
memset(rec->time, 0, sizeof(rec->time));
memset(rec->place, 0, sizeof(rec->place));
memset(rec->data, 0, sizeof(rec->data));
}
void get_text_from_user(const char prompt[], char *txt, int siz) {
char buf[BUFSIZ];
printf("%s", prompt);
fgets(buf, BUFSIZ, stdin);
memcpy(txt, buf, siz);
}
void fill_add_struct(struct add* rec) {
printf("Do not enter any lines longer than %d\n\n", BUFSIZ);
get_text_from_user("Enter Day:", rec->day, sizeof(rec->day) - 1);
get_text_from_user("Enter Date:", rec->date, sizeof(rec->date) - 1);
get_text_from_user("Enter Time:", rec->time, sizeof(rec->time) - 1);
get_text_from_user("Enter Place:", rec->place, sizeof(rec->place) - 1);
get_text_from_user("Tell me about your day:", rec->data, sizeof(rec->data) - 1);
}
void print_add_struct(struct add* rec) {
printf("\n\n\n");
printf("Day : %s\n", rec->day);
printf("Date : %s\n", rec->date);
printf("Time : %s\n", rec->time);
printf("Place: %s\n", rec->place);
printf("Data : %s\n\n\n", rec->data);
}
int main()
{
// The number of times we want the loop to run
int i = 1;
// Collect data from the user and print it
while (i-- != 0)
{
// Allocate memory and initialize it
struct add* rec = create_add_struct();
clear_add_struct(rec);
// Get data from the user and print it
fill_add_struct(rec);
print_add_struct(rec);
// Release memory
destroy_add_struct(rec);
}
// Return success
return 0;
}
The reason I allocate your structure, is to make sure a stack-overflow does not occur, no pun intended.
If you want to create 365 add structs, you can create them like so:
struct add **records;
records = (struct add **) malloc(sizeof(struct add *) * 365);
for(int idx = 0; idx < 365; ++idx) {
records[idx] = create_add_struct();
clear_add_struct(records[idx]);
/* ... insert code ... */
}
/* ... insert code ... */
// Free memory
for(int idx = 0; idx < 365; ++idx) {
destroy_add_struct(records[idx]);
}
free(records);
I have not tested if the last piece of code compiles and works. Again I use malloc() and free() to avoid a stack-overflow.
typedef struct
{
time_t tm;
char *description;
}entry;
typedef struct day
{
time_t date;
size_t nentries;
struct day *next;
entry entries[];
}day;
and create linked list od days. Every day has array of diary entries.

Sorting structures from files

I recently got an assignment to sort members in a struct by last name and if they are the same to sort by first name. What i have so far only reads their name and age from the file but I am not properly grapsing how I would be able to sort it. So far I gathered the data from the file but im at a loss from there. I followed a code I saw but i didnt get a proper grasping of the process so i reverted back to step one.
struct Members{
int id;
char fname[50];
char lname[50];
int age;
}bio;
int main(){
int i=0;
FILE *fptr;
file = fopen("Members Bio.txt", "r");
while ( fscanf(file, "%d%s%s%d", &bio[i].id,bio[i].fname,bio[i].lname,&bio[i].age) != EOF)
{
printf("%d %s %s %d %d\n", bio[i].id,bio[i].fname, bio[i].lname, bio[i].age);
i++;
}
fclose(fptr);
}
Can anyone help me out on this one?
Code goes something like this for your case.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Members{
int id;
char fname[50];
char lname[50];
int age;
};
typedef int (*compare_func)(void*, void*);
int struct_cmp(void* s1, void* s2)
{
int l_result = strcmp(((struct Members*) s1)->lname, \
((struct Members*) s2)->lname);
if (l_result < 0)
return 1;
else if (l_result > 0)
return 0;
else
return (strcmp(((struct Members*) s1)->fname, \
((struct Members*) s2)->fname) < 0 ? 1 : 0);
}
void sort(void* arr,long ele_size,long start,long end,compare_func compare)
{
// Generic Recursive Quick Sort Algorithm
if (start < end)
{
/* Partitioning index */
void* x = arr+end*ele_size;
long i = (start - 1);
void* tmp=malloc(ele_size);
for (long j = start; j <= end - 1; j++)
{
if ((*compare)(arr+j*ele_size,x))
{
i++;
// Swap is done by copying memory areas
memcpy(tmp,arr+i*ele_size,ele_size);
memcpy(arr+i*ele_size,arr+j*ele_size,ele_size);
memcpy(arr+j*ele_size,tmp,ele_size);
}
}
memcpy(tmp,arr+(i+1)*ele_size,ele_size);
memcpy(arr+(i+1)*ele_size,arr+end*ele_size,ele_size);
memcpy(arr+end*ele_size,tmp,ele_size);
i= (i + 1);
sort(arr,ele_size,start, i - 1,compare);
sort(arr,ele_size,i + 1, end,compare);
}
}
int main()
{
FILE* fp;
int bio_max = 3;
struct Members bio[bio_max]; // Define bio to be large enough.
/* Open FILE and setup bio matrix */
/* For testing */
bio[0].id = 0;
strcpy(bio[0].fname, "");
strcpy(bio[0].lname, "Apple");
bio[0].age = 0;
bio[1].id = 1;
strcpy(bio[1].fname, "");
strcpy(bio[1].lname, "Cat");
bio[1].age = 1;
bio[2].id = 2;
strcpy(bio[2].fname, "");
strcpy(bio[2].lname, "Bat");
bio[2].age = 2;
/* Sort the structure */
sort(bio, sizeof(struct Members), 0, bio_max - 1, struct_cmp);
/* Print the sorted structure */
for (int i = 0; i < bio_max; i++) {
printf("%d %s %s %d\n", bio[i].id, bio[i].fname, \
bio[i].lname, bio[i].age);
}
}
Output
0 Apple 0
2 Bat 2
1 Cat 1
If the strings are not sorting in the way you want, you can redefine the struct_cmp function. Code is self explanatory, the base logic in the code is pass an array and swap elements using memcpy functions. You cant use simple assignment operator if you want to be generic, so that is why the element size is explicitly passed.
Edit
The code was not handling the condition, if lname are same. I missed it thanks for #4386427 for pointing this out.
I think you should define bio to be an array. And google sort algorithms please. Also recommend you google how to use libc function qsort.

Key Value Pair in C Language

I am new to C programming and I am trying to create a key value structure as in Perl Programming. I saw one solution like :-
struct key_value
{
int key;
char* value;
};
struct key_value kv;
kv.key = 1;
kv.value = "foo";
But I don't know how to access these values from this structure. Can someone enlight on this ?
Here is an example:
#include <stdio.h>
#include <stdlib.h>
struct key_value
{
int key;
char* value;
};
int main(void)
{
int number_of_keys = 2;
struct key_value *kv = malloc(sizeof(struct key_value) * number_of_keys);
if (kv == NULL) {
perror("Malloc");
exit(EXIT_FAILURE);
}
kv[0].key = 8;
kv[0].value = "Test 8 key!";
kv[1].key = 6;
kv[1].value = "Test 6 key!";
printf("Key = %d\nKey value = %s\n", kv[0].key, kv[0].value);
printf("Key = %d\nKey value = %s\n", kv[1].key, kv[1].value);
free(kv);
return 0;
}
What you are missing is a collection. Most languages have a data type called a dictionary or a map or an associative array or some variation thereof. C does not have a data structure of this type; in fact, the only collection type you have built in to C is the array. So, if you want something where you can supply a key and get the value, you have to roll your own or find one on the Internet. The latter is probably preferable because you are likely to make mistakes and produce a slow data structure if you roll your own (especially if you are a beginner).
To give you a flavour of what you'll end up with, here's a simple example:
You'll need something to represent the collection; call it a ListMap for now:
struct ListMap;
The above is called an incomplete type. For now, we are not concerned with what's in it. You can't do anything with it except pass pointers to instances around.
You need a function to insert items into your collection. Its prototype would look something like this:
bool listMapInsert(struct ListMap* collection, int key, const char* value);
// Returns true if insert is successful, false if the map is full in some way.
And you need a function to retrieve the value for any one key.
const char* listMapValueForKey(struct ListMap* collection, int key);
You also need a function to initialise the collection:
struct ListMap* newListMap();
and to throw it away:
void freeListMap(struct ListMap* listMap);
The hard bit is implementing how those functions do what they do. Anyway, here's how you would use them:
struct ListMap* myMap = newListMap();
listMapInsert(myMap, 1, "foo");
listMapInsert(myMap, 1729, "taxi");
listMapInsert(myMap, 28, "perfect");
char* value = listMapValueForKey(myMap, 28); // perfect
freeListMap(myMap);
Here's a simple implementation. This is just for illustration because I haven't tested it and searching for entries increases linearly with the number of entries (you can do much better than that with hash tables and other structures).
enum
{
listMapCapacity = 20
};
struct ListMap
{
struct key_value kvPairs[listMapCapacity];
size_t count;
};
struct ListMap* newListMap()
{
struct ListMap* ret = calloc(1, sizeof *ret);
ret->count = 0; // not strictly necessary because of calloc
return ret;
}
bool listMapInsert(struct ListMap* collection, int key, const char* value)
{
if (collection->count == listMapCapacity)
{
return false;
}
collection->kvPairs[count].key = key;
collection->kvPairs[count].value = strdup(value);
count++;
return true;
}
const char* listMapValueForKey(struct ListMap* collection, int key)
{
const char* ret = NULL;
for (size_t i = 0 ; i < collection->count && ret == NULL ; ++i)
{
if (collection->kvPairs[i].key == key)
{
ret = kvPairs[i].value;
}
}
return ret;
}
void freeListMap(struct ListMap* listMap)
{
if (listMap == NULL)
{
return;
}
for (size_t i = 0 ; i < listMap->count ; ++i)
{
free(listMap->kvPair[i].value);
}
free(listMap);
}
typedef struct key_value
{
int key;
char* value;
}List;
struct key_value k1;
struct key_value k2;
struct key_value k3;
k1.key = 1;
k1.value = "foo";
k2.key = 2;
k2.value = "sec";
k3.key = 3;
k3.value = "third";
You will need to create N times the struct and give them values the way you did the first one. Or create array with N structs and iterate assign it values with a loop.
Array:
List arr[29];
int i;
for(i = 0;i<=28;i++){
arr[i].key = i;
arr[i].value = "W/e it needs to be";
}
The functionality you are looking for needs your own implementation in C; e.g. an array of your struct-type.
Here is an example of how to read the value for a key, without knowing anything about at which array-index the key will be found.
I have the keys numbered backward in order to illustrate that.
Note that more sophisticated API definitions are needed for special cases such as non-existing key; I just blindly return the last entry to keep things easy here.
#include <stdio.h>
#define MAPSIZE 30
struct key_value
{
int key;
char* value;
};
struct key_value kvmap[MAPSIZE];
void initmap(void)
{
int i;
for(i=0; i<MAPSIZE; i++)
{
kvmap[i].key=MAPSIZE-i-1;
kvmap[i].value="unset";
}
kvmap[0].value="zero";
kvmap[1].value="one";
kvmap[2].value="two";
kvmap[3].value="three";
kvmap[4].value="four";
kvmap[5].value="five";
kvmap[6].value="six";
kvmap[7].value="seven";
kvmap[8].value="eight";
kvmap[24].value="find this"; // it has the key "5"
}
char* readmap(int key)
{
int i=0;
while ((i<MAPSIZE-1) && (kvmap[i].key!=key))
{ printf("Not in %d\n", i);
++i;}
// will return last entry if key is not present
return kvmap[i].value;
}
int main(void)
{
initmap();
printf("%s\n", readmap(5));
return 0;
}
"I have to store 30 key/value pair"
Create an array of struct e.g., key_value.
struct key_value
{
int key;
char* value;
};
struct key_value kv[30];
kv[0].key = 1;
kv[0].value = "foo";
printf("%s", kv[0].value);
You can loop through to assign values to keys and values.
Access to whatever is in kv is simple.
int i = kv[0].key`;// copy value of k[0].key to i
char *v = kv[0].value; // copy value of k[0].value to v;
Your code already have the method to acess the values.
kv.key = 1
kv.value = "foo"
To get the values assigned is simple
kv.key
kv.value
It is a simple struct, if you wanna something like python dict you will need to implement a hash struct which will be more complicated.

Does C support key-value pair data structure? [duplicate]

I am implementing a way to transfer a set of data to a programmable dongle. The dongle is based on a smart card technology and can execute an arbitrary code inside. The input and output data is passed as a binary blocks that can be accessed via input and output pointers.
I would like to use an associative array to simplify the data processing code. Everything should work this way:
First the host application:
// Host application in C++
in_data["method"] = "calc_r";
in_data["id"] = 12;
in_data["loc_a"] = 56.19;
in_data["loc_l"] = 44.02;
processor->send(in_data);
Next the code inside the dongle:
// Some dongle function in C
char* method_name = assoc_get_string(in_data, "method");
int id = assoc_get_int(in_data, "id");
float loc_a = assoc_get_float(in_data, "loc_a");
float loc_l = assoc_get_float(in_data, "loc_l");
So my question is about the dongle part functionality. Is there C code or library to implement such an associative array behavior like the above?
Glib's hash table. implements a map interface or (associative array).
And it's most likely the most used hash table implementation for C.
GHashTable *table=g_hash_table_new(g_str_hash, g_str_equal);
/* put */
g_hash_table_insert(table,"SOME_KEY","SOME_VALUE");
/* get */
gchar *value = (gchar *) g_hash_table_lookup(table,"SOME_KEY");
My suspicion is that you would have to write your own. If I understand the architecture you are describing, then you will need to send the entire chunk of data in a single piece. If so, then most libraries will not work for that because they will most likely be allocating multiple pieces of memory, which would require multiple transfers (and an inside understanding of the structure). It would be similar to trying to use a library hash function and then sending its contents over the network on a socket just by passing the root pointer to the send function.
It would be possible to write some utilities of your own that manage a very simple associative array (or hash) in a single block of memory. If the amount of data is small, it could use a simple linear search for the entries and would be a fairly compact bit of code.
Try uthash, a header library implementing a hash table in C. It's small and fairly easy to use.
This is an old thread, but I thought this might still be useful for anyone out there looking for an implementation. It doesn't take too much code; I did mine in ~100 lines of without any extra library. I called it a dictionary since it parallels (sort of) the python datatype. Here is my code:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct hollow_list hollow_list;
struct hollow_list{
unsigned int size;
void *value;
bool *written;
hollow_list *children;
};
//Creates a hollow list and allocates all of the needed memory
hollow_list hollow_list_create(unsigned int size){
hollow_list output;
output = (hollow_list) {.size = size, .value = (void *) 0, .written = calloc(size, sizeof(bool)), .children = calloc(size, sizeof(hollow_list))};
return output;
}
//Frees all memory of associated with a hollow list and its children
void hollow_list_free(hollow_list *l, bool free_values){
int i;
for(i = 0; i < l->size; i++){
hollow_list_free(l->children + i, free_values);
}
if(free_values){
free(l->value);
}
free(l);
}
//Reads from the hollow list and returns a pointer to the item's data
void *hollow_list_read(hollow_list *l, unsigned int index){
if(index == 0){
return l->value;
}
unsigned int bit_checker;
bit_checker = 1<<(l->size - 1);
int i;
for(i = 0; i < l->size; i++){
if(bit_checker & index){
if(l->written[i] == true){
return hollow_list_read(l->children + i, bit_checker ^ index);
} else {
return (void *) 0;
}
}
bit_checker >>= 1;
}
}
//Writes to the hollow list, allocating memory only as it needs
void hollow_list_write(hollow_list *l, unsigned int index, void *value){
if(index == 0){
l->value = value;
} else {
unsigned int bit_checker;
bit_checker = 1<<(l->size - 1);
int i;
for(i = 0; i < l->size; i++){
if(bit_checker & index){
if(!l->written[i]){
l->children[i] = hollow_list_create(l->size - i - 1);
l->written[i] = true;
}
hollow_list_write(l->children + i, bit_checker ^ index, value);
break;
}
bit_checker >>= 1;
}
}
}
typedef struct dictionary dictionary;
struct dictionary{
void *value;
hollow_list *child;
};
dictionary dictionary_create(){
dictionary output;
output.child = malloc(sizeof(hollow_list));
*output.child = hollow_list_create(8);
output.value = (void *) 0;
return output;
}
void dictionary_write(dictionary *dict, char *index, unsigned int strlen, void *value){
void *hollow_list_value;
dictionary *new_dict;
int i;
for(i = 0; i < strlen; i++){
hollow_list_value = hollow_list_read(dict->child, (int) index[i]);
if(hollow_list_value == (void *) 0){
new_dict = malloc(sizeof(dictionary));
*new_dict = dictionary_create();
hollow_list_write(dict->child, (int) index[i], new_dict);
dict = new_dict;
} else {
dict = (dictionary *) hollow_list_value;
}
}
dict->value = value;
}
void *dictionary_read(dictionary *dict, char *index, unsigned int strlen){
void *hollow_list_value;
dictionary *new_dict;
int i;
for(i = 0; i < strlen; i++){
hollow_list_value = hollow_list_read(dict->child, (int) index[i]);
if(hollow_list_value == (void *) 0){
return hollow_list_value;
} else {
dict = (dictionary *) hollow_list_value;
}
}
return dict->value;
}
int main(){
char index0[] = "hello, this is a test";
char index1[] = "hello, this is also a test";
char index2[] = "hello world";
char index3[] = "hi there!";
char index4[] = "this is something";
char index5[] = "hi there";
int item0 = 0;
int item1 = 1;
int item2 = 2;
int item3 = 3;
int item4 = 4;
dictionary d;
d = dictionary_create();
dictionary_write(&d, index0, 21, &item0);
dictionary_write(&d, index1, 26, &item1);
dictionary_write(&d, index2, 11, &item2);
dictionary_write(&d, index3, 13, &item3);
dictionary_write(&d, index4, 17, &item4);
printf("%d\n", *((int *) dictionary_read(&d, index0, 21)));
printf("%d\n", *((int *) dictionary_read(&d, index1, 26)));
printf("%d\n", *((int *) dictionary_read(&d, index2, 11)));
printf("%d\n", *((int *) dictionary_read(&d, index3, 13)));
printf("%d\n", *((int *) dictionary_read(&d, index4, 17)));
printf("%d\n", ((int) dictionary_read(&d, index5, 8)));
}
Unfortunately you can't replicate the list[x] syntax, but this is the best alternative I have come up with.
Yes, but it will not work in the way you have specified. It will instead use a struct to store the data and functions that operate on that struct, giving you the result you want. See A Simple Associative Array Library In C. Example of use:
struct map_t *test;
test=map_create();
map_set(test,"One","Won");
map_set(test,"Two","Too");
map_set(test,"Four","Fore");
GLib's Hash Tables and Balanced Binary Trees might be what you're after.
Mark Wilkins gave you the right answer. If you want to send the data as a single chunk, you need to understand how C++ maps are represented in your architecture and write the access functions.
Anyway, if you decide to recreate the map on the dongle, I've written a small C library where you could write thinks like:
tbl_t in_data=NULL;
tblSetSS(in_data,"method","calc_r");
tblSetSN(in_data,"id",12);
tblSetSF(in_data,"loc_a",56.19);
tblSetSF(in_data,"loc_l",44.02);
and then:
char *method_name = tblGetP(in_data, "method");
int id = tblGetN(in_data, "id");
float loc_a = tblGetF(in_data, "loc_a");
float loc_l = tblGetF(in_data, "loc_l");
The hashtable is a variation of the Hopscotch hash, which is rather good on average, and you can have any mix of type for keys and data (i.e. you can use an entire table as a key).
The focus for that functions was on easing programming rather than pure speed and the code is not thoroughly tested but if you like the idea and want to expand on it, you can have a look at the code on googlecode.
(There are other things like variable length strings and a fast sttring pattern matching function but those might not be of interest in this case).

C lookup string by value

I need to translate a value into a human readable string. Normally for things I define I would use values that start at zero and create a simple array of strings with the values as the index.
static const char *foo[] = { "foo", "bar", "etc" };
if (val < 3) printf("%s\n", foo[val]);
For this circumstance I have values that do not start at zero and there are some gaps between them. Is there a good way to do this without having to manually code in a bunch of empty strings for the indexes without a matching value/string pair?
static const char *foo[] = { "", "", "", "foo", "", "bar", "", "", "etc" };
As of C99, you can use designated initialisers:
static const char *foo[] = {
[3] = "foo",
[5] = "bar",
[8] = "etc"
};
This is equivalent to the array definition you posted and will generate an array with 9 entries. There is a similar syntax for the initialisation of structs:
struct Person joe = {
.name = "Joe", .age = 24, .favcolor = "mauve"
};
Note that this is a C feature only and will not work in C++.
If there aren't too many gaps, you can encode each contiguous sequence as a separate array, and then do a little bounds-checking to find the appropriate one to use. Here's a quick-and-dirty example:
#include <stdio.h>
#include <stdlib.h>
static const int array_first_indices[] = {3, 15, 28, 32};
static const char * array0[] = {"foo"};
static const char * array1[] = {"bar", "baz"};
static const char * array2[] = {"bloop", "blorp", "blat"};
static const char * array3[] = {"glop", "slop", "bop"};
#define check_array(whichArray, idx) { \
unsigned int relIdx = idx - array_first_indices[whichArray]; \
if (relIdx < (sizeof(array##whichArray)/sizeof(const char *))) \
return array##whichArray[relIdx]; \
}
const char * LookupWord(int idx)
{
check_array(0, idx);
check_array(1, idx);
check_array(2, idx);
check_array(3, idx);
return NULL;
}
int main(int args, char ** argv)
{
for (int i=0; i<50; i++) printf(" LookupWord(%i) = %s\n", i, LookupWord(i));
return 0;
}
For a fully general lookup mechanism, you'd probably need to use a data structure like a hash table or a tree; C implementations of those data structures are available, although if you have the option of using C++ it would be easier to use those data structures in that language, as they are provided by the standard library.
Create a sorted array that maps IDs to strings and use the bsearch() function to look up the string:
#include <stdio.h>
#include <stdlib.h>
struct id_msg_map {
int id;
char const* str;
};
int comp_id_string( const void* key, const void* element)
{
int key_id = ((struct id_msg_map*) key)->id;
int element_id = ((struct id_msg_map*) element)->id;
if (key_id < element_id) return -1;
if (key_id > element_id) return 1;
return 0;
}
static struct id_msg_map msg_map[] = {
{3, "message 3"} ,
{12, "message 12"},
{100, "message 100"},
{32000, "message 32000"},
};
#define ELEMENTS_OF(x) (sizeof(x) / sizeof((x)[0]))
char const* get_msg(int x)
{
struct id_msg_map key = {x};
struct id_msg_map* msg = bsearch(&key, msg_map, ELEMENTS_OF(msg_map), sizeof(msg_map[0]), comp_id_string);
if (!msg) return "invalid msg id";
return msg->str;
}
void test_msg(int x)
{
printf("The message for ID %d: \"%s\"\n", x, get_msg(x));
}
int main(void)
{
test_msg(0);
test_msg(3);
test_msg(100);
test_msg(-12);
return 0;
}
You can use designated initialisers, as described in M. Oehm's post, but that silently introduces the same gaps you were referring to earlier (with implicit 0 values). That option is most suitable when you know 0 will never be an actual selection, when the table doesn't change dynamically (particularly in size) and when the size of the table is small.
If the table is particularly large, but items are never added or removed from it you can use qsort and bsearch on a key/value-pair style structure. For example:
struct foo_pair {
int key;
char *value;
};
int foo_pair_compare(void *x, void *y) {
struct foo_pair *a = x, *b = y;
return (a->key > b->key) - (a->key < b->key);
}
int main(void) {
struct foo_pair foo[] = { { .key = 3, .value = "foo" },
{ .key = 5, .value = "bar" },
{ .key = 6, .value = "etc" } };
/* qsort needs to be done at the start of the program,
and again each time foo changes */
qsort(foo, sizeof foo / sizeof *foo, sizeof *foo, foo_pair_compare);
/* bsearch is used to retrieve an item from the sorted array */
struct foo_pair *selection = bsearch(&(struct foo_pair) { .key = 5 },
foo, sizeof foo / sizeof *foo,
sizeof *foo, foo_pair_compare);
}
When items are routinely added or removed from the collection, it will make more sense to select a hashtable or some kind of ordered map. If you can't be bothered writing and testing your own of these collections, I imagine there are plenty of tried & tested libraries on the internet that you could check out.

Resources