discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Tables/Dictionary proposal

RD
Revar Desmera
Sat, Mar 20, 2021 11:24 PM

Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD:

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // String keys.
false: a,    // Boolean keys.
99  : 34,    // Numeric keys.
56  : "AZ",  // Value can be any type.
"baz": 19,    // Should lists, ranges, or functions be allowed for keys?
"bar": 65,    // Overwrite previous key entry.
undef: 77,    // Should undef keys be allowed?
"bla": undef, // Should undef values be allowed?
str("c","a","t") : b  // Expressions usable for keys or values.
};

// Dictionary comprehension.
dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 };

// Extending/merging dictionaries. Later duplicate keys overwrite previous ones.
// Should lists merge into dictionaries using numeric positions as keys?
dict3 = concat(dict1, dict2, {"flee": 0});

// Getting dictionary values.
x = dict3["bar"];
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (key = dict3) {
echo(key=key, val=dict3[key]);
}

This implementation would let me, among other things, rewrite my vnf_validate() (a validator for polyhedron() data.) to be FAR faster.

  • Revar
Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD: // Dictionary declaration. a = "fee"; b = "fie"; dict1 = { "foo": 23, // KEY : VALUE "bar": 77, // String keys. false: a, // Boolean keys. 99 : 34, // Numeric keys. 56 : "AZ", // Value can be any type. "baz": 19, // Should lists, ranges, or functions be allowed for keys? "bar": 65, // Overwrite previous key entry. undef: 77, // Should undef keys be allowed? "bla": undef, // Should undef values be allowed? str("c","a","t") : b // Expressions usable for keys or values. }; // Dictionary comprehension. dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 }; // Extending/merging dictionaries. Later duplicate keys overwrite previous ones. // Should lists merge into dictionaries using numeric positions as keys? dict3 = concat(dict1, dict2, {"flee": 0}); // Getting dictionary values. x = dict3["bar"]; y = dict3[a]; z = dict3["fit"]; // Returns undef for nonexistent keys. // Iterating a dictionary. Iterate keys in original declared order? for (key = dict3) { echo(key=key, val=dict3[key]); } This implementation would let me, among other things, rewrite my `vnf_validate()` (a validator for `polyhedron()` data.) to be FAR faster. - Revar
DM
Doug Moen
Sun, Mar 21, 2021 12:08 AM

Here are my comments about Revars dictionary proposal, compared to my record proposal.

Records are restricted to having strings as keys. I use a std::ordered_map with a locale-independent key comparison function, which means that when you iterate over a record, you get the keys in alphabetical order: fully deterministic and very predictable.

Dictionaries are more complicated. If you allow arbitrary values as keys, then do you use an ordered map or a hash map? What is the hash function? Are functions permitted as keys? How do you hash a function? How do you determine function equality? If you use the function address to compute the hash, then you get non-deterministic behaviour. The iteration order of a dictionary will be different on different machines, which could cause shapes to render differently on different machines. That can be nasty. Python solves this problem by preserving the insertion order of a dictionary using a more complicated data structure. Then, when you iterate over a dictionary, the iteration order is the insertion order. This gives you deterministic behaviour back. Even then, I think it is tricky to allow functions to be used as keys, because function equality is not well defined.

Also, for iterating over a dictionary or record, I think it is better to give [key,value] pairs rather than just key values.

On Sat, Mar 20, 2021, at 7:24 PM, Revar Desmera wrote:

Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD:

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // String keys.
false: a,    // Boolean keys.
99  : 34,    // Numeric keys.
56  : "AZ",  // Value can be any type.
"baz": 19,    // Should lists, ranges, or functions be allowed for keys?
"bar": 65,    // Overwrite previous key entry.
undef: 77,    // Should undef keys be allowed?
"bla": undef, // Should undef values be allowed?
str("c","a","t") : b  // Expressions usable for keys or values.
};

// Dictionary comprehension.
dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 };

// Extending/merging dictionaries. Later duplicate keys overwrite previous ones.
// Should lists merge into dictionaries using numeric positions as keys?
dict3 = concat(dict1, dict2, {"flee": 0});

// Getting dictionary values.
x = dict3["bar"];
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (key = dict3) {
echo(key=key, val=dict3[key]);
}

This implementation would let me, among other things, rewrite my vnf_validate() (a validator for polyhedron() data.) to be FAR faster.

  • Revar

OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave%40lists.openscad.org

Here are my comments about Revars dictionary proposal, compared to my record proposal. Records are restricted to having strings as keys. I use a std::ordered_map with a locale-independent key comparison function, which means that when you iterate over a record, you get the keys in alphabetical order: fully deterministic and very predictable. Dictionaries are more complicated. If you allow arbitrary values as keys, then do you use an ordered map or a hash map? What is the hash function? Are functions permitted as keys? How do you hash a function? How do you determine function equality? If you use the function address to compute the hash, then you get non-deterministic behaviour. The iteration order of a dictionary will be different on different machines, which could cause shapes to render differently on different machines. That can be nasty. Python solves this problem by preserving the insertion order of a dictionary using a more complicated data structure. Then, when you iterate over a dictionary, the iteration order is the insertion order. This gives you deterministic behaviour back. Even then, I think it is tricky to allow functions to be used as keys, because function equality is not well defined. Also, for iterating over a dictionary or record, I think it is better to give [key,value] pairs rather than just key values. On Sat, Mar 20, 2021, at 7:24 PM, Revar Desmera wrote: > Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD: > >> // Dictionary declaration. >> a = "fee"; b = "fie"; >> dict1 = { >> "foo": 23, // KEY : VALUE >> "bar": 77, // String keys. >> false: a, // Boolean keys. >> 99 : 34, // Numeric keys. >> 56 : "AZ", // Value can be any type. >> "baz": 19, // Should lists, ranges, or functions be allowed for keys? >> "bar": 65, // Overwrite previous key entry. >> undef: 77, // Should undef keys be allowed? >> "bla": undef, // Should undef values be allowed? >> str("c","a","t") : b // Expressions usable for keys or values. >> }; >> >> // Dictionary comprehension. >> dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 }; >> >> // Extending/merging dictionaries. Later duplicate keys overwrite previous ones. >> // Should lists merge into dictionaries using numeric positions as keys? >> dict3 = concat(dict1, dict2, {"flee": 0}); >> >> // Getting dictionary values. >> x = dict3["bar"]; >> y = dict3[a]; >> z = dict3["fit"]; // Returns undef for nonexistent keys. >> >> // Iterating a dictionary. Iterate keys in original declared order? >> for (key = dict3) { >> echo(key=key, val=dict3[key]); >> } > This implementation would let me, among other things, rewrite my `vnf_validate()` (a validator for `polyhedron()` data.) to be FAR faster. > > - Revar > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave%40lists.openscad.org> >
N
NateTG
Sun, Mar 21, 2021 12:15 AM

