2 * assocfunc - association table routines
4 * Copyright (C) 1999-2007 David I. Bell
6 * Calc is open software; you can redistribute it and/or modify it under
7 * the terms of the version 2.1 of the GNU Lesser General Public License
8 * as published by the Free Software Foundation.
10 * Calc is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 * Public License for more details.
15 * A copy of version 2.1 of the GNU Lesser General Public License is
16 * distributed with calc under the filename COPYING-LGPL. You should have
17 * received a copy with calc; if not, write to Free Software Foundation, Inc.
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * @(#) $Revision: 30.2 $
21 * @(#) $Id: assocfunc.c,v 30.2 2013/08/11 08:41:38 chongo Exp $
22 * @(#) $Source: /usr/local/src/bin/calc/RCS/assocfunc.c,v $
24 * Under source code control: 1993/07/20 23:04:27
25 * File existed as early as: 1993
27 * Share and enjoy! :-) http://www.isthe.com/chongo/tech/comp/calc/
31 * Association table routines.
32 * An association table is a type of value which can be "indexed" by
33 * one or more arbitrary values. Each element in the table is thus an
34 * association between a particular set of index values and a result value.
35 * The elements in an association table are stored in a hash table for
43 #define MINHASHSIZE 31 /* minimum size of hash tables */
44 #define GROWHASHSIZE 50 /* approximate growth for hash tables */
45 #define CHAINLENGTH 10 /* desired number of elements on a hash chain */
46 #define ELEMSIZE(n) (sizeof(ASSOCELEM) + (sizeof(VALUE) * ((n) - 1)))
49 S_FUNC ASSOCELEM
*elemindex(ASSOC
*ap
, long index
);
50 S_FUNC BOOL
compareindices(VALUE
*v1
, VALUE
*v2
, long dim
);
51 S_FUNC
void resize(ASSOC
*ap
, long newsize
);
52 S_FUNC
void assoc_elemfree(ASSOCELEM
*ep
);
56 * Return the address of the value specified by normal indexing of
57 * an association. The create flag is TRUE if a value is going to be
58 * assigned into the specified indexing location. If create is FALSE and
59 * the index value doesn't exist, a pointer to a NULL value is returned.
62 * ap association to index into
63 * create whether to create the index value
64 * dim dimension of the indexing
65 * indices table of values being indexed by
68 associndex(ASSOC
*ap
, BOOL create
, long dim
, VALUE
*indices
)
77 math_error("Negative dimension for indexing association");
82 * Calculate the hash value to use for this set of indices
83 * so that we can first select the correct hash chain, and
84 * also so we can quickly compare each element for a match.
87 for (i
= 0; i
< dim
; i
++)
88 hash
= hashvalue(&indices
[i
], hash
);
91 * Search the correct hash chain for the specified set of indices.
92 * If found, return the address of the found element's value.
94 listhead
= &ap
->a_table
[hash
% ap
->a_size
];
95 for (ep
= *listhead
; ep
; ep
= ep
->e_next
) {
96 if ((ep
->e_hash
!= hash
) || (ep
->e_dim
!= dim
))
98 if (compareindices(ep
->e_indices
, indices
, dim
))
103 * The set of indices was not found.
104 * Either return a pointer to a NULL value for a read reference,
105 * or allocate a new element in the list for a write reference.
109 val
.v_subtype
= V_NOSUBTYPE
;
113 ep
= (ASSOCELEM
*) malloc(ELEMSIZE(dim
));
115 math_error("Cannot allocate association element");
120 ep
->e_value
.v_type
= V_NULL
;
121 ep
->e_value
.v_subtype
= V_NOSUBTYPE
;
122 for (i
= 0; i
< dim
; i
++)
123 copyvalue(&indices
[i
], &ep
->e_indices
[i
]);
124 ep
->e_next
= *listhead
;
128 resize(ap
, ap
->a_count
/ CHAINLENGTH
);
135 * Search an association for the specified value starting at the
136 * specified index. Returns 0 and stores index if value found,
137 * otherwise returns 1.
140 assocsearch(ASSOC
*ap
, VALUE
*vp
, long i
, long j
, ZVALUE
*index
)
144 if (i
< 0 || j
> ap
->a_count
) {
145 math_error("This should not happen in assocsearch");
149 ep
= elemindex(ap
, i
);
151 math_error("This should not happen in assocsearch");
154 if (acceptvalue(&ep
->e_value
, vp
)) {
165 * Search an association backwards for the specified value starting at the
166 * specified index. Returns 0 and stores the index if the value is
167 * found; otherwise returns 1.
170 assocrsearch(ASSOC
*ap
, VALUE
*vp
, long i
, long j
, ZVALUE
*index
)
174 if (i
< 0 || j
> ap
->a_count
) {
175 math_error("This should not happen in assocsearch");
180 ep
= elemindex(ap
, j
);
182 math_error("This should not happen in assocsearch");
185 if (acceptvalue(&ep
->e_value
, vp
)) {
196 * Return the address of an element of an association indexed by the
197 * double-bracket operation.
200 * ap association to index into
201 * index index of desired element
204 elemindex(ASSOC
*ap
, long index
)
209 if ((index
< 0) || (index
> ap
->a_count
))
213 * This loop should be made more efficient by remembering
214 * previously requested locations within the association.
216 for (i
= 0; i
< ap
->a_size
; i
++) {
217 for (ep
= ap
->a_table
[i
]; ep
; ep
= ep
->e_next
) {
227 * Return the address of the value specified by double-bracket indexing
228 * of an association. Returns NULL if there is no such element.
231 * ap association to index into
232 * index index of desired element
235 assocfindex(ASSOC
*ap
, long index
)
239 ep
= elemindex(ap
, index
);
247 * Returns the list of indices for an association element with specified
248 * double-bracket index.
251 associndices(ASSOC
*ap
, long index
)
257 ep
= elemindex(ap
, index
);
261 for (i
= 0; i
< ep
->e_dim
; i
++)
262 insertlistlast(lp
, &ep
->e_indices
[i
]);
268 * Compare two associations to see if they are identical.
269 * Returns TRUE if they are different.
272 assoccmp(ASSOC
*ap1
, ASSOC
*ap2
)
284 if (ap1
->a_count
!= ap2
->a_count
)
287 table1
= ap1
->a_table
;
290 while (size1
-- > 0) {
291 for (ep1
= *table1
++; ep1
; ep1
= ep1
->e_next
) {
294 for (ep2
= ap2
->a_table
[hash
% size2
]; ;
298 if (ep2
->e_hash
!= hash
)
300 if (ep2
->e_dim
!= dim
)
302 if (compareindices(ep1
->e_indices
,
303 ep2
->e_indices
, dim
))
306 if (comparevalue(&ep1
->e_value
, &ep2
->e_value
))
315 * Copy an association value.
318 assoccopy(ASSOC
*oldap
)
323 ASSOCELEM
**listhead
;
327 ap
= assocalloc(oldap
->a_count
/ CHAINLENGTH
);
328 ap
->a_count
= oldap
->a_count
;
330 for (oldhi
= 0; oldhi
< oldap
->a_size
; oldhi
++) {
331 for (oldep
= oldap
->a_table
[oldhi
]; oldep
;
332 oldep
= oldep
->e_next
) {
333 ep
= (ASSOCELEM
*) malloc(ELEMSIZE(oldep
->e_dim
));
335 math_error("Cannot allocate "
336 "association element");
339 ep
->e_dim
= oldep
->e_dim
;
340 ep
->e_hash
= oldep
->e_hash
;
341 ep
->e_value
.v_type
= V_NULL
;
342 ep
->e_value
.v_subtype
= V_NOSUBTYPE
;
343 for (i
= 0; i
< ep
->e_dim
; i
++)
344 copyvalue(&oldep
->e_indices
[i
],
346 copyvalue(&oldep
->e_value
, &ep
->e_value
);
347 listhead
= &ap
->a_table
[ep
->e_hash
% ap
->a_size
];
348 ep
->e_next
= *listhead
;
357 * Resize the hash table for an association to be the specified size.
358 * This is only actually done if the growth from the previous size is
359 * enough to make this worthwhile.
362 resize(ASSOC
*ap
, long newsize
)
364 ASSOCELEM
**oldtable
;
365 ASSOCELEM
**newtable
;
371 if (newsize
< ap
->a_size
+ GROWHASHSIZE
)
374 newsize
= (long) next_prime((FULL
)newsize
);
375 newtable
= (ASSOCELEM
**) malloc(sizeof(ASSOCELEM
*) * newsize
);
376 if (newtable
== NULL
) {
377 math_error("No memory to grow association");
380 for (i
= 0; i
< newsize
; i
++)
383 oldtable
= ap
->a_table
;
385 for (i
= 0; i
< ap
->a_size
; i
++) {
388 *oldlist
= ep
->e_next
;
389 newlist
= &newtable
[ep
->e_hash
% newsize
];
390 ep
->e_next
= *newlist
;
396 ap
->a_table
= newtable
;
397 ap
->a_size
= newsize
;
398 free((char *) oldtable
);
403 * Free an association element, along with any contained values.
406 assoc_elemfree(ASSOCELEM
*ep
)
410 for (i
= 0; i
< ep
->e_dim
; i
++)
411 freevalue(&ep
->e_indices
[i
]);
412 freevalue(&ep
->e_value
);
420 * Allocate a new association value with an initial hash table.
421 * The hash table size is set at specified (but at least a minimum size).
424 assocalloc(long initsize
)
429 if (initsize
< MINHASHSIZE
)
430 initsize
= MINHASHSIZE
;
431 ap
= (ASSOC
*) malloc(sizeof(ASSOC
));
433 math_error("No memory for association");
437 ap
->a_size
= initsize
;
438 ap
->a_table
= (ASSOCELEM
**) malloc(sizeof(ASSOCELEM
*) * initsize
);
439 if (ap
->a_table
== NULL
) {
441 math_error("No memory for association");
444 for (i
= 0; i
< initsize
; i
++)
445 ap
->a_table
[i
] = NULL
;
451 * Free an association value, along with all of its elements.
456 ASSOCELEM
**listhead
;
461 listhead
= ap
->a_table
;
462 for (i
= 0; i
< ap
->a_size
; i
++) {
472 free((char *) ap
->a_table
);
479 * Print out an association along with the specified number of
480 * its elements. The elements are printed out in shortened form.
483 assocprint(ASSOC
*ap
, long max_print
)
490 if (max_print
<= 0) {
491 math_fmt("assoc (%ld element%s)", ap
->a_count
,
492 ((ap
->a_count
== 1) ? "" : "s"));
495 math_fmt("\n assoc (%ld element%s):\n", ap
->a_count
,
496 ((ap
->a_count
== 1) ? "" : "s"));
498 for (index
= 0; ((index
< max_print
) && (index
< ap
->a_count
));
500 ep
= elemindex(ap
, index
);
504 for (i
= 0; i
< ep
->e_dim
; i
++) {
507 savemode
= math_setmode(MODE_FRAC
);
508 printvalue(&ep
->e_indices
[i
],
509 (PRINT_SHORT
| PRINT_UNAMBIG
));
510 math_setmode(savemode
);
513 printvalue(&ep
->e_value
, PRINT_SHORT
| PRINT_UNAMBIG
);
516 if (max_print
< ap
->a_count
)
522 * Compare two lists of index values to see if they are identical.
523 * Returns TRUE if they are the same.
526 compareindices(VALUE
*v1
, VALUE
*v2
, long dim
)
530 for (i
= 0; i
< dim
; i
++)
531 if (v1
[i
].v_type
!= v2
[i
].v_type
)
535 if (comparevalue(v1
++, v2
++))