Associative arrays/Creation/C: Difference between revisions

→‎POSIX hsearch(): Redesign and rewrite the code. The old code was a mess. The new code has fetch(), store() and delete() functions.
(Split from Associative arrays/Creation and Associative arrays/Iteration, to make room for expansion. This text has multiple authors.)
 
(→‎POSIX hsearch(): Redesign and rewrite the code. The old code was a mess. The new code has fetch(), store() and delete() functions.)
Line 82:
}</lang>
 
===POSIX hcreatehsearch()===
POSIX defines hcreate(), hdestroy() and hsearch() to manage a hash table. If you have a [[Unix]] system or clone, then your libc probably has these functions, so there is no extra library to install.
 
These functions have some major limitations:
 
* You can only have one hash table, in the entire program!
** With GNU libc, you can call hcreate_r() to have more than one hash table.
* There is no way to delete an entry from the table!
* There is no way to iterate all keys in the table!
* Every key must be a NUL-terminated string.
* Every value is a void *, needs a cast to the correct type.
* The table might become full, and refuse to accept more entries. If you use hcreate(50) then the table might hold at most 50 entries.
* hdestroy() is almost impossible to use. Many programs keep the table and never call hdestroy().
** With BSD libc, hdestroy() will call free() with each key in the table.
** Else the program leaks memory, because there is no way to iterate the keys to free them.
 
The Linux manual page [http://manwww.cxkernel.org/hcreatedoc/man-pages/online/pages/man3/hsearch.3.html hcreatehsearch(3)] hascontains a short example.
 
====To create the hash table====
The next example shows how to use hdestroy(), how to use ''intptr_t'' as a value, how to use ''double'' as a key, and how to pretend to delete an entry from the table! POSIX hash table is awful, and the code is too long, but the program does run.
The hash table has a fixed capacity. <code>hcreate(50)</code> creates a table for 50 entries. The library might increase 50 to a convenient value (perhaps 64 being a power of 2, or 67 being a prime number). The hash table might have only 64 or 67 slots. Each slot might hold one entry, or one list of entries. Access to the hash table is near [[O|O(1)]], but slows to [[O|O(n)]] as the slots become full.
 
The hash table is an associative array of key-value pairs. Each key must be a NUL-terminated string. Each value is a void *.
{{libheader|POSIX}}
 
====To fetch or store====
<lang c>#include <inttypes.h> /* PRIxPTR */
To use the hash table as an associative array, this program defines fetch() and store().
#include <search.h> /* hcreate(), hsearch(), hdestroy() */
 
#include <stdint.h> /* intptr_t */
{{libheader|POSIX}}
#include <stdio.h> /* perror(), printf(), puts(), snprintf() */
<lang c>#include <stdlibinttypes.h> /* calloc(), exit()intptr_t, free()PRIxPTR */
#include <stringsearch.h> /* strduphcreate(), hsearch() */
#include <stdio.h> /* perror(), printf() */
#include <stdlib.h> /* exit() */
 
void
Line 115 ⟶ 113:
exit(1);
}
 
 
/*
* Must hcreate() the hash table before calling fetch() or store().
* color_example:
* Maps color strings to integer values, like "red" => 0xff0000.
*
* Because thep->data valuesis (ENTRYa data) are pointerspointer, Ifetch() useand castsstore() to andcast frombetween
* void * and intptr_t.
* intptr_t to store integers instead of pointers.
*/
 
void
/* Fetch value from the hash table. */
color_example(void)
int
fetch(const char *key, intptr_t *value)
{
ENTRY e = {key: (char *)key}, *epp;
p = hsearch(e, FIND);
int i;
if (p) {
 
*value = (intptr_t)p->data;
puts("color_example:");
return 1;
 
} else
/* Create an empty table. */
return 0;
if (hcreate(50) == 0)
}
fail("hcreate");
 
/* Add keys => values to table. */
{
char *keys[] = { "red", "green", "blue" };
intptr_t values[] = { 0xff0000, 0x00ff00, 0x0000ff };
 
for (i = 0; i < 3; i++) {
/* Need strdup() when using ENTER. */
if ((e.key = strdup(keys[i])) == NULL)
fail("strdup");
e.data = (void *)values[i];
 
/* Insert e into table. */
if (hsearch(e, ENTER) == NULL)
fail("hsearch");
}
}
 
/* Check if keys exist. */
{
char *keys[] = { "blue", "green", "orange", "red" };
 
for (i = 0; i < 4; i++) {
/* Not using ENTER, so no strdup(). */
e.key = keys[i];
ep = hsearch(e, FIND);
if (ep) {
printf("\t'%s' has value %06" PRIxPTR "\n",
ep->key, (intptr_t)ep->data);
} else
printf("\t'%s' is not in table\n", e.key);
}
}
 