It is already possible to create dictionary / associative array functionality
using function literals.  This is a quick and dirty version that uses a dumb
search, but that's something that could be corrected with a little bit of
work.

--
Sent from: http://forum.openscad.org/

It is already possible to create dictionary / associative array functionality using function literals. This is a quick and dirty version that uses a dumb search, but that's something that could be corrected with a little bit of work. -- Sent from: http://forum.openscad.org/
RD
Revar Desmera
Sun, Mar 21, 2021 1:00 AM

As I understand this code, it still has an O(n) key search time, and it’s not even making use of search().  So, pretty slow for a table.  That’s not even getting into the construction time. However, I think there may be the promise here for the implementation of faster structures.  Since function literals can effectively contain their own data, they can act as nodes.  This bears investigation, but I think it will still be a fair lot less efficient than built-in tables.

  • Revar

On Mar 20, 2021, at 5:15 PM, NateTG nate-openscadforum@pedantic.org wrote:

It is already possible to create dictionary / associative array functionality using function literals.  This is a quick and dirty version that uses a dumb search, but that's something that could be corrected with a little bit of work.

function last(list)=
let(
listcheck=assert(is_list(list))
)
len(list)-1
;
<raw>
function index_list_find(list,index,i=0)=
(i>last(list))?
undef
:(list[i][0]==index)?
list[i][1]
:
index_list_find(list,index,i+1)
;

function index_list_from_pairs(
filter=function(index) (is_string(index)),
pairlist=[],
)=
let(
test=[for(item=pairlist) assert(is_list(item) && len(item)==2)]
)
[for (item=pairlist) if(filter(item[0])) item]
;

function list_to_pairs(list)=
let(
test=assert(0==len(list)%2,"odd length list")
)
[for(i=[0:2:last(list)-1]) [list[i],list[i+1]]]
;

function simple_struct(
arg=undef,
nlist=[],
slist=[]
)=
(is_string(arg))?
index_list_find(list=slist,index=arg)
:(is_num(arg))?
index_list_find(list=nlist,index=arg)
:(is_list(arg))?
("add_list"==arg[0])?
function (list) (
simple_struct(arg=["add_pairs"],nlist=nlist,slist=slist) (list_to_pairs(list))
)
:("add_pairs"==arg[0])?
function(pairs) (
function(arg) (
simple_struct(
arg=arg,
nlist=concat(
nlist,
index_list_from_pairs(
filter=function(x) (is_num(x)),
pairlist=pairs
)
)
,
slist=concat(
slist,
index_list_from_pairs(
filter=function(x) (is_string(x)),
pairlist=pairs
)
)
)
)
)
:("keys"==arg[0])?
concat([for(n=nlist) n[0]],[for(s=slist) s[0]])
:("values"==arg[0])?
concat([for(n=nlist) n[1]],[for(s=slist) s[1]])
:
assert(false,str("unsupported command:",arg[0]))
:
assert(false,str("unexpected argument:",arg))
;

test=simple_struct(["add_pairs"])([ ["a",1],[1,"a"],["1",2]]);

echo(test("a"));
echo(test("1"));
echo(test(1));

test2=test(["add_list"])(["one","two","three","four"]);

echo(test2("one"));
echo(test2(["keys"]));
echo(test2(["values"]));

Sent from the OpenSCAD mailing list archive http://forum.openscad.org/ at Nabble.com.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

