The lsearch command has -start index as one of the options
-start index The list is searched starting at position index. If index has the value end, it refers to the last element in the list, and
end-integer refers to the last element in the list minus the specified
integer offset.
I would like to use -end along with -start. How can it be done?
It can be done by discarding the indices greater than or equal to -end index in the lsearch returned list. But is there any better way?
I'd be tempted in your case to use lrange to produce a copy of the list being searched without the elements you don't want returned.
lsearch [lrange $theList $start $end] $searchTerm
The real purpose of the -start option is to allow skipping over previously-found matches, and it is less useful now that we have the -all option (which makes lsearch return a list of all the places where it can match the search term). It was used a bit like this:
for {set idx -1} {[set idx [lsearch -start [incr idx] $list $term]] >= 0} {} {
# Process the match index...
puts "found $term at $idx"
}
And now you'd write:
foreach idx [lsearch -all $list $term] {
puts "found $term at $idx"
}
Related
I have a list and need to search some strings in this list. My list is like following:
list1 = {slt0_reg_11.CK slt0_reg_11.Q slt0_reg_12.CK slt0_reg_12.Q}
I am trying to use lsearch to check if above list includes some strings or not. Strings are like:
string1 = {slt0_reg_1 slt0_reg_1}
I am doing the following to check this:
set listInd [lsearch -all -exact -nocase -regexp $list1 $string1]
This commands gives the indexes if list1 includes $string1 (This is what I want). However, problem is if I have a string like slt0_reg_1, the above command identifies the first two elements of the list (slt0_reg_11.CK slt0_reg_11.Q) because these covers the string I search.
How can I make exact search?
It sound like you want to add in word-boundary constraints (\y) to your RE. (Don't use -exact and -regexp at the same time; only one of those modes can be used on any run because they change the comparison engine used.) A little care must be taken because we can't enclose the RE in braces as we want to do variable substitution within it.
set list1 {slt0_reg_11.CK slt0_reg_11.Q slt0_reg_12.CK slt0_reg_12.Q}
foreach str {slt0_reg_11 slt0_reg_1} {
set matches [lsearch -all -regexp $list1 "\\y$str\\y"]
puts "$str: $matches"
}
Prints:
slt0_reg_11: 0 1
slt0_reg_1:
If you want to compare your list for an exact match of the part before the dot against another list, you may be better off using lmap:
set index -1
set listInd [lmap str $list1 {
incr index
if {[lindex [split $str .] 0] ni $string1} continue
set index
}]
I have the following line in my file
The image of yours doesnot match the image I had in mind
I need to find the word image in this line and print the next word succeeding it
i.e I need the following o/p:
word_1 = of
word_2 = I
I have regexp command to find the word image but how can I find words succeeding it without having to use lsearch cmd??
You have to use the parameter -inline that returns the array of matching.
So you could have an example here:
set text "The image of yours doesnot match the image I had in mind"
set i 0
set word_1 ""
set word_2 ""
set words [list ]
foreach {img _get} [regexp -all -inline -- {image ([a-zA-Z]+)} $text] {
# print out the word after "image"
puts $_get
# this if you want to save in a list
lappend words $_get
# here you can save on separate variables
if {$i == 0} {
set word_1 $_get
} else {
set word_2 $_get
}
incr i
}
Using a list is a more flexible approach, but if you already know the exact number of words that will match the sentence, than the single variables should fit well.
You can do it like this:
set txt {The image of yours doesnot match the image I had in mind}
set words [split $txt]
for {set i 0} {$i < [llength $words]} {incr i} {
if {[lindex $words $i] eq "image"} {
puts [lindex $words [incr i]]
}
}
This solution looks at each word in sequence. If it is equal to "image" it prints the following word and then continues with the next word in the list.
edit
To save each found word in a variable and use it immediately, replace puts [lindex $words [incr i]] with:
set found [lindex $words [incr i]]
# do something with $found
To save each found word in a list and deal with all the words after finding them all, replace the same line with:
lappend found [lindex $words [incr i]]
It's a good idea to set found to the empty list before searching for words.
Documentation:
< (operator),
eq (operator),
for,
if,
incr,
lappend,
lindex,
llength,
puts,
set,
split
I have a string in this pattern:
2(some_substring) -> 3(some_other_substring)
Now these number can be anything.
I think this answer would solve the problem. But it gives all the integers in one variable. I want them to be in different variables, so that I can analyze them. Can we split it? But Splitting would cause problem:
If the the numbers are not single-digit, then the splitting will be erroneous.
Is there any other way?
You can use a variation of this: instead of removing the non-digit characters, you can extract all digit characters into a list:
set text {2(some_substring) -> 3(some_other_substring)}
set numbers [regexp -all -inline -- {[0-9]+} $text]
puts $numbers
# => 2 3
And to get each number, you can use lindex:
puts [lindex $numbers 0]
# => 2
Or in versions 8.5 and later, you can use lassign to assign them to specific variable names:
lassign $numbers first second
puts $first
# => 2
puts $second
# => 3
In regexp -all -inline -- {[0-9]+} $text, -all extract all the matches, -inline puts the matches into a list, -- ends the options, [0-9]+ matches at least one integer.
To extend Jerry's answer, in case digits can appear within the parentheses, a regular expression to only extract digits that are immediately followed by an open parenthesis is: {\d+(=\()}
% set text {2(some_6substring) -> 3(some_other_5substring)}
2(some_6substring) -> 3(some_other_5substring)
% lassign [regexp -all -inline {\d+(?=\()} $text] first second
% set first
2
% set second
3
This assumes that you don't have nested parentheses.
I am doing :
glob -nocomplain *
as a result I get 4 files:
a b c d
how can I remove from list b?
I am using this func:
proc lremove {args} {
if {[llength $args] < 2} {
puts stderr {Wrong # args: should be "lremove ?-all? list pattern"}
}
set list [lindex $args end-1]
set elements [lindex $args end]
if [string match -all [lindex $args 0]] {
foreach element $elements {
set list [lsearch -all -inline -not -exact $list $element]
}
} else {
# Using lreplace to truncate the list saves having to calculate
# ranges or offsets from the indexed element. The trimming is
# necessary in cases where the first or last element is the
# indexed element.
foreach element $elements {
set idx [lsearch $list $element]
set list [string trim \
"[lreplace $list $idx end] [lreplace $list 0 $idx]"]
}
}
return $list
}
however it does not working with glob results, but only with strings. please help.
That lreplace procedure is rather dodgy, really, what with swapping the order around, ghetto concatenation and string trim to try to clean up the mess. Yuck. Here's a simpler version (without support for -all, which you don't need for processing the output of glob as that's normally a list of unique elements anyway):
proc lremove {list args} {
foreach toRemove $args {
set index [lsearch -exact $list $toRemove]
set list [lreplace $list $index $index]
}
return $list
}
Let's test it!
% lremove {a b c d e} b d f
a c e
Theoretically it could be made more efficient, but it would take a lot of work and be a PITA to debug. This version is way easier to write and is obviously correct. It should also be substantially faster than what you were working with, as it sticks to purely list operations.
The results from glob shouldn't be particularly special that any unusual effort be required to work with them, but there were some really nasty historic bugs that made that not always true. The latest versions of 8.4 and 8.5 (i.e., 8.4.20 and 8.5.15) don't have the bugs. Nor does any release version of 8.6 (8.6.0 or 8.6.1). If stuff is behaving mysteriously, we'll get into asking about versions and telling you to not be quite so behind the times…
I am looking for a nice short way to get every nth item from a list of lists if the nested list matches a criteria.
So if I have this list:
set x [list [list a 1] [list b 2] [list a 3] [list b 4]]
looking for all the second items in the lists that has "a" as the first item
I want to get {1 3}.
(The list is a key-value pair, so in short I want all the values of the specified key).
This does the work:
lsearch -all -index 1 -inline -subindices [lsearch -all -index 0 -inline $x a] *
But I am looking for a neater shorter way to do this.
Thanks!
With 8.5, I'd advise sticking with what you've got. With Tcl 8.6, you can use lmap:
lmap i $x {lassign $i k v; if {$k ne "a"} continue; set v}
lmap i $x {if {[lindex $i 0] ne "a"} continue {lindex $i 1}}
I'm not sure which one you prefer. (The second is a little longer and a little trickier, but generates better bytecode. The versions with lsearch aren't bytecoded in any version.)