Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

looking for [dict equal]

231 views
Skip to first unread message

Petro Kazmirchuk

unread,
Nov 9, 2022, 5:57:10 AM11/9/22
to
I need to compare 2 dicts disregarding order of keys (just equal/not equal), preferably without unnecessary generation of string representations. Can't believe it's not in the core :(
I'd prefer to import it from Tcllib or other standard package rather than copy-paste from the Wiki
thanks in advance!

Schelte

unread,
Nov 9, 2022, 7:14:39 AM11/9/22
to
Because Tcl is not typed, it is not possible to have a general [dict
equal] command. Maybe some dict entries are dicts themselves. Then you
would want those to also disregard the order of their keys. If some of
them are lists, you may care about the order or not. Is 0x2A equal to
42? Things like that are impossible to be inferred from only the data.

So, as the developer of the code with knowledge of what the different
entries may contain, you will just have to loop over one dict, check
that it exists in the other dict and then perform the appropriate
comparisons. Of course the first step is to compare the dict sizes.


Schelte.


saitology9

unread,
Nov 9, 2022, 10:48:15 AM11/9/22
to
Well, since EIAS, you can just compare them directly:

if {$dict_1 eq $dict_2} {...}


To disregard the ordering of the keys, you can sort them first:

if {[lsort -dict -index 0 $dict_1] eq [lsort -dict -index 0 $dict_2]} {...}



Luc

unread,
Nov 9, 2022, 1:17:49 PM11/9/22
to
One may be shocked (or not really) to learn that I know nothing about
dicts. I used to write Tcl every day and hang around the wiki and
chat room often in the oughts, a long time ago. Then I stepped away
from Tcl and the community for personal reasons...

Until now.

When I left the scene, dict was a pretty new addition and I never got
around to learning it. I just read the man page and found it very
confusing. I don't see how it's much different from a list. Looks like
it's a list-like array or an array-like list. Whatever. I need to put
some time aside to study it carefully.

With that said, in my ignorance, I suppose you can traverse the entire
content of a dict, can't you? It would seem useless to me if you can't.
So it must be possible and not even very difficult to write a proc
that will "gut" or "disassemble" a dict in multiple parts that can then
be compared directly with the counterparts of another dict, one to one.
A little bit like comparing two directories, each one with multiple
subdirectories and files.

Maybe you can even use md5sum to compare those "gutted" parts.

It's an idea. But I think I've made it clear that I don't understand
how dicts work, so pardon me if the idea is not good -- if possible
at all.

--
Luc
>>
**************************

Rich

unread,
Nov 9, 2022, 5:14:47 PM11/9/22
to
Luc <n...@no.no> wrote:
> When I left the scene, dict was a pretty new addition and I never got
> around to learning it. I just read the man page and found it very
> confusing. I don't see how it's much different from a list. Looks like
> it's a list-like array or an array-like list. Whatever. I need to put
> some time aside to study it carefully.

One way of viewing it is as an array with "ordering" -- the ordering
being the insertion order of the keys.

The big difference between dicts and array's is that arrays as they
were built way back in the beginning of Tcl, are logically collections
of variables (each array entry is as if it was a separate variable
underneath).

That is why you can't use $array to get all of the contents of an
array, and why you can't use $array to pass an entire array into a proc
as a parameter. You have to pass the name of the array, and then use
upvar to link the passed in name to a variable within the proc.

Dicts are arrays that do not have that restriction of being
"collections of variables". Because of that, one can do "puts $dict" to
obtain the string representation of the dict on an output channel, and
one can use $dict to pass a dict to a proc as a parameter, instead of
having to pass in the name of the dict.

Now, one could argue that the 'definition' of arrays could have also
changed to allow $array to retreive the string rep., and to allow
passing entire arrays into a proc via $array syntax. My *guess* as to
why that was not done is doing so was likely seen as to much of a risk
of a breaking change to very old Tcl syntax, so the definiton of arrays
was unchanged and [dict] was added.

> With that said, in my ignorance, I suppose you can traverse the entire
> content of a dict, can't you? It would seem useless to me if you can't.

You can.

> So it must be possible and not even very difficult to write a proc
> that will "gut" or "disassemble" a dict in multiple parts that can then
> be compared directly with the counterparts of another dict, one to one.

For your own dicts that you create in your own programs, yes, this is
quite possible. Because you know what each leg of the tree is supposed
to be.

But creating a generic "equal" procedure, that operates on any two
arbitrary dicts from any coder's piece of code, is what is not
possible. Because any value could itself be another dict, or a plain
list, or just a string. But because Tcl is typeless, there is no way
for a generic Tcl "dict equals" to know, when encountering "this might
be a dict here" whether that is just a string of characters, a list of
six words, or a dict with three keys and three values. More often than
not, the distinction won't matter, but there will be enough instances
where the distinction does matter than a generic 'dict equals' can't be
created that works for all possible inputs.

> A little bit like comparing two directories, each one with multiple
> subdirectories and files.

Except, imagine that you can't know when you encounter something at
level 3, whether it is a file or a directory. If you don't know which
are directories, you don't know which ones to further descend into.
That's the problem with a generic 'dict equals' intended to work on any
possible input.

Petro Kazmirchuk

unread,
Nov 10, 2022, 3:50:23 AM11/10/22
to
Thank you all for responses. Now I understand why there's no generic [dict equal]
yet another thing to reinvent in my code :-( alongside assert, sleep, vwait with timeout and endless argument parsing

Ralf Fassel

unread,
Nov 10, 2022, 5:59:00 AM11/10/22
to
* Rich <ri...@example.invalid>
| Luc <n...@no.no> wrote:
| > A little bit like comparing two directories, each one with multiple
| > subdirectories and files.
>
| Except, imagine that you can't know when you encounter something at
| level 3, whether it is a file or a directory. If you don't know which
| are directories, you don't know which ones to further descend into.
| That's the problem with a generic 'dict equals' intended to work on any
| possible input.

For example:

% set a [dict create val1 [list val11 val12]]
val1 {val11 val12}

% set b [dict create val1 [dict create val11 val12]]
val1 {val11 val12}

% set c [dict create val1 "val12 val12"]
val1 {val11 val12}

'a' has a *list* as value for the key, 'b' has a *dict* as value for the
same key, 'c' a *string*. They all have the same string representation,
but are they *equal*?

For files and directories you have [file isdirectory] and [file isfile],
but that info is not easily available for the dict elements (and
probably not even reliably, considering "dict set a val1 val11 val13" is
possible).

R'

Rich

unread,
Nov 10, 2022, 7:59:42 AM11/10/22
to
For 'asssert' there is the 'error' command and/or 'return -code error'.

For argument parsing, one option is to use 'cmdline' from Tcllib (if
you do not yet have Tcllib installed, then I recommend you do so, it
contains a lot of ready made pieces).

For a blocking sleep, plain 'after' works just fine. But if you
instead want a sleep that does not otherwise block the event loop then
yes there is a little work there.

vwait with timeout is just vwait with a background after to 'unwait'
the vwait if the timeout expires. Better done as a namespace ensemble
or an actual object just to keep the necessary variables isolated.

You will want to be careful of the 'nested vwait' issue:

https://wiki.tcl-lang.org/page/vwait

Oleg Nemanov

unread,
Nov 10, 2022, 8:27:05 AM11/10/22
to
среда, 9 ноября 2022 г. в 13:57:10 UTC+3, Petro Kazmirchuk:
> I need to compare 2 dicts disregarding order of keys (just equal/not equal), preferably without unnecessary generation of string representations. Can't believe it's not in the core :(
> I'd prefer to import it from Tcllib or other standard package rather than copy-paste from the Wiki
> thanks in advance!

You can't compare 2 dicts without additional info from the side about every value type(is this a leaf element - string, for example; or this is a dict).
If you create a dict with "dict create" and "dict set" commands, then you can try to derive a type info with help of ::tcl::unsupported::representation.

It would be better if tcl has no shimmering, but instead has explicit routines to stringify and parse values, imho.
Implicit data type convertion(shimmering) is the cause of hardly findable bugs.

May be, sometime in the future we will get something like tcl strict mode(turnable by some option) for turning off shimmering.

Luc

unread,
Nov 10, 2022, 10:49:28 AM11/10/22
to
On Thu, 10 Nov 2022 11:58:54 +0100, Ralf Fassel wrote:

> For example:
>
> % set a [dict create val1 [list val11 val12]]
> val1 {val11 val12}
>
> % set b [dict create val1 [dict create val11 val12]]
> val1 {val11 val12}
>
> % set c [dict create val1 "val12 val12"]
> val1 {val11 val12}
>
> 'a' has a *list* as value for the key, 'b' has a *dict* as value for the
> same key, 'c' a *string*. They all have the same string representation,
> but are they *equal*?
>
> For files and directories you have [file isdirectory] and [file isfile],
> but that info is not easily available for the dict elements (and
> probably not even reliably, considering "dict set a val1 val11 val13" is
> possible).
>
> R'


For the strict sake of comparison, I would treat all occurrences of
[llength $list] <= 1 as strings.

Or maybe treat all non-space strings as [list $string].

About the dicts, I don't know. I still don't know how they really work
until I reserve some time to study them adequately.

Of course, there is a 99.9% probability that you are right and I am wrong.

I'm just very stubborn. It can be a valuable trait in certain situations.

--
Luc
>>

Ralf Fassel

unread,
Nov 10, 2022, 11:39:40 AM11/10/22
to
* Luc <n...@no.no>
| About the dicts, I don't know. I still don't know how they really work
| until I reserve some time to study them adequately.

Always a good idea :-) I don't know the internals of dicts either, but
Rich has listed the advantages when using them instead of arrays up-thread.

| I'm just very stubborn. It can be a valuable trait in certain situations.

I wouldn't call that stubborn in this context. If you're honestly
trying to understand what this is all about, that's a Good Thing¹ IMHO.

R'
---
¹ http://www.catb.org/~esr/jargon/html/G/Good-Thing.html

Harald Oehlmann

unread,
Nov 10, 2022, 11:56:14 AM11/10/22
to
To guess the type of a list value, you may look what the pdict does with
values:

https://wiki.tcl-lang.org/page/pdict%3A+Pretty+print+a+dict?R=0&O=pdict&W=

Look at the line:
[string match "value is a dict*"\
[tcl::unsupported::representation $val]]

Enjoy,
Harald

Rich

unread,
Nov 10, 2022, 1:14:17 PM11/10/22
to
Luc <n...@no.no> wrote:
> On Thu, 10 Nov 2022 11:58:54 +0100, Ralf Fassel wrote:
>
>> For example:
>>
>> % set a [dict create val1 [list val11 val12]]
>> val1 {val11 val12}
>>
>> % set b [dict create val1 [dict create val11 val12]]
>> val1 {val11 val12}
>>
>> % set c [dict create val1 "val12 val12"]
>> val1 {val11 val12}
>>
>> 'a' has a *list* as value for the key, 'b' has a *dict* as value for the
>> same key, 'c' a *string*. They all have the same string representation,
>> but are they *equal*?
>>
>> For files and directories you have [file isdirectory] and [file isfile],
>> but that info is not easily available for the dict elements (and
>> probably not even reliably, considering "dict set a val1 val11 val13" is
>> possible).
>>
>> R'
>
>
> For the strict sake of comparison, I would treat all occurrences of
> [llength $list] <= 1 as strings.
>
> Or maybe treat all non-space strings as [list $string].

Whereupon you delve into the philosophical discussion of what it means to
be "equal" and the reason Javascript has both == and === operators for
different flavors of equality.

Is any "thing" (list, dict string) "equal" if its string representation
is equal? Some will say yes, some will say no.

For those that say no, they will want equal to mean: if the thing is a
list, then each of its individual elements are "equal" (to the same
recursive definition they use for "equal"). But those same folks would
say that a list of four elements is not equal to a dict of two keys and
two values, even if the "Tcl string representation" of both is
identical.

I.e.:

$ rlwrap tclsh
% set a [list one two three four]
one two three four
% set b [dict create one two three four]
one two three four
% string equal $a $b
1

$a is a list of four elements. $b is a dict of two key/value pairs.
Their string representations are equal, but a list is a subtly
different 'thing' than a dict. So some would say that $a should not be
equal to $b because they are different "types" underneath. Those that
usualy want such strict definitions also often use languages who's type
systems enforce such definitions, where a variable defined as a "list"
is never equal to a variable defined as a "dict" because list and dict
are two different "types" in the language.

One single 'equal' can't satisify both sides of that divide.

> About the dicts, I don't know. I still don't know how they really work
> until I reserve some time to study them adequately.

At a very broad level, dict's are essentially just arrays that you can
pass into procs by value instead of by name only.

Rich

unread,
Nov 10, 2022, 1:21:16 PM11/10/22
to
True, but that big "unsupported" there in the proc namespace should
serve as a warning....

0 new messages