As I understand this code, it still has an O(n) key search time, and it’s not even making use of `search()`. So, pretty slow for a table. That’s not even getting into the construction time. However, I think there may be the promise here for the implementation of faster structures. Since function literals can effectively contain their own data, they can act as nodes. This bears investigation, but I think it will still be a fair lot less efficient than built-in tables. - Revar > On Mar 20, 2021, at 5:15 PM, NateTG <nate-openscadforum@pedantic.org> wrote: > > It is already possible to create dictionary / associative array functionality using function literals. This is a quick and dirty version that uses a dumb search, but that's something that could be corrected with a little bit of work. > > function last(list)= > let( > listcheck=assert(is_list(list)) > ) > len(list)-1 > ; > <raw> > function index_list_find(list,index,i=0)= > (i>last(list))? > undef > :(list[i][0]==index)? > list[i][1] > : > index_list_find(list,index,i+1) > ; > > function index_list_from_pairs( > filter=function(index) (is_string(index)), > pairlist=[], > )= > let( > test=[for(item=pairlist) assert(is_list(item) && len(item)==2)] > ) > [for (item=pairlist) if(filter(item[0])) item] > ; > > function list_to_pairs(list)= > let( > test=assert(0==len(list)%2,"odd length list") > ) > [for(i=[0:2:last(list)-1]) [list[i],list[i+1]]] > ; > > function simple_struct( > arg=undef, > nlist=[], > slist=[] > )= > (is_string(arg))? > index_list_find(list=slist,index=arg) > :(is_num(arg))? > index_list_find(list=nlist,index=arg) > :(is_list(arg))? > ("add_list"==arg[0])? > function (list) ( > simple_struct(arg=["add_pairs"],nlist=nlist,slist=slist) (list_to_pairs(list)) > ) > :("add_pairs"==arg[0])? > function(pairs) ( > function(arg) ( > simple_struct( > arg=arg, > nlist=concat( > nlist, > index_list_from_pairs( > filter=function(x) (is_num(x)), > pairlist=pairs > ) > ) > , > slist=concat( > slist, > index_list_from_pairs( > filter=function(x) (is_string(x)), > pairlist=pairs > ) > ) > ) > ) > ) > :("keys"==arg[0])? > concat([for(n=nlist) n[0]],[for(s=slist) s[0]]) > :("values"==arg[0])? > concat([for(n=nlist) n[1]],[for(s=slist) s[1]]) > : > assert(false,str("unsupported command:",arg[0])) > : > assert(false,str("unexpected argument:",arg)) > ; > > test=simple_struct(["add_pairs"])([ ["a",1],[1,"a"],["1",2]]); > > echo(test("a")); > echo(test("1")); > echo(test(1)); > > test2=test(["add_list"])(["one","two","three","four"]); > > echo(test2("one")); > echo(test2(["keys"])); > echo(test2(["values"])); > > Sent from the OpenSCAD mailing list archive <http://forum.openscad.org/> at Nabble.com. > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org
RD
Revar Desmera
Sun, Mar 21, 2021 1:07 AM

Based on comments, here’s my revised proposal.  Main changes are:

  • All keys are coerced to strings, similar to str().
  • Using each for dictionary comprehensions.
  • Structure type var.foo syntax.
  • Iteration using [key,value].
  • Discussion of interaction with ternary ?: operators.
  • Discussion of setting values in a dictionary, in both mutable and immutable variants.

// Dictionaries proposal, v2.

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // Keys are Strings.
false: a,    // All keys are coerced into a string via str().  This key is "false"
99  : 34,    // This key is the string "99"
56  : "AZ",  // Value can be any type.
"bar": 65,    // Overwrites previous "bar" key entry.
str("c","a","t") : b  // Expressions usable for keys or values.
a>b?a:b : a>b?5:8  // If you have a '?' then the next ':' is part of the ternary expression,
// not a separator.  This should always be unambiguous.  If not, use parens.
};

// Dictionary comprehension.
dict2 = {
for (i=[0:100])
if(i%2==0)  // if works as expected.
each {      // each will expand sub-dictionaries, but only in a dictionary comprehension
i:i5,  // Using each on a dictionary in a list comprehension expands into [key,val] items.
i+0.5:i
5+1
}
};

// Extending/merging dictionaries. Later duplicated keys overwrite previous ones.
// Lists merge into dictionaries using numeric positions as keys.
dict3 = concat(dict1, dict2, {"flee":0});

// Getting dictionary values.
w = dict3["bar"];
x = dict3.baz;  // Same as dict3["baz"].  Lets you make structures.
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (kv = dict3) {
echo(key=kv[0], val=kv[1]);
}

// If we ever have mutable dictionaries, then you can set items like:
dict3.foo = 17;
dict3["bar"] = 12;

// Otherwise with immutable dictionaries, we end up doing
dict4 = concat(dict3, {"foo":17});
dict5 = {each dict4, "bar":12};

-Revar

On Mar 20, 2021, at 5:08 PM, Doug Moen doug@moens.org wrote:

Here are my comments about Revars dictionary proposal, compared to my record proposal.

Records are restricted to having strings as keys. I use a std::ordered_map with a locale-independent key comparison function, which means that when you iterate over a record, you get the keys in alphabetical order: fully deterministic and very predictable.

Dictionaries are more complicated. If you allow arbitrary values as keys, then do you use an ordered map or a hash map? What is the hash function? Are functions permitted as keys? How do you hash a function? How do you determine function equality? If you use the function address to compute the hash, then you get non-deterministic behaviour. The iteration order of a dictionary will be different on different machines, which could cause shapes to render differently on different machines. That can be nasty. Python solves this problem by preserving the insertion order of a dictionary using a more complicated data structure. Then, when you iterate over a dictionary, the iteration order is the insertion order. This gives you deterministic behaviour back. Even then, I think it is tricky to allow functions to be used as keys, because function equality is not well defined.

Also, for iterating over a dictionary or record, I think it is better to give [key,value] pairs rather than just key values.

On Sat, Mar 20, 2021, at 7:24 PM, Revar Desmera wrote:

Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD:

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // String keys.
false: a,    // Boolean keys.
99  : 34,    // Numeric keys.
56  : "AZ",  // Value can be any type.
"baz": 19,    // Should lists, ranges, or functions be allowed for keys?
"bar": 65,    // Overwrite previous key entry.
undef: 77,    // Should undef keys be allowed?
"bla": undef, // Should undef values be allowed?
str("c","a","t") : b  // Expressions usable for keys or values.
};

// Dictionary comprehension.
dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 };

// Extending/merging dictionaries. Later duplicate keys overwrite previous ones.
// Should lists merge into dictionaries using numeric positions as keys?
dict3 = concat(dict1, dict2, {"flee": 0});

// Getting dictionary values.
x = dict3["bar"];
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (key = dict3) {
echo(key=key, val=dict3[key]);
}
This implementation would let me, among other things, rewrite my vnf_validate() (a validator for polyhedron() data.) to be FAR faster.

  • Revar

OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave%40lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org

