Here’s my first pass thoughts on an immutable table/dictionary data type for OpenSCAD:
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 [hidden email] |
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:
_______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
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 at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
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
_______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by doug.moen
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.
-Revar
_______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
<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:
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.polar = { rho: 10, theta:0, phi:45 }; 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: 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" ] }?th = "theta"; polar = { rho: 10, [th, 10], phi: 45 }; 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:
No. Not worth the hassle. But if keys can be expressions, they
could be allowed in the future.
Yes. But mostly to support some sort of concat-like expression.
Yes.
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.
Should also allow dict3.bar. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hmm. What about a declaration syntax that allows key literals preceded by ‘.’:
_______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
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
_______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
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 ![]() Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
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)); } Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
On 2021-03-21 00:24, 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. >> }; For what it is worth, most languages have these kind of look-up tables. AngelCAD has both 'map' and 'dictionary': A 'map' is a look-up table where the key and value types can be anything, but all the entries have the same key and value types: // type-safe map of integers map<string,int> dict1; dict1.insert("foo",23); dict1.insert("bar",77); Since geometric objects also have types, you can include them in a map. If the abstract shape@ type is used in the declaration, you can have a mixture of 2d or 3d objects in the same map (the @ denotes a handle): // type-safe map of abstract 2d or 3d shapes map<string,shape@> dict2; dict2.insert("sphere",sphere(33)); dict2.insert("circle",circle(50)); A slightly different look-up table is the 'dictionary' type, which uses only strings as keys, but the value type can be anything and can vary between entries in the dictionary: // dictionary of mixed types dictionary dict3 = { {"foo", 23} ,{"bar", 77} ,{"sphere", @sphere(33)} ,{"circle", @circle(50)} }; Personally, I find the map more useful than dictionary. Carsten Arnholm _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
I did write that the dumb search could be fixed easily. To prove it, a version that uses quicksort (average O(n log n) ) during construction and a binary search O(log n) during lookup is below. Again, this is quick and dirty stuff. I'm sure there are ways to do that with better performance characteristics. It's true that a "table feature" would make for faster implementation, but implementing the data structure in OpenScad means that the end user can (at least in principle) modify the behavior of the list themselves. So if someone decides that a dictionary miss should be an assert(false) instead of an undef, then they can do that without involving the OpenScad devs or waiting for a new version to come out. function last(list)= let( listcheck=assert(is_list(list)) ) len(list)-1 ; function index_list_find(list,index,start=0,end=undef)= let( last=(is_undef(end)?last(list):end), pivot=ceil( (start+last)/2 ) ) (list[pivot][0]==index)? list[pivot][1] :(list[pivot][0]<index)? (pivot==last)? undef : index_list_find(list,index,start=pivot+1,end=last) ://(list[pivot][0]>index) (pivot==start)? undef : index_list_find(list,index,start=start,end=pivot-1) ; function index_quicksort(arr) = !(len(arr)>0) ? [] : let( pivot = arr[floor(len(arr)/2)], lesser = [ for (y = arr) if (y[0] < pivot[0]) y ], equal = [ for (y = arr) if (y[0] == pivot[0]) y ], greater = [ for (y = arr) if (y[0] > pivot[0]) y ] ) concat( index_quicksort(lesser), equal, index_quicksort(greater) ); 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=index_quicksort( concat( nlist, index_list_from_pairs( filter=function(x) (is_num(x)), pairlist=pairs ) ) ) , slist=index_quicksort( 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")); function last(list)= let( listcheck=assert(is_list(list)) ) len(list)-1 ; function index_list_find(list,index,start=0,end=undef)= let( last=(is_undef(end)?last(list):end), pivot=ceil( (start+last)/2 ) ) (list[pivot][0]==index)? list[pivot][1] :(list[pivot][0]<index)? (pivot==last)? undef : index_list_find(list,index,start=pivot+1,end=last) ://(list[pivot][0]>index) (pivot==start)? undef : index_list_find(list,index,start=start,end=pivot-1) ; function index_quicksort(arr) = !(len(arr)>0) ? [] : let( pivot = arr[floor(len(arr)/2)], lesser = [ for (y = arr) if (y[0] < pivot[0]) y ], equal = [ for (y = arr) if (y[0] == pivot[0]) y ], greater = [ for (y = arr) if (y[0] > pivot[0]) y ] ) concat( index_quicksort(lesser), equal, index_quicksort(greater) ); 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=index_quicksort( concat( nlist, index_list_from_pairs( filter=function(x) (is_num(x)), pairlist=pairs ) ) ) , slist=index_quicksort( 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("keys",test2(["keys"])); echo(test2("foo")); echo("keys",test2(["keys"])); echo("values",test2(["values"])); Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by doug.moen
Strings as keys is plenty enough for me. You can easily encode any other datatype as a string anyway. Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Free forum by Nabble | Edit this page |