Searching from list in tcl - list

I am trying to search whether the list elements are not equal to 0 and if they are not then return the pointer to individual element or elements and append it to a list.
for example
list1 has {a b c d e}
I use some api command to extract values of these elements to a new list
set list2 ""
foreach element $list1 {
lappend list2 [api] # Api is a procedure that queries a,b,c etc of list1 and then stores the value in list2.
}
$> puts $list2
$> {0 0 4 0 1}
this list2 can haave any number of elements in the list and the order is not fixed, so i want to develop something that will work for anything. For example list2 can be {0 0 0} or {0 0 0 0 1 2 0} etc.
Now coming back to my original question from this list first of all using lsearch or ay other command i want to detect whether any elements is 0 or not. If it is not then append it to a new list.
set nonzero_list ""
# code below is wrong, I am trying to explain only what I a trying to achieve.
if {[lsearch $list2 0] != -1} {
lappend nonzero_list ["element which is not zero, in our case it is 2nd and 4th element"]
}
Finally my output should show as :
$> puts $nonzero_list
{c e} # Note: these are not actual individual elements of list2 but these are values of list1 to which they are associated to
Hope am able to understand the question correctly.
Thanks in advance.
/For Hai Vu : updated question below
Your proc "filterNonZero" works fine. My Goal is to append the values of these numbers from the output of this proc to the new list. So, in the example snippet that you provided list 2 will get {1 2 3 5} that is correct but I want a new list list3 that belongs to corresponding values of these elements. For Example :
set list0 {a b c d e f g i}
do some processing and obtain list2 (i know what to do you here, use my API) -->
puts $list2
{0 1 2 0 3 0 0 5 0}
then use your proc to obtain list 2 ->
set list2 [filterNonZero $list1]
puts $list2
{ 1 2 3 5 }
--> Now do some processing and get the final result list3 ( i dont know how to do this part) -->
{b c e h}

The way to filter is to:
set filteredList {}
foreach item $inputList {
if {[api $item] != 0} {
lappend filteredList $item,$val
}
}
In Tcl 8.6, I'd write this (because continue skips the collecting of the result of the body):
set filteredList [lmap item $inputList {
if {[api $item] == 0} continue
set item
}]
I am always assuming that you pass the item into the API as an argument. That's highly advised!

If I understand your question correctly, you want to take as input a list and return a list of all non-zero items. Here is one way to do it.
# Given list1 which contains any number of integers, we want to return a
# list of all items that is non-zero
set list1 {0 1 2 0 3 0 0 5 0}
set list2 {}
foreach item $list1 {
if {$item != 0} {
lappend list2 $item
}
}
# At this point, list2 contains all the non-zero items from list1
puts "List1: $list1"
puts "List2: $list2"
Output:
List1: 0 1 2 0 3 0 0 5 0
List2: 1 2 3 5
You can turn this into a proc (a procedure):
proc filterNonZero {theList} {
set newList {}
foreach item $theList {
if {$item != 0} {
lappend newList $item
}
}
return $newList
}
set list1 {0 1 2 0 3 0 0 5 0}
set list2 [filterNonZero $list1]; # list2 = {1 2 3 5}
Another way is to use the struct::list package to filter out what you want:
package require struct::list
set list1 {0 1 2 0 3 0 0 5 0}
set list2 [struct::list filterfor item $list1 {$item != 0}]
puts "\nList2: $list2"
Update
Let me see if I understand the problem correctly:
list1 | a b c d e f g h i
list2 | 0 1 2 0 3 0 0 5 0
result | b c e h
If this is what you want, the solution is simple:
# These are the input
set list1 {a b c d e f g h i}
set list2 {0 1 2 0 3 0 0 5 0}
set result {}
foreach item $list1 selector $list2 {
if {$selector != 0} {
lappend result $item
}
}
puts "result = $result"
Discussion
In this case, list1 is the original list; list2 is what you get from calling [API].
For each item in list2, if the item is not zero, you want to add to the result the correspond item in list1
The foreach loop does that: it cycles through both lists at the same time.
You might want to review Donal Fellows' solution, his does not require the creation of list2, which saves some steps.

Related

How to giving variable spacing between word in tcl?

I just started learning Tcl, my main objective to print the two lists like this:
List1 List2
1 A
2 B
3 C
4 D
You can use a loop and format:
set List1 {1 2 3 4}
set List2 {A B C D}
puts [format %-10s%-10s List1 List2]
foreach i $List1 j $List2 {
puts [format %-10s%-10s $i $j]
}
Output from the above:
List1 List2
1 A
2 B
3 C
4 D
Note: In the above, %-10s roughly means left align the text within a column of 10 spaces

Split a list of numbers into smaller list based on a range in TCL