Based on comments, here’s my revised proposal. Main changes are: - All keys are coerced to strings, similar to `str()`. - Using `each` for dictionary comprehensions. - Structure type var.foo syntax. - Iteration using [key,value]. - Discussion of interaction with ternary ?: operators. - Discussion of setting values in a dictionary, in both mutable and immutable variants. // Dictionaries proposal, v2. // Dictionary declaration. a = "fee"; b = "fie"; dict1 = { "foo": 23, // KEY : VALUE "bar": 77, // Keys are Strings. false: a, // All keys are coerced into a string via str(). This key is "false" 99 : 34, // This key is the string "99" 56 : "AZ", // Value can be any type. "bar": 65, // Overwrites previous "bar" key entry. str("c","a","t") : b // Expressions usable for keys or values. a>b?a:b : a>b?5:8 // If you have a '?' then the next ':' is part of the ternary expression, // not a separator. This should always be unambiguous. If not, use parens. }; // Dictionary comprehension. dict2 = { for (i=[0:100]) if(i%2==0) // `if` works as expected. each { // `each` will expand sub-dictionaries, but only in a dictionary comprehension i:i*5, // Using `each` on a dictionary in a list comprehension expands into [key,val] items. i+0.5:i*5+1 } }; // Extending/merging dictionaries. Later duplicated keys overwrite previous ones. // Lists merge into dictionaries using numeric positions as keys. dict3 = concat(dict1, dict2, {"flee":0}); // Getting dictionary values. w = dict3["bar"]; x = dict3.baz; // Same as dict3["baz"]. Lets you make structures. y = dict3[a]; z = dict3["fit"]; // Returns undef for nonexistent keys. // Iterating a dictionary. Iterate keys in original declared order? for (kv = dict3) { echo(key=kv[0], val=kv[1]); } // If we ever have mutable dictionaries, then you can set items like: dict3.foo = 17; dict3["bar"] = 12; // Otherwise with immutable dictionaries, we end up doing dict4 = concat(dict3, {"foo":17}); dict5 = {each dict4, "bar":12}; -Revar > On Mar 20, 2021, at 5:08 PM, Doug Moen <doug@moens.org> wrote: > > Here are my comments about Revars dictionary proposal, compared to my record proposal. > > Records are restricted to having strings as keys. I use a std::ordered_map with a locale-independent key comparison function, which means that when you iterate over a record, you get the keys in alphabetical order: fully deterministic and very predictable. > > Dictionaries are more complicated. If you allow arbitrary values as keys, then do you use an ordered map or a hash map? What is the hash function? Are functions permitted as keys? How do you hash a function? How do you determine function equality? If you use the function address to compute the hash, then you get non-deterministic behaviour. The iteration order of a dictionary will be different on different machines, which could cause shapes to render differently on different machines. That can be nasty. Python solves this problem by preserving the insertion order of a dictionary using a more complicated data structure. Then, when you iterate over a dictionary, the iteration order is the insertion order. This gives you deterministic behaviour back. Even then, I think it is tricky to allow functions to be used as keys, because function equality is not well defined. > > Also, for iterating over a dictionary or record, I think it is better to give [key,value] pairs rather than just key values. > > On Sat, Mar 20, 2021, at 7:24 PM, Revar Desmera wrote: >> Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD: >> >> // Dictionary declaration. >> a = "fee"; b = "fie"; >> dict1 = { >> "foo": 23, // KEY : VALUE >> "bar": 77, // String keys. >> false: a, // Boolean keys. >> 99 : 34, // Numeric keys. >> 56 : "AZ", // Value can be any type. >> "baz": 19, // Should lists, ranges, or functions be allowed for keys? >> "bar": 65, // Overwrite previous key entry. >> undef: 77, // Should undef keys be allowed? >> "bla": undef, // Should undef values be allowed? >> str("c","a","t") : b // Expressions usable for keys or values. >> }; >> >> // Dictionary comprehension. >> dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 }; >> >> // Extending/merging dictionaries. Later duplicate keys overwrite previous ones. >> // Should lists merge into dictionaries using numeric positions as keys? >> dict3 = concat(dict1, dict2, {"flee": 0}); >> >> // Getting dictionary values. >> x = dict3["bar"]; >> y = dict3[a]; >> z = dict3["fit"]; // Returns undef for nonexistent keys. >> >> // Iterating a dictionary. Iterate keys in original declared order? >> for (key = dict3) { >> echo(key=key, val=dict3[key]); >> } >> This implementation would let me, among other things, rewrite my `vnf_validate()` (a validator for `polyhedron()` data.) to be FAR faster. >> >> - Revar >> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave%40lists.openscad.org> >> > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
JB
Jordan Brown
Sun, Mar 21, 2021 1:08 AM
<head> <meta http-equiv="Content-Type" content="text/html; charset="> My first thought is that the simple case of dictionary literals should allow constant unquoted keys, ala JavaScript:
polar = { rho: 10, theta:0, phi:45 };

That seems like by far the dominant use case, at least in JS. 
Everything else would have to work around it.  The big question is then
how you use an expression as the key when creating one of these.

The JavaScript answer is a subscripted assignment, e.g. polar['rho'] =
5;.   But that can never fly in OpenSCAD because everything is immutable.

What comes to mind is an alternate syntax, where the name:value could be
replaced by [ name, value ].  Thus:

th = "theta";
polar = { rho: 10, [th, 10], phi: 45 };

Perhaps the two alternatives are name:value and expr, where expr must
evaluate to a two-element vector.  This has implications for non-string
keys.  Is { 5: 10 } the same as {[5, 10]}?  Is { true: "hello" } the
same as { [ true, "hello" ] }?  Is  { [ true, "hello" } ]the same as { [
"true", "hello" ] }?

For comparison, note Torsten's prototype at
https://github.com/openscad/openscad/pull/3087.  It appears to allow
only for constant keys, and uses = as the separator between name and
value, to align with normal assignment.  That makes this syntax be more
like the existing { } syntax, but not like JavaScript or Python.

