Retrieve selective items from list - list

Is there a way to get items from a list according to some function?
I know there is a way to get items by regular expression by using lsearch -regexp but It's not what I need.

In Tcl 8.6, you can use the lmap command to do this by using continue to skip the items you don't want (or break to indicate that you've done enough processing):
set items {0 1 2 3 4 5 6 7 8 9 10}
set filtered [lmap i $items {if {$i==sqrt($i)**2} {set i} else continue}]
# Result: 0 1 4 9
This can be obviously extended into a procedure that takes a lambda term and a list.
proc filter {list lambda} {
lmap i $list {
if {[apply $lambda $i]} {
set i
} else {
continue
}
}
}
set filtered [filter $items {i { expr {$i == sqrt($i)**2} }}]
It's possible to do something similar in Tcl 8.5 with foreach though you'll need to do more work yourself to build the list of result items with lappend…
proc filter {list lambda} {
set result {}
foreach i $list {
if {[apply $lambda $i]} {
lappend result $i
}
}
return $result
}
Usage is identical. (Tcl 8.4 and before — now unsupported — don't support the apply command.)

Related

How to extract only two fields using Regular Expression

In TCL I am writing the regular expression for below output:
Output args is
packet-filter 0
identifier 0
direction bidirectional
network-ip 10.7.98.231/32
ue-port-start 0
ue-port-end 0
nw-port-start 0
nw-port-end 0
protocol 1
precedence 0
packet-filter 1
identifier 1
direction uplink
network-ip 10.7.98.231/32
ue-port-start 0
ue-port-end 0
nw-port-start 0
nw-port-end 0
protocol 1
precedence 0
Output of my Regular Expression : regexp -all -inline {direction\s+(\S+)} $args is
{direction bidirectional} bidirectional {direction uplink} uplink
I need to extract the direction value which is bidirectional and uplink
Any suggestion ?
For the current case, where the captured substrings are chunks of non-whitespace text, you may re-build the output checking if each item has length set to 1:
set results [regexp -all -inline {direction\s+(\S+)} $args]
set res {}
foreach item $results {
if {[llength $item] == 1} {
lappend res $item
}
}
Then, $res will only hold bidirectional and uplink.
See the Tcl demo.
For a more generic case, you may use
set res {}
foreach {whole capture1} $results {
lappend res $capture1
}
See this Tcl demo
You may add more captureX arguments to accommodate all the capturing group values returned by your regex.
You simply need a loop or something equivalent. If you need to work on each direction individually, a foreach loop is appropriate:
set results [regexp -all -inline {direction\s+(\S+)} $args]
foreach {main sub} $results {
puts $sub
}
# bidirectional
# uplink
Or if you need the list of directions, then lmap sounds appropriate:
set directions [lmap {main sub} $results {set sub}]
# bidirectional uplink
The regexp is not absolutely necessary, you may process the value of args into a dictionary:
set d [dict create]
foreach {k v} $args {
dict lappend d $k $v
}
puts [dict get $d direction]

Identify curly bracket and multiple instances in list using TCL

I'm reading a large file and I'm only interested in small part of the file as shown below.
TC.0.Type = Bob 1
TC.1.Type = Mark 1
TC.2.Type =
TC.3.Type =
TC.4.Type = Fred 1
TC.5.Type =
TC.6.Type =
TC.7.Type =
TC.8.Type =
TC.9.Type = Fred 1
I've created a variable that is now holds this information
data = "{Bob 1} {Mark 1} {} {} {Fred 1} {} {} {} {} {Fred 1}"
TC is always between 0-9, so length is known.
What I would like to do is:
1) If there are multiple instances of "Fred 1" and delete it.
2) Find the first empty slot and determine the index.
Question 1)
Is it typical to get brackets when using lappend? I expected this to be only in the case of empty fields
set data ""
for {set j 0} {$j < 10} {incr j} {
lappend data $fromfile
}
puts "Data in list = $data"
Question 2) I've even tried using regexp to pick out empty but don't seem to be successful.
Find empty field {}
set j 0
for {set i 0} {$i < $ldata} {incr i} {
# set nline [split $data "\s"]
# puts "data ($i) = $nline"
if {[regexp {\{.*\}} $data]} {
puts " Found {}"
incr j
puts "j = $j"
}
}
Find field with name e.g. Bob 1
for {set i 0} {$i < $ldata} {incr i} {
if {[regexp {\{.*[a-zA-Z0-9]\}} $data]} {
puts " Found something with names"
}
}
Would appreciate if someone can advice and guide.
The lsearch command is going to be tremendously useful for what you are doing, especially with the -all option.
set data "{Bob 1} {Mark 1} {} {} {Fred 1} {} {} {} {} {Fred 1}"
puts [lsearch -all -exact $data "Fred 1"]
# ==> 4 9
We can also use it to remove specific values:
puts [lsearch -all -inline -exact -not $data "Fred 1"]
# ==> {Bob 1} {Mark 1} {} {} {} {} {} {}
To find the first empty slot, we just do:
puts [lsearch -exact $data ""]
# ==> 2
We most definitely would expect braces back from list operations; that's how empty list elements are expressed.

regexp loop to find first instance of each query TCL

