|
From: Robert D. <rob...@gm...> - 2026-01-24 21:54:14
|
On Sat, Jan 24, 2026 at 8:56 AM Martin Marmsoler
<mar...@gm...> wrote:
> Is there any documentation about hash tables?
There are notes about different things scattered throughout the
reference manual ... this is of course regrettable.
Before we go on, let me explain that there are two kinds of hash
tables in Maxima, one is a Maxima implementation and the other is
built into the Lisp implementation. Lisp hash tables are values,
created explicitly by make_array or implicitly when use_fast_arrays =
true, while Maxima hash tables are symbol properties.
The fundamental difference is that Maxima hash tables use ALIKE1
(i.e., same as "=") as the key test, while Lisp hash tables use EQUAL.
The two tests are pretty much the same but not exactly, which leads to
extremely subtle bugs when you have expressions which are ALIKE1 but
not EQUAL. It is not possible to define Lisp hash tables with ALIKE1
as the test; this is, frankly, a gross oversight on the part of the
language designers. Oh well! There's no use crying over water under
the bridge, to quote a phrase.
I have thought about ways to unify the two kinds of hash tables, so
that the same implementation is used for both value and symbol
property hash tables. One is to import a library which allows one to
define a hash table with ALIKE1 as the key test and use that for both
kinds. Another is to just reuse the Maxima implementation for value
hash tables; I guess that's the simpler of the two ideas. Still
thinking about it.
> How do I check if the hash table contains a value or not
> because hash[c]; returns for me just a symbol, I don't know how to check it.
At present, there is no built-in function for that, but that's just an
oversight. I've pieced together some code for it which I'll add to
src/mlisp.lisp. In the meantime you can load the code below. Here is
an example:
foo[123]: "aaa";
contains (foo, [123]);
=> true
contains (foo, [223]);
=> false
I'm a little conflicted about requiring the second argument to be a
list. It's a hassle when there's only one key (usually the case), but
multiple keys are also possible, which makes [123] ambiguous -- do you
mean the key is 123 or is it the list [123] ?? If we always pull the
key out of the list, that means [123] is no longer a possible key.
Maybe nobody will notice but I don't like building inconsistencies
into the code because it opens the door for unpleasant surprises.
Note that a destructuring for loop was recently implemented, so you
can say things like
for [ [ k ], v ] in foo do (something with key k and value v);
> Is a hash table suitable for a large amount of data, like 1 million key value pairs?
As far as I know, both implementations of hash tables (Maxima and
Lisp) should be OK with large numbers of items, but I haven't
specifically explored the limits.
> Are there any limitations regarding the key?
As far as I can tell, any expressions can be keys. For Lisp hash
tables, that is certainly true, and for Maxima hash tables, it is true
from what I can see, although I am not 100% sure that Maxima hash
tables can handle all kinds of Lisp expressions -- any nonatomic
expression is OK, but I wonder about "exotic" Lisp atoms such as
structures, functions, arrays, hash tables, etc.
Hope this helps,
Robert
PS. Here is the current version of my code for "contains".
(defmfun $contains (a sub)
(cond
((not ($listp sub)) (merror (intl:gettext "contains: second
argument must be a list; found: ~M") sub))
((symbolp a) (contains-for-undeclared-array a sub))
((hash-table-p a) (contains-for-lisp-hash-table a sub))
(t
(merror (intl:gettext "contains: ~M is neither an undeclared
array nor a hash table.") a))))
;; This is adapted from the first part of HARRFIND.
(defun contains-for-undeclared-array (a sub)
(prog (ary iteml)
(setq ary (symbol-array (mget a 'hashar)))
(when (null ary) (merror (intl:gettext "contains: ~M doesn't
appear to be an undeclared array.") a))
(cond ((not (= (aref ary 2) (length (cdr sub))))
(merror (intl:gettext "contains array ~:M must have ~:M
indices; found: ~M") a (aref ary 2) sub)))
(setq sub (cdr sub))
(setq iteml (aref ary (+ 3 (rem (hasher sub) (aref ary 0)))))
a (cond ((null iteml) (go b))
((alike (caar iteml) sub) (return t)))
(setq iteml (cdr iteml))
(go a)
b nil))
(defun contains-for-lisp-hash-table (a sub)
;; If SUB looks like [something], then assume the key is something.
;; But if SUB looks like [something, something else, ...], then
assume the key is (something, something else, ...).
;; This is kind of terrible, but inevitable here since
foo[something]: 1234 uses something as the key, not (something).
(let ((key (if (= (length (cdr sub)) 1) (cadr sub) (cdr sub))))
(multiple-value-bind (value is-present) (gethash key a)
(declare (ignore value))
is-present)))
|