Some specific comments:

     "baz": 19,    // Should lists, ranges, or functions be allowed
 for keys?

No.  Not worth the hassle.  But if keys can be expressions, they could
be allowed in the future.

     "bar": 65,    // Overwrite previous key entry.

Yes.  But mostly to support some sort of concat-like expression.

     undef: 77,    // Should undef keys be allowed?

Yes.

     "bla": undef, // Should undef values be allowed?

Yes, absolutely.  (Especially important to allow a concat-like
expression to delete values supplied earlier.)  No strong opinion on
whether this deletes the key or leaves it with a value of undef.  The
difference would be in how it iterates.

 // Getting dictionary values.
 x = dict3["bar"];
 y = dict3[a];
 z = dict3["fit"];   // Returns undef for nonexistent keys.

Should also allow dict3.bar.

<head> <meta http-equiv="Content-Type" content="text/html; charset="> My first thought is that the simple case of dictionary literals should allow constant unquoted keys, ala JavaScript: polar = { rho: 10, theta:0, phi:45 }; That seems like by far the dominant use case, at least in JS.  Everything else would have to work around it.  The big question is then how you use an expression as the key when creating one of these. The JavaScript answer is a subscripted assignment, e.g. polar['rho'] = 5;.   But that can never fly in OpenSCAD because everything is immutable. What comes to mind is an alternate syntax, where the name:value could be replaced by [ name, value ].  Thus: th = "theta"; polar = { rho: 10, [th, 10], phi: 45 }; Perhaps the two alternatives are name:value and expr, where expr must evaluate to a two-element vector.  This has implications for non-string keys.  Is { 5: 10 } the same as {[5, 10]}?  Is { true: "hello" } the same as { [ true, "hello" ] }?  Is  { [ true, "hello" } ]the same as { [ "true", "hello" ] }? For comparison, note Torsten's prototype at https://github.com/openscad/openscad/pull/3087.  It appears to allow only for constant keys, and uses = as the separator between name and value, to align with normal assignment.  That makes this syntax be more like the existing { } syntax, but not like JavaScript or Python. Some specific comments: >     "baz": 19,    // Should lists, ranges, or functions be allowed > for keys? > No.  Not worth the hassle.  But if keys can be expressions, they could be allowed in the future. >     "bar": 65,    // Overwrite previous key entry. > Yes.  But mostly to support some sort of concat-like expression. >     undef: 77,    // Should undef keys be allowed? > Yes. >     "bla": undef, // Should undef values be allowed? > Yes, absolutely.  (Especially important to allow a concat-like expression to delete values supplied earlier.)  No strong opinion on whether this deletes the key or leaves it with a value of undef.  The difference would be in how it iterates. > // Getting dictionary values. > x = dict3["bar"]; > y = dict3[a]; > z = dict3["fit"];   // Returns undef for nonexistent keys. > Should also allow dict3.bar.
RD
Revar Desmera
Sun, Mar 21, 2021 1:26 AM

Hmm.  What about a declaration syntax that allows key literals preceded by ‘.’:

dict = { .rho: 10, .theta:0, “foo”:”bar” };

That would make it similar in feel to the key literal getter syntax:

x = dict.theta;

-Revar

On Mar 20, 2021, at 6:08 PM, Jordan Brown openscad@jordan.maileater.net wrote:

<head> <meta http-equiv="Content-Type" content="text/html; charset="> My first thought is that the simple case of dictionary literals should allow constant unquoted keys, ala JavaScript: polar = { rho: 10, theta:0, phi:45 }; That seems like by far the dominant use case, at least in JS. Everything else would have to work around it. The big question is then how you use an expression as the key when creating one of these.

The JavaScript answer is a subscripted assignment, e.g. polar['rho'] = 5;.  But that can never fly in OpenSCAD because everything is immutable.

What comes to mind is an alternate syntax, where the name:value could be replaced by [ name, value ].  Thus:
th = "theta";
polar = { rho: 10, [th, 10], phi: 45 };
Perhaps the two alternatives are name:value and expr, where expr must evaluate to a two-element vector.  This has implications for non-string keys.  Is { 5: 10 } the same as {[5, 10]}?  Is { true: "hello" } the same as { [ true, "hello" ] }?  Is  { [ true, "hello" } ]the same as { [ "true", "hello" ] }?

For comparison, note Torsten's prototype at https://github.com/openscad/openscad/pull/3087 https://github.com/openscad/openscad/pull/3087.  It appears to allow only for constant keys, and uses = as the separator between name and value, to align with normal assignment.  That makes this syntax be more like the existing { } syntax, but not like JavaScript or Python.

Some specific comments:

 "baz": 19,    // Should lists, ranges, or functions be allowed for keys?

No.  Not worth the hassle.  But if keys can be expressions, they could be allowed in the future.

 "bar": 65,    // Overwrite previous key entry.

Yes.  But mostly to support some sort of concat-like expression.

 undef: 77,    // Should undef keys be allowed?

Yes.

 "bla": undef, // Should undef values be allowed?

Yes, absolutely.  (Especially important to allow a concat-like expression to delete values supplied earlier.)  No strong opinion on whether this deletes the key or leaves it with a value of undef.  The difference would be in how it iterates.

// Getting dictionary values.
x = dict3["bar"];
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

Should also allow dict3.bar.

