TCL sort a file mathematically - tcl

I have a file which has multiple lines like :-
A B A 10 20
A B A 10 20
C D A 10 15
A B Q 15 20
A B A 35 45
A B A 15 20
C D A 10 15
A B A 20 25
.
.
.
A A A x1 y1
The first three fileds are some text patterns.
Now I want to write a program in TCL which does BOTH of the following:-
Does a unique sort "sort -u" for the file & reoves the repeated lines & dumps the O/P in new file.
For case where 1st three field is same dump only those lines where the numbers are greater than 10 from each other.
For eg the O/P of above file satisfying both conditions will be:-
A B A 10 20
A B A 35 45
C D A 10 15
A B Q 15 20
The order of lines is not important in file.
##Changed the program
set input [open "data.txt" "r"]
set content [read $input]
set lines [lsort -unique [split $content "\n"]]
set keylist ""
set valuelist ""
foreach line $lines {
if {$line == ""} { continue }
set data [split $line " "]
set key [join [lrange $data 0 2] "_"]
set index [lsearch $keylist $key]
if {$index != -1} {
set value [lindex $valuelist $index]
set diff_a [expr [lindex $data 3] - [lindex $value 0]]
set diff_b [expr [lindex $data 4] - [lindex $value 1]]
if {$diff_a > 10 && $diff_b > 10 } {
puts $line
}
set a [ lreplace valuelist $index $index [lrange $data 3 4]]
set valuelist $a
} else {
lappend keylist $key
lappend valuelist [lrange $data 3 4]
puts $line
}
}

It's not a smart solution, but works.
set input [open "data.txt" "r"]
set content [read $input]
set lines [lsort -unique [split $content "\n"]]
set keylist ""
set valuelist ""
foreach line $lines {
if {$line == ""} { continue }
set data [split $line " "]
set key [join [lrange $data 0 2] "_"]
set index [lsearch $keylist $key]
if {$index != -1} {
set value [lindex $valuelist $index]
set diff_a [expr [lindex $data 3] - [lindex $value 0]]
set diff_b [expr [lindex $data 4] - [lindex $value 1]]
if {$diff_a > 10 && $diff_b > 10 } {
puts $line
}
set valuelist [lreplace valuelist $index $index [lrange $data 3 4]]
} else {
lappend keylist $key
lappend valuelist [lrange $data 3 4]
puts $line
}
}
Output:
A B A 10 20
A B A 35 45
A B Q 15 20
C D A 10 15

Related

extract variables from proc using upvar

I want to return some value from proc.. but It didn't work because of pointer..
So I tried to use upvar to return $sofar. but It didn't work..
Could you help me to extarct list of $sofar to return??
I need to grep $test_output as list of list.
thanks :)
proc combinationSum {sum sofar want numbers output } {
if {$sum == $want} {
puts $sofar
#upvar $output tmp
#set tmp $output
}
if {($sum < $want) && ([lindex $numbers 0] > 0) && ([llength $numbers] > 0)} {
combinationSum [expr $sum + [lindex $numbers 0]] [concat $sofar [lindex $numbers 0]] $want $numbers $output]
combinationSum $sum $sofar $want [lrange $numbers 1 end] $output]
}
}
set test_input [list 2 3 4 7 8 ]
set test_target 15
set test_output [ combinationSum 0 [] $test_target $test_input [] ]
puts $test_output
I expand the test_output is list ( {2 2 2 2 2 2 3} { 2 2 2 2 3 4 } { 2 2 2 2 7} ..... )

parse specific branch in hiearchically formatted text file in Tcl

