See code below:
set k [list]
foreach i [list 1 2] {
lappend k [ list "hey" [ list "ho" [ list $i ] ] ]
}
puts [ join $k ",and,"]
exit
The result is:
hey {ho 1},and,hey {ho 2}
But I expected the result to look like:
hey {ho {1}},and,hey {ho {2}}
Any ideas why is that so?
Thanks.
If anyone of the list command's arguments are more than elements one, then only that corresponding indexed element's return value will have the braced list form.
% list a b c; # All list args are having only single element
a b c
% list "a" "b" "c"; # Same as above
a b c
% list {a} {b} {c}; # Same again...
a b c
% list "a b" c d; # Here, 1st arg is having 2 elements.
{a b} c d
%
Tcl's wiki page already mentioned about bizarre behavior of the nested lists in only one case, which is
% list [list [list x]]
x
It means that Tcl lists alone cannot be used to represent ALL kinds of data structures, as Tcl lists magically collapse when it's a series of nested lists with the terminal list having only a single bare word that requires no escaping.
Update :
More importantly, if the arg is having a space in it,
% list "x "
{x }
% list "x"
x
%
Since the space has to be considered as well, Tcl has no other way, but to enclose the braces.
Related
I was wondering about how I could completely flatten lists and things that contain them. Among other things, I came up with this solution that slips things that have more than one element and puts them back, or takes things with one element after slipping it.
This is a bit different than How do I “flatten” a list of lists in perl 6?, which doesn't completely flat because the task is to restructure.
But, maybe there's a better way.
my #a = 'a', ('b', 'c' );
my #b = ('d',), 'e', 'f', #a;
my #c = 'x', $( 'y', 'z' ), 'w';
my #ab = #a, #b, #c;
say "ab: ", #ab;
my #f = #ab;
#f = gather {
while #f {
#f[0].elems == 1 ??
take #f.shift.Slip
!!
#f.unshift( #f.shift.Slip )
}
}
say "f: ", #f;
This gives:
ab: [[a (b c)] [(d) e f [a (b c)]] [x (y z) w]]
f: [a b c d e f a b c x y z w]
Curiously, I also read some python answers:
Making a flat list out of list of lists in Python
How flatten a list of lists one step
flatten list of lists of lists to a list of lists
itertools.chain(*sublist) look interesting, but the answers were either recursive or limited to two levels from hard-coding. The functional languages were recursive in the source code, but I expected that.
Unfortunately there's no direct built-in that completely flattens a data structure even when sub-lists are wrapped in item containers.
Some possible solutions:
Gather/take
You've already come up with a solution like this, but deepmap can take care of all the tree iteration logic to simplify it. Its callback is called once for every leaf node of the data structure, so using take as the callback means that gather will collect a flat list of the leaf values:
sub reallyflat (+#list) { gather #list.deepmap: *.take }
Custom recursive function
You could use a subroutine like this to recursively slip lists into their parent:
multi reallyflat (#list) { #list.map: { slip reallyflat $_ } }
multi reallyflat (\leaf) { leaf }
Another approach would be to recursively apply <> to sub-lists to free them of any item containers they're wrapped in, and then call flat on the result:
sub reallyflat (+#list) {
flat do for #list {
when Iterable { reallyflat $_<> }
default { $_ }
}
}
Multi-dimensional array indexing
The postcircumfix [ ] operator can be used with a multi-dimensional subscript to get a flat list of leaf nodes up to a certain depth, though unfortunately the "infinite depth" version is not yet implemented:
say #ab[*;*]; # (a (b c) (d) e f [a (b c)] x (y z) w)
say #ab[*;*;*]; # (a b c d e f a (b c) x y z w)
say #ab[*;*;*;*]; # (a b c d e f a b c x y z w)
say #ab[**]; # HyperWhatever in array index not yet implemented. Sorry.
Still, if you know the maximum depth of your data structure this is a viable solution.
Avoiding containerization
The built-in flat function can flatten a deeply nested lists of lists just fine. The problem is just that it doesn't descend into item containers (Scalars). Common sources of unintentional item containers in nested lists are:
An Array (but not List) wraps each of its elements in a fresh item container, no matter if it had one before.
How to avoid: Use Lists of Lists instead of Arrays of Arrays, if you don't need the mutability that Array provides. Binding with := can be used instead of assignment, to store a List in a # variable without turning it into an Array:
my #a := 'a', ('b', 'c' );
my #b := ('d',), 'e', 'f', #a;
say flat #b; # (d e f a b c)
$ variables are item containers.
How to avoid: When storing a list in a $ variable and then inserting it as an element into another list, use <> to decontainerize it. The parent list's container can also be bypassed using | when passing it to flat:
my $a = (3, 4, 5);
my $b = (1, 2, $a<>, 6);
say flat |$b; # (1 2 3 4 5 6)
I'm unaware of a built-in way to do so, though there very well might be (and if not, there probably should be).
The best I could come up with on short notice is this:
gather #ab.deepmap(*.take)
I'm not sure how gather/take interacts with the potentially parallelized evaluation of hyper operators, so the following alternative might not be safe to use, in particular if you care about element order:
gather #ab>>.take
You can put the code into square brackets if you need an array or reify it into a list via .list.
Lastly, this is the first solution rewitten as a retro-style subroutine:
sub deepflat { gather deepmap &take, #_ }
I'm familiar with finding the intersection of two lists, however, I wanted to find the union of two lists in tcl (while eliminating duplicates). I do have a working copy of this code, but I'm not sure it is robust enough for any kind/number of lists and am hence looking for a better solution.
Any help or ideas are appreciated.
If you treat lists as sets, so you don't worry about order if the items, you could just sort the joined list:
set union [lsort -unique [list {*}$list1 {*}$list2]]
Tclx provides a union command:
% info patchlevel
8.5.9
% set a [list a b c]
a b c
% set b [list a d e]
a d e
% package require Tclx
8.4
% union $a $b
a b c d e
%
% union
wrong # args: should be "union lista listb"
%
One way that doesn't need sorting is to use dictionary keys as sets:
% set a [list a b c]
a b c
% set b [list a d e]
a d e
% set d {}
% foreach k $a { dict set d $k . }
% foreach k $b { dict set d $k . }
% set c [dict keys $d]
a b c d e
This has the advantage of not needing to sort at all, which can help quite a lot with large input sets.
I was wondering about how I could completely flatten lists and things that contain them. Among other things, I came up with this solution that slips things that have more than one element and puts them back, or takes things with one element after slipping it.
This is a bit different than How do I “flatten” a list of lists in perl 6?, which doesn't completely flat because the task is to restructure.
But, maybe there's a better way.
my #a = 'a', ('b', 'c' );
my #b = ('d',), 'e', 'f', #a;
my #c = 'x', $( 'y', 'z' ), 'w';
my #ab = #a, #b, #c;
say "ab: ", #ab;
my #f = #ab;
#f = gather {
while #f {
#f[0].elems == 1 ??
take #f.shift.Slip
!!
#f.unshift( #f.shift.Slip )
}
}
say "f: ", #f;
This gives:
ab: [[a (b c)] [(d) e f [a (b c)]] [x (y z) w]]
f: [a b c d e f a b c x y z w]
Curiously, I also read some python answers:
Making a flat list out of list of lists in Python
How flatten a list of lists one step
flatten list of lists of lists to a list of lists
itertools.chain(*sublist) look interesting, but the answers were either recursive or limited to two levels from hard-coding. The functional languages were recursive in the source code, but I expected that.
Unfortunately there's no direct built-in that completely flattens a data structure even when sub-lists are wrapped in item containers.
Some possible solutions:
Gather/take
You've already come up with a solution like this, but deepmap can take care of all the tree iteration logic to simplify it. Its callback is called once for every leaf node of the data structure, so using take as the callback means that gather will collect a flat list of the leaf values:
sub reallyflat (+#list) { gather #list.deepmap: *.take }
Custom recursive function
You could use a subroutine like this to recursively slip lists into their parent:
multi reallyflat (#list) { #list.map: { slip reallyflat $_ } }
multi reallyflat (\leaf) { leaf }
Another approach would be to recursively apply <> to sub-lists to free them of any item containers they're wrapped in, and then call flat on the result:
sub reallyflat (+#list) {
flat do for #list {
when Iterable { reallyflat $_<> }
default { $_ }
}
}
Multi-dimensional array indexing
The postcircumfix [ ] operator can be used with a multi-dimensional subscript to get a flat list of leaf nodes up to a certain depth, though unfortunately the "infinite depth" version is not yet implemented:
say #ab[*;*]; # (a (b c) (d) e f [a (b c)] x (y z) w)
say #ab[*;*;*]; # (a b c d e f a (b c) x y z w)
say #ab[*;*;*;*]; # (a b c d e f a b c x y z w)
say #ab[**]; # HyperWhatever in array index not yet implemented. Sorry.
Still, if you know the maximum depth of your data structure this is a viable solution.
Avoiding containerization
The built-in flat function can flatten a deeply nested lists of lists just fine. The problem is just that it doesn't descend into item containers (Scalars). Common sources of unintentional item containers in nested lists are:
An Array (but not List) wraps each of its elements in a fresh item container, no matter if it had one before.
How to avoid: Use Lists of Lists instead of Arrays of Arrays, if you don't need the mutability that Array provides. Binding with := can be used instead of assignment, to store a List in a # variable without turning it into an Array:
my #a := 'a', ('b', 'c' );
my #b := ('d',), 'e', 'f', #a;
say flat #b; # (d e f a b c)
$ variables are item containers.
How to avoid: When storing a list in a $ variable and then inserting it as an element into another list, use <> to decontainerize it. The parent list's container can also be bypassed using | when passing it to flat:
my $a = (3, 4, 5);
my $b = (1, 2, $a<>, 6);
say flat |$b; # (1 2 3 4 5 6)
I'm unaware of a built-in way to do so, though there very well might be (and if not, there probably should be).
The best I could come up with on short notice is this:
gather #ab.deepmap(*.take)
I'm not sure how gather/take interacts with the potentially parallelized evaluation of hyper operators, so the following alternative might not be safe to use, in particular if you care about element order:
gather #ab>>.take
You can put the code into square brackets if you need an array or reify it into a list via .list.
Lastly, this is the first solution rewitten as a retro-style subroutine:
sub deepflat { gather deepmap &take, #_ }
I have a "flat" Tcl list. Now I want to append a new element as a child to one of the existing elements. How can I do this?
This is what I tried:
[ lindex $flights $i ] [ lindex $flight 0 ] ]
I try to add an element form the list "flight" to an element of the list "flights". The element $i in the flights list already exists.
I might be running against Tcl syntax as I'm new to Tcl.
Thanks for your help.
You can use lset to replace an element of your list with a new list. http://www.tcl.tk/man/tcl8.5/TclCmd/lset.htm The first element of the new list will be the old element, the 2nd element will be its child. Here's an example:
% set flights [list a b c d e]
a b c d e
% set i 1
1
% lset flights $i [list b child]
a {b child} c d e
% lindex $flights 1
b child
% lindex [lindex $flights 1] 1
child
% lindex [lindex $flights 1] 0
b
I have a c function (dbread) that reads 'fields' from a 'database'. Most of those fields are single valued; but sometimes they are multi-valued. So I had c code that said
if valcount == 1
return string
else
make list
foreach item in vals
append to list
return list
Because i thought most of the time people want a scalar.
However doing this leads to some odd parsing errors. Specifically if I want to add a value
set l [dbread x] # get current c value
lappend l "extra value" # add a value
dbwrite x {*}$l # set it back to db
If x has single value and that value contains spaces the lappend parses wrong. I get a list with 3 items not 2. I see that this is because it is passed something that is not a list and it parses it to a list and sees 2 items.
set l "foo bar"
lappend l "next val" # string l is parsed into list -> [list foo bar]
so I end up with [list foo bar {next val}]
Anyway, the solution is to make dbread always return a list - even if there is only one item. My question is - is there any downside to this? Are there surprises lurking for the 90% case where people would expect a scalar
The alternative would be to do my own lappend that checks for llength == 1 and special cases it
I think it's cleaner to have an API which always returns a list of results, be it one result or many. Then there's no special casing needed.
No downside, only upside.
Think about it, what if you move away from returning a single scalar and have a case in the future where you're returning a single value that happens to be a string with a space in it. If you didn't construct a list of that single value, you'd treat it as two values (because Tcl would shimmer the string into a list of two things). By always constructing a list of return values, all the code using your API will handle this correctly.
Just because Tcl doesn't have strict typing doesn't mean it's good style to return different types at different times.
One of the approaches I have taken in the past (when the data for each row could contain nulls or empty strings), was to use a list of lists of list:
{{a b} {c d}} ;# two rows, each with two elements
{{{} b} {c d}} ;# two rows, first element of first row is null
;# llength [lindex [lindex {{{} b} {c d}} 0] 0] -> 0
{ { {{}} b } { c d } }
;# two rows, first element of first row is the empty string
;# llength [lindex [lindex {{{{}} b} {c d}} 0] 0] -> 1
It looks complicated, but it's really not if you treat the actual data items as an opaque data structure and add accessors to use it:
foreach row $db_result {
foreach element $row {
if {[db_isnull $element]} {
puts "null"
} elseif {![string length [db_value $element]]} {
puts "empty string"
} else {
puts [db_value $element]
}
}
}
Admittedly, far more complicated than you're looking for, but I thought it worth mentioning.