Hmm. What about a declaration syntax that allows key literals preceded by ‘.’: dict = { .rho: 10, .theta:0, “foo”:”bar” }; That would make it similar in feel to the key literal getter syntax: x = dict.theta; -Revar > On Mar 20, 2021, at 6:08 PM, Jordan Brown <openscad@jordan.maileater.net> wrote: > > <head> <meta http-equiv="Content-Type" content="text/html; charset="> > My first thought is that the simple case of dictionary literals should allow constant unquoted keys, ala JavaScript: > polar = { rho: 10, theta:0, phi:45 }; > That seems like by far the dominant use case, at least in JS. Everything else would have to work around it. The big question is then how you use an expression as the key when creating one of these. > > The JavaScript answer is a subscripted assignment, e.g. polar['rho'] = 5;. But that can never fly in OpenSCAD because everything is immutable. > > What comes to mind is an alternate syntax, where the name:value could be replaced by [ name, value ]. Thus: > th = "theta"; > polar = { rho: 10, [th, 10], phi: 45 }; > Perhaps the two alternatives are name:value and expr, where expr must evaluate to a two-element vector. This has implications for non-string keys. Is { 5: 10 } the same as {[5, 10]}? Is { true: "hello" } the same as { [ true, "hello" ] }? Is { [ true, "hello" } ]the same as { [ "true", "hello" ] }? > > For comparison, note Torsten's prototype at https://github.com/openscad/openscad/pull/3087 <https://github.com/openscad/openscad/pull/3087>. It appears to allow only for constant keys, and uses = as the separator between name and value, to align with normal assignment. That makes this syntax be more like the existing { } syntax, but not like JavaScript or Python. > > Some specific comments: > >> "baz": 19, // Should lists, ranges, or functions be allowed for keys? > > No. Not worth the hassle. But if keys can be expressions, they could be allowed in the future. > > >> "bar": 65, // Overwrite previous key entry. > > Yes. But mostly to support some sort of concat-like expression. > >> undef: 77, // Should undef keys be allowed? > > Yes. > >> "bla": undef, // Should undef values be allowed? > > Yes, absolutely. (Especially important to allow a concat-like expression to delete values supplied earlier.) No strong opinion on whether this deletes the key or leaves it with a value of undef. The difference would be in how it iterates. > >> // Getting dictionary values. >> x = dict3["bar"]; >> y = dict3[a]; >> z = dict3["fit"]; // Returns undef for nonexistent keys. > > Should also allow dict3.bar. >
RD
Revar Desmera
Sun, Mar 21, 2021 1:36 AM

Actually, thinking on this, I’m having second thoughts about coercing keys to strings.  There’s way too many rounding error problems for numbers.  Also, it just feels kind of out of place in OpenSCAD.

We only have a few data types to worry about.  Making hashes for each type shouldn’t be hard at all. Function literals could be hashed, but why bother?  They could simply be disallowed.

-Revar

On Mar 20, 2021, at 6:07 PM, Revar Desmera revarbat@gmail.com wrote:

Based on comments, here’s my revised proposal.  Main changes are:

  • All keys are coerced to strings, similar to str().
  • Using each for dictionary comprehensions.
  • Structure type var.foo syntax.
  • Iteration using [key,value].
  • Discussion of interaction with ternary ?: operators.
  • Discussion of setting values in a dictionary, in both mutable and immutable variants.

// Dictionaries proposal, v2.

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // Keys are Strings.
false: a,    // All keys are coerced into a string via str().  This key is "false"
99  : 34,    // This key is the string "99"
56  : "AZ",  // Value can be any type.
"bar": 65,    // Overwrites previous "bar" key entry.
str("c","a","t") : b  // Expressions usable for keys or values.
a>b?a:b : a>b?5:8  // If you have a '?' then the next ':' is part of the ternary expression,
// not a separator.  This should always be unambiguous.  If not, use parens.
};

// Dictionary comprehension.
dict2 = {
for (i=[0:100])
if(i%2==0)  // if works as expected.
each {      // each will expand sub-dictionaries, but only in a dictionary comprehension
i:i5,  // Using each on a dictionary in a list comprehension expands into [key,val] items.
i+0.5:i
5+1
}
};

// Extending/merging dictionaries. Later duplicated keys overwrite previous ones.
// Lists merge into dictionaries using numeric positions as keys.
dict3 = concat(dict1, dict2, {"flee":0});

// Getting dictionary values.
w = dict3["bar"];
x = dict3.baz;  // Same as dict3["baz"].  Lets you make structures.
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (kv = dict3) {
echo(key=kv[0], val=kv[1]);
}

// If we ever have mutable dictionaries, then you can set items like:
dict3.foo = 17;
dict3["bar"] = 12;

// Otherwise with immutable dictionaries, we end up doing
dict4 = concat(dict3, {"foo":17});
dict5 = {each dict4, "bar":12};

-Revar

On Mar 20, 2021, at 5:08 PM, Doug Moen <doug@moens.org mailto:doug@moens.org> wrote:

Here are my comments about Revars dictionary proposal, compared to my record proposal.

Records are restricted to having strings as keys. I use a std::ordered_map with a locale-independent key comparison function, which means that when you iterate over a record, you get the keys in alphabetical order: fully deterministic and very predictable.

Dictionaries are more complicated. If you allow arbitrary values as keys, then do you use an ordered map or a hash map? What is the hash function? Are functions permitted as keys? How do you hash a function? How do you determine function equality? If you use the function address to compute the hash, then you get non-deterministic behaviour. The iteration order of a dictionary will be different on different machines, which could cause shapes to render differently on different machines. That can be nasty. Python solves this problem by preserving the insertion order of a dictionary using a more complicated data structure. Then, when you iterate over a dictionary, the iteration order is the insertion order. This gives you deterministic behaviour back. Even then, I think it is tricky to allow functions to be used as keys, because function equality is not well defined.

Also, for iterating over a dictionary or record, I think it is better to give [key,value] pairs rather than just key values.

On Sat, Mar 20, 2021, at 7:24 PM, Revar Desmera wrote:

Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD:

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // String keys.
false: a,    // Boolean keys.
99  : 34,    // Numeric keys.
56  : "AZ",  // Value can be any type.
"baz": 19,    // Should lists, ranges, or functions be allowed for keys?
"bar": 65,    // Overwrite previous key entry.
undef: 77,    // Should undef keys be allowed?
"bla": undef, // Should undef values be allowed?
str("c","a","t") : b  // Expressions usable for keys or values.
};