I have a list variable containing some values:
lappend list {query1}
{query2}
{query3}
And some data in file1 with parts of them matching the values above
query1 first data
query1 different data
query1 different data
query2 another data
query2 random data
query3 data something
query3 last data
How do I create a regexp loop that catches only the first instance found of each query and prints them out? In this case the output would be:
query1 first data
query2 another data
query3 data something
Attempted code to produce the output
set readFile1 [open file1.txt r]
while { [gets $readFile1 data] > -1 } {
for { set n 0 } { $n < [llength $list] } { incr n } {
if { [regexp "[lindex $list $n]" $data] } {
puts $data
}
}
}
close $readFile1
I tried using a for loop while reading the data from a file, but it seems to catch all values even if the -all option is not used.
You can either read the file as a whole into a variable using read command, if the text file is smaller in size. Apply the regexp for the content and we can extract the required data.
set list {query1 query2 query3}
set fp [open file1.txt r]
set data [read $fp]
close $fp
foreach elem $list {
# '-line' flag will enable the line sensitive matching
if {[regexp -line "$elem.+" $data line]} {
puts $line
}
}
If suppose the file too large to hold or if you consider run-time memory usage, then go ahead with the reading the content line by line. There we need to have control on what already matched for which you can keep an array to maintain whether the first occurrence of any query matched or not.
set list {query1 query2 query3}
set fp [open file1.txt r]
array set first_occurence {}
while {[gets $fp line]!=-1} {
foreach elem $list {
if {[info exists first_occurence($elem)]} {
continue
}
if {[regexp $elem $line]} {
set first_occurence($elem) 1
puts $line
}
}
}
close $fp
Reference : regexp
package require fileutil
set queries {query1 query2 query3}
set result {}
::fileutil::foreachLine line file1.txt {
foreach query $queries {
if {![dict exists $result $query]} {
if {[regexp $query $line]} {
dict set result $query $line
puts $line
}
}
}
}
The trick here is to store the findings in a dictionary. If there is a value corresponding to the query in the dictionary already, we don’t search for it again. This also has the advantage that the found lines are available to the script after the search and aren’t just printed out. The regexp search looks for the query string anywhere in the line: if it should only be in the beginning of the line, use regexp ^$query $line instead.
Documentation: dict, fileutil package, foreach, if, package, puts, regexp, set
Try This,
set fd [open "query_file.txt" r]
set data [read $fd]
set uniq_list ""
foreach l [split $data "\n"] {
lappend uniq_list [lindex $l 0]
}
set uniq_list [lsort -unique $uniq_list]
foreach l $uniq_list {
if {[string equal $l ""]} {
continue
}
foreach line [split $data "\n"] {
if {[regexp $l $line]} {
puts "$line"
break
}
}
}
close $fd
References: file , list , regexp
Not using regexp at all: I assume your "query"s do not contain whitespace
set list [list query1 query2 query3]
array set seen {}
set fh [open file1]
while {[gets $fh line] != -1} {
set query [lindex [split $line] 0]
if {$query in $list && $query ni [array names seen]} {
set seen($query) 1
puts $line
}
}
query1 first data
query2 another data
query3 data something

Return index of duplicates in list of list in tcl 8.4

I have this kind of list :
{ A D C } { D S D } { A S D } { Y D D }
I want to list all the index that have duplicates in the same index of the sublist.
For example if I want to serach every "D" at index 2 in sublist, I want to know the index of the list (here 0 and 3)
here is the code :
proc findElement {lst idx value} {
set i 0
foreach sublist $lst {
if {[string equal [lindex $sublist $idx] $value]} {
return $i
}
incr i
}
return -1
}
When i call it findElement $toto 1 D
it returns only 0 !
Why ?
Because you have a return statement when it finds a match when $i = 0.
Try the following which instead returns a list of all the matching indexes
proc findElement {lst idx value} {
set i 0
set return_list [list]
foreach sublist $lst {
puts "i=$i sublist=$sublist"
if {[string equal [lindex $sublist $idx] $value]} {
puts "Found $i"
lappend return_list $i
}
incr i
}
return $return_list
}
You can do a shorter and faster version with lsearch -all -exact -index.
proc findElement {lst idx value} {
return [lsearch -all -exact -index $idx $lst $value]
}

TCL list and foreach problem

Say I have a TCL list:
set myList {}
lappend myList [list a b 1]
lappend myList [list c d 2]
.....
Now I want to modify the list like this:
foreach item $myList {
lappend item "new"
}
But at the end I have not modified list. Why? item is a reference on the list item no?
item is not a reference to the list item. It is a copy. To do what you want, you could do this:
set newlist {}
foreach item $myList {
lappend item "new"
lappend newlist $item
}
set mylist $newlist
To edit the list “in place”, you can do this:
set idx -1
foreach item $myList {
lappend item "new"
lset myList [incr idx] $item
}
You can also do this if you've got Tcl 8.6 (notice that I'm not actually using $item; it's just convenient looping):
set idx -1
foreach item $myList {
lset myList [incr idx] end+1 "new"
}
But it won't work on 8.5, where lset will only replace existing items (and sublists).
for {set i 0} {$i < [llength $myList]} {incr i} {
set item [lindex $myList $i]
lappend item new
set myList [lreplace $myList $i $i $item]
}
If your list is very large, some efficiencies can be made (e.g. the K combinator). They would just add complexity here.
foreach item $myList {
lappend item "new"
}
What you're doing here, is getting a variable (called item), and modifying it to also contain 'new'. basically, you get lists that look like {a new}, {b new} and so on. But you leak those variables at the end of each iteration.
What do you really want your list to look like when you're done?