I have a sorted list of numbers and I am trying to split the list into smaller lists based on range of 50 and find the average in TCL.
For eg: set xlist {1 2 3 4 5 ...50 51 52 ... 100 ... 101 102}
split lists: {1 ... 50} { 51 .. 100} {101 102}
result: sum(1:50)/50; sum(51:100)/50; sum(101:102)/2
The lrange command is the core of what you need here. Combined with a for loop, that'll give you the splitting that you're after.
proc splitByCount {list count} {
set result {}
for {set i 0} {$i < [llength $list]} {incr i $count} {
lappend result [lrange $list $i [expr {$i + $count - 1}]]
}
return $result
}
Testing that interactively (with a smaller input dataset) looks good to me:
% splitByCount {a b c d e f g h i j k l} 5
{a b c d e} {f g h i j} {k l}
The rest of what you want is a trivial application of lmap and tcl::mathop::+ (the command form of the + expression operator).
set sums [lmap sublist [splitByCount $inputList 50] {
expr {[tcl::mathop::+ {*}$sublist] / double([llength $sublist])}
}]
We can make that slightly neater by defining a custom function:
proc tcl::mathfunc::average {list} {expr {
[tcl::mathop::+ 0.0 {*}$list] / [llength $list]
}}
set sums [lmap sublist [splitByCount $inputList 50] {expr {
average($sublist)
}}]
(I've moved the expr command to the previous line in the two cases so that I can pretend that the body of the procedure/lmap is an expression instead of a script.)

Get items in specific index list of lists

I have a key-value list such as:
set x {{a 1} {b 2} {c 3}}
I need to extract all the items in index=1 in all sub-lists to get:
{1 2 3}
You can use this:
$ set y {}
$ foreach sublist $x { lappend y [lindex $sublist 1]}
$ puts $y
1 2 3
A solution for TCL 8.6 or newer:
Use lmap to iterate through x without saving value anywhere ,in one-line:
$ lmap sublist $x {lindex $sublist 1}
References:
lmap,tcl.tk
I've used the following function:
proc MapList {Var List Script} {
if {![llength $List]} {return $List}
upvar 1 $Var Item
foreach Item $List {lappend Res [uplevel 1 $Script]}
return $Res
}
And used it like this:
MapList Arg $x {lindex $Arg 1}
One solution is to conscript the dict values command:
dict values [concat {*}{{a 1} {b 2} {c 3}}]
How this works: the dict values collects a list consisting of every other item (starting from the second) in another list. This is intended to be used on dictionaries, but since dictionaries are basically just even-sized lists, it works on any even-sized list, with one caveat: if any key appears more than once, the result of dict values will only contain the last value associated with that key.
A list consisting of two-item sublists can easily be transformed into an even-sized list by passing the sublists individually as arguments to concat.
Another way is to traverse the list using one of the methods mentioned in the other answers, or maybe like this:
set res {}
for {set i 0} {$i < [llength $x]} {incr i} {
lappend res [lindex $x $i 1]
}
set res
This is similar to
set res {}
foreach item $x {
lappend res [lindex $item 1]
}
set res
(or the corresponding lmap item $x {lindex $item 1})
but does provide the option to 1) start at an index ≠ 0, 2) end before the end of the list, and 3) traverse the list by two (or more) item steps.
Documentation: concat, dict, for, foreach, incr, lappend, lindex, llength, lmap, set

Adding a nested element to a "flat" list

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

TCL remove an element from a list

How te remove an element from TCL list say:
which has index = 4
which has value = "aa"
I have Googled and have not found any built-in function yet.
set mylist {a b c}
puts $mylist
a b c
Remove by index
set mylist [lreplace $mylist 2 2]
puts $mylist
a b
Remove by value
set idx [lsearch $mylist "b"]
set mylist [lreplace $mylist $idx $idx]
puts $mylist
a
The other way to remove an element is to filter it out. This Tcl 8.5 technique differs from the lsearch&lreplace method mentioned elsewhere in that it removes all of a given element from the list.
set stripped [lsearch -inline -all -not -exact $inputList $elemToRemove]
What it doesn't do is search through nested lists. That's a consequence of Tcl not putting effort into understanding your data structures too deeply. (You can tell it to search by comparing specific elements of the sublists though, via the -index option.)
Lets say you want to replace element "b":
% set L {a b c d}
a b c d
You replace the first element 1 and last element 1 by nothing:
% lreplace $L 1 1
a c d
regsub may also be suitable to remove a value from a list.
set mylist {a b c}
puts $mylist
a b c
regsub b $mylist "" mylist
puts $mylist
a c
llength $mylist
2
Just wrapped up what others have done
proc _lremove {listName val {byval false}} {
upvar $listName list
if {$byval} {
set list [lsearch -all -inline -not $list $val]
} else {
set list [lreplace $list $val $val]
}
return $list
}
Then call with
Inline edit, list lappend
set output [list 1 2 3 20]
_lremove output 0
echo $output
>> 2 3 20
Set output like lreplace/lsearch
set output [list 1 2 3 20]
echo [_lremove output 0]
>> 2 3 20
Remove by value
set output [list 1 2 3 20]
echo [_lremove output 3 true]
>> 1 2 20
Remove by value with wildcar
set output [list 1 2 3 20]
echo [_lremove output "2*" true]
>> 1 3
You can also try like this :
set i 0
set myl [list a b c d e f]
foreach el $myl {
if {$el in {a b e f}} {
set myl [lreplace $myl $i $i]
} else {
incr i
}
}
set myl
There are 2 easy ways.
# index
set mylist "a c b"
set mylist [lreplace $mylist 2 2]
puts $mylist
a b
# value
set idx [lsearch $mylist "b"]
set mylist [lreplace $mylist $idx $idx]
puts $mylist
a