// Dictionary comprehension.
dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 };

// Extending/merging dictionaries. Later duplicate keys overwrite previous ones.
// Should lists merge into dictionaries using numeric positions as keys?
dict3 = concat(dict1, dict2, {"flee": 0});

// Getting dictionary values.
x = dict3["bar"];
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (key = dict3) {
echo(key=key, val=dict3[key]);
}
This implementation would let me, among other things, rewrite my vnf_validate() (a validator for polyhedron() data.) to be FAR faster.

  • Revar

OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave%40lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org mailto:discuss-leave@lists.openscad.org

Actually, thinking on this, I’m having second thoughts about coercing keys to strings. There’s way too many rounding error problems for numbers. Also, it just feels kind of out of place in OpenSCAD. We only have a few data types to worry about. Making hashes for each type shouldn’t be hard at all. Function literals could be hashed, but why bother? They could simply be disallowed. -Revar > On Mar 20, 2021, at 6:07 PM, Revar Desmera <revarbat@gmail.com> wrote: > > Based on comments, here’s my revised proposal. Main changes are: > - All keys are coerced to strings, similar to `str()`. > - Using `each` for dictionary comprehensions. > - Structure type var.foo syntax. > - Iteration using [key,value]. > - Discussion of interaction with ternary ?: operators. > - Discussion of setting values in a dictionary, in both mutable and immutable variants. > > // Dictionaries proposal, v2. > > // Dictionary declaration. > a = "fee"; b = "fie"; > dict1 = { > "foo": 23, // KEY : VALUE > "bar": 77, // Keys are Strings. > false: a, // All keys are coerced into a string via str(). This key is "false" > 99 : 34, // This key is the string "99" > 56 : "AZ", // Value can be any type. > "bar": 65, // Overwrites previous "bar" key entry. > str("c","a","t") : b // Expressions usable for keys or values. > a>b?a:b : a>b?5:8 // If you have a '?' then the next ':' is part of the ternary expression, > // not a separator. This should always be unambiguous. If not, use parens. > }; > > // Dictionary comprehension. > dict2 = { > for (i=[0:100]) > if(i%2==0) // `if` works as expected. > each { // `each` will expand sub-dictionaries, but only in a dictionary comprehension > i:i*5, // Using `each` on a dictionary in a list comprehension expands into [key,val] items. > i+0.5:i*5+1 > } > }; > > // Extending/merging dictionaries. Later duplicated keys overwrite previous ones. > // Lists merge into dictionaries using numeric positions as keys. > dict3 = concat(dict1, dict2, {"flee":0}); > > // Getting dictionary values. > w = dict3["bar"]; > x = dict3.baz; // Same as dict3["baz"]. Lets you make structures. > y = dict3[a]; > z = dict3["fit"]; // Returns undef for nonexistent keys. > > // Iterating a dictionary. Iterate keys in original declared order? > for (kv = dict3) { > echo(key=kv[0], val=kv[1]); > } > > // If we ever have mutable dictionaries, then you can set items like: > dict3.foo = 17; > dict3["bar"] = 12; > > // Otherwise with immutable dictionaries, we end up doing > dict4 = concat(dict3, {"foo":17}); > dict5 = {each dict4, "bar":12}; > > -Revar > > >> On Mar 20, 2021, at 5:08 PM, Doug Moen <doug@moens.org <mailto:doug@moens.org>> wrote: >> >> Here are my comments about Revars dictionary proposal, compared to my record proposal. >> >> Records are restricted to having strings as keys. I use a std::ordered_map with a locale-independent key comparison function, which means that when you iterate over a record, you get the keys in alphabetical order: fully deterministic and very predictable. >> >> Dictionaries are more complicated. If you allow arbitrary values as keys, then do you use an ordered map or a hash map? What is the hash function? Are functions permitted as keys? How do you hash a function? How do you determine function equality? If you use the function address to compute the hash, then you get non-deterministic behaviour. The iteration order of a dictionary will be different on different machines, which could cause shapes to render differently on different machines. That can be nasty. Python solves this problem by preserving the insertion order of a dictionary using a more complicated data structure. Then, when you iterate over a dictionary, the iteration order is the insertion order. This gives you deterministic behaviour back. Even then, I think it is tricky to allow functions to be used as keys, because function equality is not well defined. >> >> Also, for iterating over a dictionary or record, I think it is better to give [key,value] pairs rather than just key values. >> >> On Sat, Mar 20, 2021, at 7:24 PM, Revar Desmera wrote: >>> Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD: >>> >>> // Dictionary declaration. >>> a = "fee"; b = "fie"; >>> dict1 = { >>> "foo": 23, // KEY : VALUE >>> "bar": 77, // String keys. >>> false: a, // Boolean keys. >>> 99 : 34, // Numeric keys. >>> 56 : "AZ", // Value can be any type. >>> "baz": 19, // Should lists, ranges, or functions be allowed for keys? >>> "bar": 65, // Overwrite previous key entry. >>> undef: 77, // Should undef keys be allowed? >>> "bla": undef, // Should undef values be allowed? >>> str("c","a","t") : b // Expressions usable for keys or values. >>> }; >>> >>> // Dictionary comprehension. >>> dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 }; >>> >>> // Extending/merging dictionaries. Later duplicate keys overwrite previous ones. >>> // Should lists merge into dictionaries using numeric positions as keys? >>> dict3 = concat(dict1, dict2, {"flee": 0}); >>> >>> // Getting dictionary values. >>> x = dict3["bar"]; >>> y = dict3[a]; >>> z = dict3["fit"]; // Returns undef for nonexistent keys. >>> >>> // Iterating a dictionary. Iterate keys in original declared order? >>> for (key = dict3) { >>> echo(key=key, val=dict3[key]); >>> } >>> This implementation would let me, among other things, rewrite my `vnf_validate()` (a validator for `polyhedron()` data.) to be FAR faster. >>> >>> - Revar >>> >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave%40lists.openscad.org> >>> >> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org <mailto:discuss-leave@lists.openscad.org>
C
caterpillar
Sun, Mar 21, 2021 2:17 AM
I implement a simple hashmap in[dotSCAD](https://github.com/JustinSDK/dotSCAD). https://openhome.cc/eGossip/OpenSCAD/lib3x-hashmap.html I have it in dotSCAD because something requires it, such as OpenSCAD Wave Function Collapse. https://twitter.com/caterpillar/status/1372372145901244416 <http://forum.openscad.org/file/t1825/EwujrGzVoAQlXYs.png> ----- https://openhome.cc -- Sent from: http://forum.openscad.org/
C
caterpillar
Sun, Mar 21, 2021 2:47 AM

RevarBat wrote

Here’s my first pass thoughts on an immutable table/dictionary data type
for OpenSCAD:

// Dictionary declaration.
a = "fee"; b = "fie";
dict1 = {
"foo": 23,    // KEY : VALUE
"bar": 77,    // String keys.
false: a,    // Boolean keys.
99  : 34,    // Numeric keys.
56  : "AZ",  // Value can be any type.
"baz": 19,    // Should lists, ranges, or functions be allowed for
keys?
"bar": 65,    // Overwrite previous key entry.
undef: 77,    // Should undef keys be allowed?
"bla": undef, // Should undef values be allowed?
str("c","a","t") : b  // Expressions usable for keys or values.
};

// Dictionary comprehension.
dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 };