/* Store key-value pair into the hash table. */
void
store(const char *key, intptr_t value)
{
/*
* hsearch() may insert a new entry or find an existing entry
* Destroy table. On my machine, hdestroy() will free() all the
* keys inwith the tablesame key. Thishsearch() isignores whye.data Iif hadit tofinds strdup() my keys.an
* existing entry. We must call hsearch(), then set p->data.
*/
ENTRY e = {key: (char *)key}, *p;
hdestroy();
p = hsearch(e, ENTER);
if (p == NULL)
fail("hsearch");
p->data = (void *)value;
}
 
 
/*
* Use the hash table to map color strings to integer values,
* number_example:
* like "red" => 0xff0000.
* Maps numbers to strings or numbers, like 2 => 2.71828 or 4 => "four".
*
* First I need a VALUE that can either be a string or a number.
*/
int
typedef struct value {
main()
int v_type;
#define T_NUM 0x1 /* use v_num */
#define T_STR 0x2 /* use v_str */
#define T_DEL 0x4 /* pretend to delete from table */
 
union {
double u_num;
char *u_str;
} v_union;
#define v_num v_union.u_num
#define v_str v_union.u_str
 
struct value *next; /* link all VALUEs in a list */
} VALUE;
 
void
number_example(void)
{
static const char *const keys[] =
ENTRY e, *ep;
{"red", "orange", "yellow", "green", "blue", "white", "black"};
VALUE *all = NULL, *vp;
intptr_t value;
int i, s;
charint buf[16]i;
 
puts("number_example:");
 
/* First, create an empty table that can hold 50 entries. */
if (hcreate(50) == 0)
fail("hcreate");
 
/*
/* Add numeric values. */
* Some colors from CSS2,
{
* http://www.w3.org/TR/CSS2/syndata.html#value-def-color
double keys[] = { 2, 3, 4, 5.6 };
*/
double values[] = { 2.71828, 3.14159, 4.47214, 7.8 };
store("red", 0xff0000);
store("orange", 0x123456); /* Insert wrong value! */
store("green", 0x008000);
store("blue", 0x0000ff);
store("white", 0xffffff);
store("black", 0x000000);
store("orange", 0xffa500); /* Replace with correct value. */
 
for (i = 0; i < 4sizeof(keys) / sizeof(keys[0]); i++) {
if (fetch(keys[i], &value))
/* Must convert keys to strings. */
printf("%s has value %06" PRIxPTR "\n",
snprintf(buf, sizeof buf, "%.6g", keys[i]);
keys[i], value);
if ((e.key = strdup(buf)) == NULL)
else
fail("strdup");
printf("%s is not in table\n", keys[i]);
 
/* Allocate a new VALUE. */
if ((vp = calloc(1, sizeof vp[0])) == NULL)
fail("calloc");
vp->v_type = T_NUM;
vp->v_num = values[i];
e.data = vp;
 
/* Remember to free it. */
vp->next = all;
all = vp;
 
if (hsearch(e, ENTER) == NULL)
fail("hsearch");
}
}
 
/*
* AddDO stringNOT valuesCALL hdestroy().
*
* With BSD libc, hdestroy() would call free() with each key in
* For this example, all of my values will be static string
* constantstable. ThisOur removeskeys theare needstatic tostrings, so free(vp->v_str) would crash.
* when I replace or delete a value.
*/
return 0;
{
}</lang>
double keys[] = { 4, 8, 10 };
char *values[] = { "four", "eight", "ten" };
 
<pre>red has value ff0000
for (i = 0; i < 3; i++) {
orange has value ffa500
/* Must convert keys to strings. */
yellow is not in table
snprintf(buf, sizeof buf, "%.6g", keys[i]);
green has value 008000
blue has value 0000ff
white has value ffffff
black has value 000000</pre>
 
====To delete or iterate====
/*
{{libheader|POSIX}}
* This shows how to add or replace a value
<lang c>#include <inttypes.h>
* in the table (so I can change an entry
#include <search.h>
* from 4 => 4.47214 to 4 => "four").
#include <stdio.h>
*/
#include <stdlib.h>
e.key = buf;
#include <string.h>
ep = hsearch(e, FIND);
if (ep) {
/* Already have key. Change value. */
vp = (VALUE *)ep->data;
vp->v_type = T_STR;
vp->v_str = values[i];
} else {
/* Need to add key. */
if ((e.key = strdup(buf)) == NULL)
fail("strdup");
 
void
if ((vp = calloc(1, sizeof vp[0])) == NULL)
fail("calloc"char *message);
{
vp->v_type = T_STR;
perror(message);
vp->v_str = values[i];
exit(1);
e.data = vp;
}
 
/* RememberA tokey-value free it.pair */
struct pair {
vp->next = all;
/* prev is q_forw, so insque(d, &head) sets head.prev = d */
all = vp;
struct pair *prev; /* q_forw */
struct pair *next; /* q_back */
if (hsearch(e, ENTER) == NULL)
int32_t key;
fail("hsearch");
int32_t value;
}
int deleted;
};
 
/*
* A circular queue of all pairs in the hash table.
* head.next begins a list of all pairs in order of insertion.
*/
struct pair head = {&head, &head};
 
/* Fetch value from the hash table. */
int
fetch(int32_t key, int32_t *value)
{
ENTRY e, *p;
char buf[16];
 
snprintf(buf, sizeof buf, "%"PRId32, key);
e.key = buf;
p = hsearch(e, FIND);
if (p) {
struct pair *d = p->data;
if (d->deleted)
return 0;
else {
*value = d->value;
return 1;
}
} else
return 0;
}
 
/* DeleteStore key-value 8pair into the hash table. */
void
snprintf(buf, sizeof buf, "%.6g", (double)8);
store(int32_t key, int32_t value)
{
ENTRY e, *p;
char buf[16];
 
snprintf(buf, sizeof buf, "%"PRId32, key);
e.key = buf;
epp = hsearch(e, FINDENTER);
if (ep)p {== NULL)
fail("hsearch");
vp = (VALUE *)ep->data;
vp->v_type = T_DEL;
}
 
if (p->key == buf) {
/* Check if keys exist. */
/* Allocate and initialize a new pair. */
{
struct pair *d = malloc(sizeof *d);
double keys[] = { 2, 3, 4, 5.6, 7, 8, 10 };
if (d == NULL)
fail("malloc");
d->key = key;
d->value = value;
d->deleted = 0;
 
for/* (iAllocate =space 0;for ikey, <apart 7;from i++)buf. {*/
p->key = strdup(buf);
snprintf(buf, sizeof buf, "%.6g", keys[i]);
e.if (p->key == buf;NULL)
ep = hsearchfail(e, FIND"strdup");
 
/*
if (ep == NULL ||
* Insert the new pair into the hash table's entry, and
(vp = (VALUE *)ep->data)->v_type & T_DEL) {
* into the circular queue.
printf("\t%s is not in the table\n",
buf);*/
p->data = d;
} else if (vp->v_type & T_NUM) {
insque(d, &head);
printf("\t%s has value %g\n",
} else {
buf, vp->v_num);
/* Replace the value. */
} else if (vp->v_type & T_STR) {
struct pair *d = p->data;
printf("\t%s has value '%s'\n",
d->value = value;
buf, vp->v_str);
}if else(d->deleted) {
/* Restore a deleted key. */
printf("\t%s has invalid value\n",
insque(d, buf&head);
d->deleted = 0;
}
}
}
}
 
/* DestroyDelete tablekey andfrom keysthe hash table. */
int
hdestroy();
delete(int32_t key)
{
ENTRY e, *p;
char buf[16];
 
snprintf(buf, sizeof buf, "%"PRId32, key);
/* Free all values. */
e.key = buf;
while (all != NULL) {
p = hsearch(e, FIND);
vp = all->next;
if (p) {
free(all);
allstruct pair *d = vpp->data;
if (d->deleted)
}
return 0;
else {
remque(d);
return d->deleted = 1;
}
} else
return 0;
}
 
Line 336 ⟶ 326:
main()
{
struct pair *p;
color_example();
int32_t value;
number_example();
int i;
 
if (hcreate(50) == 0)
fail("hcreate");
 
store(1, mrand48());
store(2, mrand48());
store(3, mrand48());
for (i = 0; i < 3; i++)
store(mrand48(), mrand48());
store(4, mrand48());
delete(1) || puts("1 is not deleted");
delete(2) || puts("2 is not deleted");
delete(5) || puts("5 is not deleted");
store(1, mrand48());
store(3, mrand48());
fetch(2, &value) ? puts("2 is in table") : puts("2 is missing");
fetch(4, &value) ? puts("4 is in table") : puts("4 is missing");
fetch(6, &value) ? puts("6 is in table") : puts("6 is missing");
 
puts("Iterating the hash table:");
for (p = head.next; p != &head; p = p->next) {
printf(" %"PRId32" => %"PRId32"\n", p->key, p->value);
}
 
return 0;
}</lang>
 
<pre>5 is not deleted
Output:
2 is missing
4 is in table
6 is missing
Iterating the hash table:
3 => 252797108
1368775034 => 1918061247
66927828 => -487786166
684483038 => -1786318902
4 => 1648047133
1 => -1327126111</pre>
 
====hdestroy()====
<pre>color_example:
hdestroy() is almost impossible to use. With BSD libc, hdestroy() will call free() with each key in the table. With other systems, hdestroy() might leak memory, because the program has no way to iterate the keys to free them. Most programs keep the hash table and never call hdestroy().
'blue' has value 0000ff
'green' has value 00ff00
'orange' is not in table
'red' has value ff0000
number_example:
2 has value 2.71828
3 has value 3.14159
4 has value 'four'
5.6 has value 7.8
7 is not in the table
8 is not in the table
10 has value 'ten'</pre>
 
===BSD dbopen()===
Anonymous user