I'm trying to parse an ascii text file that looks like this.
KEY1 VAL1
KEY2 VAL2
KEY3 VAL3
KEY4 VAL4
KEY5 VAL5
KEY6 VAL6
KEY7 VAL7
KEY8 VAL8
KEY9 VAL9
I would like to convert this to a flat table of values from KEYs 1,5,7. I have a very ugly brute force algorithm that loops through the file and sets flags to read values, but that doesn't appear most efficient.
something like:
set f [open $filename]
set data [split [read $f] "\n"]
foreach line $data {
if {[string match KEY1* $line] ==1} {set key1match 1}
if {($keymatch1==1) && ([string match KEY5* $line] ==1} {set key5match 1}
...
Is there a more elegant way to generate this mapping?
Is this what you wanted?
set keylist {}
set keyset {KEY1 KEY5 KEY7}
set flatDict {}
foreach line [split [string trim $input] \n] {
if {[regexp {(\s*)(\w+)\s*(.*)} $line -> indent key val] && $key in $keyset} {
set level [expr {[string length $indent] / 2}]
set keylist [lrange $keylist 0 $level]
lappend keylist $key
dict set flatDict $keylist $val
}
}
% set flatDict
KEY1 VAL1 {KEY1 KEY5} VAL5 {KEY1 KEY5 KEY7} VAL7
This code keeps a list of keys, keylist, that grows (by lappend) and contracts (by lrange) according to indentation (and is completely dependent on indentation being correct). Only keys in a given set, keyset are considered. For each value added to the dictionary, the current $keylist is used as a key (the dict command can deal with key hierarchies, but then the keys must be separate and not inside a list (e.g. dict set myDict foo bar 123).
Documentation:
&& (operator),
/ (operator),
dict,
expr,
foreach,
if,
in (operator),
lappend,
lrange,
regexp,
set,
split,
string,
Syntax of Tcl regular expressions
Afterthought: with your selection of keys there is actually no need to contract the key list. If you only use keys that follow a single line of descent from the root, you could use this code:
set keylist {}
set flatDict {}
foreach line [split [string trim $input] \n] {
set val [lassign [split [string trim $line]] key]
if {$key in $keyset} {
lappend keylist $key
dict set flatDict $keylist $val
}
}
% set flatDict
KEY1 VAL1 {KEY1 KEY5} VAL5 {KEY1 KEY5 KEY7} VAL7
Note that in both examples, I have provided for values that might contain whitespace. The code can be made a little more regular if the value is always atomic.
Here's some code to parse that data into a dictionary:
set indent_width 2
set d [dict create]
set fh [open [lindex $argv 0] r]
while {[gets $fh line] != -1} {
regexp {^(\s*)(\S+)\s*(.*)} $line -> indent key value
if {$key eq ""} continue
set level [expr {[string length $indent] / $indent_width}]
dict set d $key level $level
dict set d $key value $value
dict set d $key children [list]
dict set d $key parent ""
dict set d last $level $key
set prev_level [expr {$level - 1}]
if {$prev_level >= 0} {
set parent_key [dict get $d last $prev_level]
dict update d $parent_key item {
dict lappend item children $key
}
dict set d $key parent $parent_key
}
}
dict unset d last
dict for {key value} $d {puts [list $key $value]}
outputs
KEY1 {level 0 value VAL1 children {KEY2 KEY5} parent {}}
KEY2 {level 1 value VAL2 children KEY3 parent KEY1}
KEY3 {level 2 value VAL3 children KEY4 parent KEY2}
KEY4 {level 3 value VAL4 children {} parent KEY3}
KEY5 {level 1 value VAL5 children KEY6 parent KEY1}
KEY6 {level 2 value VAL6 children KEY7 parent KEY5}
KEY7 {level 3 value VAL7 children {} parent KEY6}
KEY8 {level 0 value VAL8 children KEY9 parent {}}
KEY9 {level 1 value VAL9 children {} parent KEY8}

How to append output of rescheduled procedure in a list

In below code proc processDistance gives sorted list which is used by proc Inverse2.Proc Inverse2 gives list which contains {x,y,current time}.This is executed after every 1s.but the problem is when I write puts $results inside proc Inverse2 ,in terminal I can only see $result at current time only.Though I have used lappend when I check length of $result it shows length of list is 1.I want to access previously generated values also. How to do it.
code:
proc distance {n1 n2 nd1 nd2} {
global ns
set now [$ns now]
set x1 [expr int([$n1 set X_])]
set y1 [expr int([$n1 set Y_])]
set x2 [expr int([$n2 set X_])]
set y2 [expr int([$n2 set Y_])]
set d [expr hypot($x2-$x1,$y2-$y1)]
return [list $nd1 $nd2 $x1 $y1 $x2 $y2 $d $now]
}
proc processDistances {count threshold {filter ""}} {
global node_
global ns
set now [$ns now]
set t 1.0
set distances {}
for {set i 1} {$i < $count} {incr i} {
for {set j 1} {$j < $count} {incr j} {
# Skip self comparisons
if {$i == $j} continue
# Apply target filter
if {$filter ne "" && $j != $filter} continue
# Get the distance information
set thisDistance [distance $node_($i) $node_($j) $i $j]
# Check that the nodes are close enough
if {[lindex $thisDistance 6] < $threshold} {
lappend distances $thisDistance
}
}
}
# Sort the pairs, by distances
set distances [lsort -real -increasing -index 6 $distances]
puts $distances
$ns at [expr $now+$t] [list processDistances $count $threshold $filter]
Inverse2 $distances
}
$ns at 8.0 [list processDistances $val(nn) 200 41]
proc Inverse2 {m} {
set result {}
lassign [lindex $m 0 2] x1
lassign [lindex $m 0 3] y1
lassign [lindex $m 0 6] d1
lassign [lindex $m 1 2] x2
lassign [lindex $m 1 3] y2
lassign [lindex $m 1 6] d2
lassign [lindex $m 2 2] x3
lassign [lindex $m 2 3] y3
lassign [lindex $m 2 6] d3
set mt {{? ?} {? ?}}
lset mt 0 0 [expr 2*($x1-$x2)]
lset mt 0 1 [expr 2*($y1-$y2)]
lset mt 1 0 [expr 2*($x1-$x3)]
lset mt 1 1 [expr 2*($y1-$y3)]
set const {{?} {?}}
lset const 0 [expr {(pow($x1,2)+pow($y1,2)-pow($d1,2))-(pow($x2,2)+pow($y2,2)-pow($d2,2))}]
lset const 1 [expr {(pow($x1,2)+pow($y1,2)-pow($d1,2))-(pow($x3,2)+pow($y3,2)-pow($d3,2))}]
set x [expr {double([lindex [Inverse3 $mt] 0 0] * [lindex $const 0]
+ [lindex [Inverse3 $mt] 0 1] * [lindex $const 1])}]
set y [expr {double([lindex [Inverse3 $mt] 1 0] * [lindex $const 0]
+ [lindex [Inverse3 $mt] 1 1] * [lindex $const 1])}]
lappend result "$x $y $now"
puts $result
}

How to pass output of one procedure as an argument to another procedure in tcl

I want pass output of one procedure which is a list as an argument to another procedure.Below is a code which I have tried.
proc distance {n1 n2 nd1 nd2} {
set x1 [expr int([$n1 set X_])]
set y1 [expr int([$n1 set Y_])]
set x2 [expr int([$n2 set X_])]
set y2 [expr int([$n2 set Y_])]
set d [expr hypot($x2-$x1,$y2-$y1)]
return [list $nd1 $nd2 $x1 $y1 $x2 $y2 $d]
}
proc processDistances {count threshold {filter ""}} {
global node_
set distances {}
for {set i 1} {$i < $count} {incr i} {
for {set j 1} {$j < $count} {incr j} {
# Skip self comparisons
if {$i == $j} continue
# Apply target filter
if {$filter ne "" && $j != $filter} continue
# Get the distance information
set thisDistance [distance $node_($i) $node_($j) $i $j]
# Check that the nodes are close enough
if {[lindex $thisDistance 6] < $threshold} {
lappend distances $thisDistance
}
}
}
# Sort the pairs, by distances
set distances [lsort -real -increasing -index 6 $distances]
Inverse2 {*}$distances
}
$ns at 8.5 [list processDistances $val(nn) 200 41]
proc Inverse2 {m} {
set result [open R.tr w]
lassign [lindex $m 0 2] x1
lassign [lindex $m 0 3] y1
lassign [lindex $m 0 4] d1
lassign [lindex $m 1 2] x2
lassign [lindex $m 1 3] y2
lassign [lindex $m 1 4] d2
lassign [lindex $m 2 2] x3
lassign [lindex $m 2 3] y3
lassign [lindex $m 2 4] d3
set mt {{? ?} {? ?}}
lset mt 0 0 [expr 2*($x1-$x2)]
lset mt 0 1 [expr 2*($y1-$y2)]
lset mt 1 0 [expr 2*($x1-$x3)]
lset mt 1 1 [expr 2*($y1-$y3)]
set const {{?} {?}}
lset const 0 [expr {(pow($x1,2)+pow($y1,2)-pow($d1,2))-(pow($x2,2)+pow($y2,2)-pow($d2,2))}]
lset const 1 [expr {(pow($x1,2)+pow($y1,2)-pow($d1,2))-(pow($x3,2)+pow($y3,2)-pow($d3,2))}]
set x [expr {double([lindex [Inverse3 $mt] 0 0] * [lindex $const 0]
+ [lindex [Inverse3 $mt] 0 1] * [lindex $const 1])}]
set y [expr {double([lindex [Inverse3 $mt] 1 0] * [lindex $const 0]
+ [lindex [Inverse3 $mt] 1 1] * [lindex $const 1])}]
puts $result "x location of object is: $x \ny location of object is: $y"
}
Error:
ns: processDistances 42 200 41: wrong # args: should be "Inverse2 m"
while executing
"Inverse2 {*} $distances"
(procedure "processDistances" line 32)
invoked from within
"processDistances 42 200 41"
I'm getting output of proc processDistances successfully which is a sorted list but when I pass this output to procedure Inverse2 using command Inverse2 {*}$distances written in processDistances (I have tcl8.5).I'm getting above error.Where I'm dong wrong.please help me out.
I am suggesting you run it just as I replaced it. If this does not work, I'm not sure what you're asking for.
proc distance {n1 n2 nd1 nd2} {
set x1 [expr int([$n1 set X_])]
set y1 [expr int([$n1 set Y_])]
set x2 [expr int([$n2 set X_])]
set y2 [expr int([$n2 set Y_])]
set d [expr hypot($x2-$x1,$y2-$y1)]
return [list $nd1 $nd2 $x1 $y1 $x2 $y2 $d]
}
proc processDistances {count threshold {filter ""}} {
global node_
set distances {}
for {set i 1} {$i < $count} {incr i} {
for {set j 1} {$j < $count} {incr j} {
# Skip self comparisons
if {$i == $j} continue
# Apply target filter
if {$filter ne "" && $j != $filter} continue
# Get the distance information
set thisDistance [distance $node_($i) $node_($j) $i $j]
# Check that the nodes are close enough
if {[lindex $thisDistance 6] < $threshold} {
lappend distances $thisDistance
}
}
}
# Sort the pairs, by distances
set distances [lsort -real -increasing -index 6 $distances]
#Inverse2 {*}$distances
Inverse2 $distances
}
$ns at 8.5 [list processDistances $val(nn) 200 41]
proc Inverse2 {m} {
set result [open R.tr w]
lassign [lindex $m 0 2] x1
lassign [lindex $m 0 3] y1
lassign [lindex $m 0 4] d1
lassign [lindex $m 1 2] x2
lassign [lindex $m 1 3] y2
lassign [lindex $m 1 4] d2
lassign [lindex $m 2 2] x3
lassign [lindex $m 2 3] y3
lassign [lindex $m 2 4] d3
set mt {{? ?} {? ?}}
lset mt 0 0 [expr 2*($x1-$x2)]
lset mt 0 1 [expr 2*($y1-$y2)]
lset mt 1 0 [expr 2*($x1-$x3)]
lset mt 1 1 [expr 2*($y1-$y3)]
set const {{?} {?}}
lset const 0 [expr {(pow($x1,2)+pow($y1,2)-pow($d1,2))-(pow($x2,2)+pow($y2,2)-pow($d2,2))}]
lset const 1 [expr {(pow($x1,2)+pow($y1,2)-pow($d1,2))-(pow($x3,2)+pow($y3,2)-pow($d3,2))}]
set x [expr {double([lindex [Inverse3 $mt] 0 0] * [lindex $const 0]
+ [lindex [Inverse3 $mt] 0 1] * [lindex $const 1])}]
set y [expr {double([lindex [Inverse3 $mt] 1 0] * [lindex $const 0]
+ [lindex [Inverse3 $mt] 1 1] * [lindex $const 1])}]
puts $result "x location of object is: $x \ny location of object is: $y"
}

handling 2 lists in TCL

I am rephrasing my earlier question as the output requirement is changed.
two lists
fruit_type={apple, apple, orange, orange, pineapple, pineapple, pineapple}
num_type = {2, 1, 1, 2, 3, 1, 2}
Output Expected
apple 2
apple 1
orange 1 2
pineapple 3
pineapple 1 2
proc debugityourself-or-maybe-even-port-to-tcl8.4 {fruit_type num_type} {
foreach fruit $fruit_type num $num_type {
if {[info exists result]&&
([lindex $result end-1 0] eq $fruit)&&
([lindex $result end end] <= $num)} {
lset result end [list {*}[lindex $result end] $num]
} else {
lappend result [list $fruit] [list $num]
}
}
if {[info exists result]} {
return [concat {*}$result]
}
}
UPD: as requirements has changed again, here is a new version:
proc fruits-of-labour-for-new-requirements {fruit_type num_type} {
foreach fruit $fruit_type num $num_type {
if {[info exists resultF]&&
([lindex $resultF end] eq $fruit)&&
([lindex $resultN end end] <= $num)} {
lset resultN end [list {*}[lindex $resultN end] $num]
} else {
lappend resultF $fruit
lappend resultN [list $num]
}
}
if {[info exists resultF]} {
# $resultF is a list of fruits, $resultN is a list of
# lists with corresponding numbers.
return [list $resultF $resultN]
}
}