// Extending/merging dictionaries. Later duplicate keys overwrite previous
ones.
// Should lists merge into dictionaries using numeric positions as keys?
dict3 = concat(dict1, dict2, {"flee": 0});

// Getting dictionary values.
x = dict3["bar"];
y = dict3[a];
z = dict3["fit"];  // Returns undef for nonexistent keys.

// Iterating a dictionary.  Iterate keys in original declared order?
for (key = dict3) {
echo(key=key, val=dict3[key]);
}

This implementation would let me, among other things, rewrite my
vnf_validate() (a validator for polyhedron() data.) to be FAR faster.

  • Revar

OpenSCAD mailing list
To unsubscribe send an email to

discuss-leave@.openscad

A workaround...

// require https://github.com/JustinSDK/dotSCAD
use <util/map/hashmap.scad>;
use <util/map/hashmap_entries.scad>;
use <util/map/hashmap_get.scad>;
use <util/map/hashmap_keys.scad>;

a = "fee";
b = "fie";

m1 = hashmap([
["foo", 23],
["bar", 77],
[false, a],
[99  , 34],
[56  , "AZ"],
["baz", 19],
["bar", 65],
[undef, 77],
["bla", undef],
[str("c","a","t"), b]
]);

m2 = hashmap([
for(i =[0:100]) if(i%2 == 0) [i, i * 5]
]);

m3 = hashmap(concat(hashmap_entries(m1), hashmap_entries(m2)));

x = hashmap_get(m3, "bar");
y = hashmap_get(m3, a);
z = hashmap_get(m3, "fit");

for (key = hashmap_keys(m3)) {
echo(key=key, val=hashmap_get(m3, key));
}


https://openhome.cc

Sent from: http://forum.openscad.org/

RevarBat wrote > Here’s my first pass thoughts on an immutable table/dictionary data type > for OpenSCAD: > > // Dictionary declaration. > a = "fee"; b = "fie"; > dict1 = { > "foo": 23, // KEY : VALUE > "bar": 77, // String keys. > false: a, // Boolean keys. > 99 : 34, // Numeric keys. > 56 : "AZ", // Value can be any type. > "baz": 19, // Should lists, ranges, or functions be allowed for > keys? > "bar": 65, // Overwrite previous key entry. > undef: 77, // Should undef keys be allowed? > "bla": undef, // Should undef values be allowed? > str("c","a","t") : b // Expressions usable for keys or values. > }; > > // Dictionary comprehension. > dict2 = { for (i=[0:100]) if(i%2==0) i : i*5 }; > > // Extending/merging dictionaries. Later duplicate keys overwrite previous > ones. > // Should lists merge into dictionaries using numeric positions as keys? > dict3 = concat(dict1, dict2, {"flee": 0}); > > // Getting dictionary values. > x = dict3["bar"]; > y = dict3[a]; > z = dict3["fit"]; // Returns undef for nonexistent keys. > > // Iterating a dictionary. Iterate keys in original declared order? > for (key = dict3) { > echo(key=key, val=dict3[key]); > } > > This implementation would let me, among other things, rewrite my > `vnf_validate()` (a validator for `polyhedron()` data.) to be FAR faster. > > - Revar > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to > discuss-leave@.openscad A workaround... // require https://github.com/JustinSDK/dotSCAD use <util/map/hashmap.scad>; use <util/map/hashmap_entries.scad>; use <util/map/hashmap_get.scad>; use <util/map/hashmap_keys.scad>; a = "fee"; b = "fie"; m1 = hashmap([ ["foo", 23], ["bar", 77], [false, a], [99 , 34], [56 , "AZ"], ["baz", 19], ["bar", 65], [undef, 77], ["bla", undef], [str("c","a","t"), b] ]); m2 = hashmap([ for(i =[0:100]) if(i%2 == 0) [i, i * 5] ]); m3 = hashmap(concat(hashmap_entries(m1), hashmap_entries(m2))); x = hashmap_get(m3, "bar"); y = hashmap_get(m3, a); z = hashmap_get(m3, "fit"); for (key = hashmap_keys(m3)) { echo(key=key, val=hashmap_get(m3, key)); } ----- https://openhome.cc -- Sent from: http://forum.openscad.org/