I suspect there is a one liner that takes an array into a string which looks like x=1;y=2;z=3. How can I do that? I am currently using
set vals [join [array names a] \;]
to get x;y;z but would like the values in there. If there happens not to be a value, I would like to skip the = sign, e.g., x=1;y;z=3. Maybe with array get?
This gets all the info in there, but the result looks like x;1;y;2;z;3;q;3
set vals [join [array get a] \;]
some how I'm thinking there is a slice we can take here
Update. Yes, I know that you could do a foreach, but I wonder if there is a one-liner. For example this seems to work
foreach { k v } [array get a] {
if {$v ne ""} {
lappend valList $k=$v
} else {
lappend valList $k
}
}
set vals [join $valList \;]
join [lmap {k v} [array get a] {if {$v ne {}} {join [list $k $v] =} {set k}}] \;
If your Tcl doesn't have lmap, there's a handy replacement.
Related
I have following code to print string which appears more than once in the list
set a [list str1/str2 str3/str4 str3/str4 str5/str6]
foreach x $a {
set search_return [lsearch -all $a $x]
if {[llength $search_return] > 1} {
puts "search_return : $search_return"
}
}
I need to print str3/str4 which appears more than once in the list
The canonical methods of doing this are with arrays or dictionaries, both of which are associative maps. Here's a version with a single loop over the data using a dictionary (it doesn't know the total number of times an item appears when it prints, but sometimes just knowing you've got a multiple is enough).
set a [list str1/str2 str3/str4 str3/str4 str5/str6]
# Make sure that the dictionary doesn't exist ahead of time!
unset -nocomplain counters
foreach item $a {
if {[dict incr counters $item] == 2} {
puts "$item appears several times"
}
}
I guess you could use an array to do something like that, since arrays have unique keys:
set a [list str1/str2 str3/str4 str3/str4 str5/str6]
foreach x $a {
incr arr($x) ;# basically counting each occurrence
}
foreach {key val} [array get arr] {
if {$val > 1} {puts "$key appears $val times"}
}
I have a file having following data
Anny : dancing
Sonny : reciting
Joel : dancing
Anny : reciting
Anny : singing
I want the following o/p in tcl:
Anny -
singing 1
dancing 1
reciting 1
Joel -
dancing 1
I want to print in this format along with their count.
Working with Donal's answer, but using a single dictionary instead of an array of dictionaries:
set data [dict create]
set f [open yourinputfile.txt r]
while {[gets $f line] != -1} {
if {[scan $line "%s : %s" who what] == 2} {
dict update data $who activities {
dict incr activities $what
}
}
}
close $f
dict for {who activities} $data {
puts "$who -"
dict for {what count} $activities {
puts "$what $count"
}
puts ""
}
This is really about counting words, so we're going to be dealing with dictionaries — dict incr is a perfect tool for this — but you also need to do a bit of parsing. Parsing is done in many ways, but in this case scan can do what we want easily. (Remember when reading my code that the result of scan is the number of fields that it managed to satisfy.)
set f [open "yourinputfile.txt"]
set data [split [read $f] "\n"]
close $f
# Peel apart that data
foreach line $data {
if {[scan $line "%s : %s" who what] == 2} {
dict incr activity($who) $what
}
}
# Now produce the output
foreach who [lsort [array names activity]] {
puts "$who -"
dict for {what count} $activity($who) {
puts "$what $count"
}
# And the extra blank line
puts ""
}
You could use an array to store the info while you're collecting in.
The regexp you're using is wrong.
Use a list of list to collect the matching in a pair way (i.e. word #n), and then assign all the collected matching to the proper key on the array.
Here is an example on how to do it:
set file_content {Anny : dancing
Sonny : reciting
Joel : dancing
Anny : reciting
Anny : singing
}
array set res {}
set anny {}
lappend anny [list dancing [regexp -all {Anny\s*:\s*dancing} $file_content] ]
lappend anny [list singing [regexp -all {Anny\s*:\s*singing} $file_content] ]
lappend anny [list reciting [regexp -all {Anny\s*:\s*reciting} $file_content] ]
set res(Anny) $anny
puts [array get res]
If I run this the output is:
Anny {{dancing 1} {singing 1} {reciting 1}}
Now you could use the array to format the output as you wish.
Of course you should do the same with other names, so the best is to put the code inside a function.
This is one way to do it.
Count the number of different lines. Get rid of the colon.
foreach line [split $data \n] {
dict incr d0 [string map {: {}} $line]
}
Convert the dictionary of lines and counts to a hierarchical dictionary with names on the highest level and activities on the next level. If line contains "Joel dancing", the invocation below will be, after expansion with {*}: dict set d1 Joel dancing 1, creating the dictionary item Joel {dancing 1}.
dict for {line count} $d0 {
dict set d1 {*}$line $count
}
Iterate over the dictionary and print the keys and values.
dict for {name activities} $d1 {
puts "$name -"
foreach {activity count} $activities {
puts "$activity $count"
}
puts {}
}
Documentation:
dict,
foreach,
puts,
split,
string,
{*} (syntax)
Looking for a way to lsort a list of strings by the n last characters.
Desired outcome:
lsort -lastchars 3 {123xyz 456uvw 789abc}
789abc 456uvw 123xyz
My fall back position would be to use -command option and write my proc discarding all but the last 3 characters.
Thanks,
Gert
A fast way to do this is to compute a collation key and to sort on that. A collation key is just a string that sorts in the order that you want; you package them up with the real values to sort and sort together.
set yourList {123xyz 456uvw 789abc}
set withCKs {}
foreach value $yourList {
lappend withCKs [list $value [string range $value end-2 end]]
}
set sorted {}
foreach pair [lsort -index 1 $withCKs] {
lappend sorted [lindex $pair 0]
}
This can be made more elegant in Tcl 8.6:
set sorted [lmap pair [lsort -index 1 [lmap val $yourList {list $val [string range $val end-2 end]}]] {lindex $pair 0}]
Splitting up the one-liner for clarity:
# Add in the collation keys
set withCKs [lmap val $yourList {list $val [string range $val end-2 end]}]
# Sort by the collation keys and then strip them from the result list
set sorted [lmap pair [lsort -index 1 $withCKs] {lindex $pair 0}]
A different approach is to produce the collation keys in a separate list and then to get lsort to spit out the indices it produces when sorting.
set CKs [lmap val $yourList {string range $val end-2 end}]
set sorted [lmap idx [lsort -indices $CKs] {lindex $yourList $idx}]
As a one-liner:
set sorted [lmap idx [lsort -indices [lmap val $yourList {string range $val end-2 end}]] {lindex $yourList $idx}]
For Tcl 8.5 (there's no -indices option in 8.4 or before):
set CKs [set sorted {}]
foreach val $yourList {
lappend CKs [string range $val end-2 end]
}
foreach idx [lsort -indices $CKs] {
lappend sorted [lindex $yourList $idx]
}
(The foreach/lappend pattern is precisely what lmap improves on in 8.6.)
Your fallback idea is the way to achieve this.
proc f {lhs rhs} {
return [string compare [string range $lhs end-2 end] \
[string range $rhs end-2 end]]
}
lsort -command f {123xyz 456uvw 789abc}
returns
789abc 456uvw 123xyz
How to remove duplicate element from Tcl list say:
list is like [this,that,when,what,when,how]
I have Googled and have found lsort unique but same is not working for me. I want to remove when from list.
The following works for me
set myList [list this that when what when how]
lsort -unique $myList
this returns
how that this what when
which you could store in a new list
set uniqueList [lsort -unique $myList]
You could also use an dictionary, where the keys must be unique:
set l {this that when what when how}
foreach element $l {dict set tmp $element 1}
set unique [dict keys $tmp]
puts $unique
this that when what how
That will preserve the order of the elements.
glenn jackman's answer work perfectly on Tcl 8.6 and above.
For Tcl 8.4 and below (No dict command). You can use:
proc list_unique {list} {
array set included_arr [list]
set unique_list [list]
foreach item $list {
if { ![info exists included_arr($item)] } {
set included_arr($item) ""
lappend unique_list $item
}
}
unset included_arr
return $unique_list
}
set list [list this that when what when how]
set unique [list_unique $list]
This will also preserve the order of the elements
and this is the result:
this that when what how
Another way, if do not wanna use native lsort function.This is what the interviewer asks :)
`set a "this that when what when how"
for {set i 0} {$i < [llength $a]} {incr i} {
set indices [lsearch -all $a [lindex $a $i]]
foreach index $indices {
if {$index != $i} {
set a [lreplace $a $index $index]
}
}
}
`
Is there a way to split strings and save in a list ?
How to split string and save in two list
For example, I have a string where I split several string with =:
a=1
b=2
c=3
d=4
and then I want to create two list like this [a,b,c,d] and [1,2,3,4]:
Following is a simple tcl code
set s "a=1\nb=2\nc=3\nd=4"
set s [split $s "\n"]
foreach e $s {
set e [split $e "="]
lappend l1 [lindex $e 0]
lappend l2 [lindex $e 1]
}
Now you have list l1 with [a b c d] and l2 has [1 2 3 4]
The simplest way is to read all the data in, split into lines, and then use regexp with each line to extract the pieces.
set f [open "theFile.txt"]
set lines [split [read $f] "\n"]
close $f
set keys [set values {}]
foreach line $lines {
if {[regexp {^([^=]*)=(.*)$} $line -> key value]} {
lappend keys $key
lappend values $value
} else {
# No '=' in the line!!!
}
}
# keys in $keys, values in $values
puts "keys = \[[join $keys ,]\]"
puts "values = \[[join $values ,]\]"
Run that (assuming that the filename is right) and you'll get output like:
keys = [a,b,c,d]
values = [1,2,3,4]
Collecting two lists like that might not be the best thing to do with such stuff. Often, it is better to instead to store in an array:
# Guarded by that [regexp] inside the foreach
set myArray($key) $value
Like that, you can do lookups by name rather than having to manually search. Assuming that keys are unique and order doesn't matter.
A simple way might be using a loop:
% set lines "a=1\nb=2\nc=3\nd=4"
a=1
b=2
c=3
d=4
% set expressionList [split $lines "\n"]
a=1 b=2 c=3 d=4
% set var [list]
% set val [list]
% foreach i $expressionList {
set variable [lindex [split $i "="] 0]
set value [lindex [split $i "="] 1]
lappend val $value
lappend var $variable
}
% puts $var
a b c d
% puts $val
1 2 3 4
If you don't mind a regex, you might try something like this:
% set lines "a=1\nb=2\nc=3\nd=4"
a=1
b=2
c=3
d=4
% set var [regexp -inline -lineanchor -all -- {^[^=\n\r]+} $lines]
a b c d
% set val [regexp -inline -lineanchor -all -- {[^=\n\r]+$} $lines]
1 2 3 4
If replacing the equals sign characters in $data with blanks always leaves a proper, even-valued list (as in the example) it can be done a lot simpler:
set dict [string map {= { }} $data]
set keys [dict keys $dict]
set values [dict values $dict]
Documentation: dict, set, string
Let say your strings placed in file abc.txt in the following order
a=1
b=2
c=3
d=4
You need to create 2 lists, one for numbers and one for characters:
set number_list [list]
set char_list [list]
set fh [open "abc.txt" "r"]
while {[gets $fh line] != -1} {
regexp -- {(\S+)=(\S+)} $line foo char number
lappend char_list $char
lappend number_list $number
}
close $fh
puts $char_list
puts $number_list
This is pretty old, but I would actually go about it differently... Something like the following, considering that the string is [a=1\nb=1\n ... etc.] with variable name "str":
# determine num entries in string
set max [llength $str]
#create new strings (alph & num) based on split string
set i 0
set str [split $str \n]
set alph []
set num []
while {$i < $max} {
set alph "$alph [lindex [split [lindex $str $i] "="] 0]
set num "$num [lindex [split [lindex $str $i] "="] 1]
incr i}
Maybe just personal preference, but seems simplest to me; code was not tested, but it's similar to something I was just working on.