tcl 将计数器添加到相同的列表值
tcl add counter to identical list values
我有一个包含以下值的列表:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
我想为后续相同的值添加一个计数器,如下所示:
numbered [ 101.1 101.2 101.3 102.1 102.2 103 104 105.1 105.2 105.3 106]
到目前为止我已经尝试了以下方法:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list ]
set previous [lindex $unnumbered 0]
set subcounter 1
foreach current $unnumbered {
if { $current eq $previous } {
lappend numbered $current.$counter
set previous $current
incr subcounter
} else {
lappend numbered $current
set previous $current
set subcounter 1
}
}
结果差不多就是我需要的了
101.1 101.2 101.3 102 102.1 103 104 105 105.1 105.2 106
对于除第一个值之外的所有值,计数器开始计数到很晚。前 102 个是“.1”
我该如何解决这个问题?
问题是您的代码在将数字添加到 numbered
时没有足够的信息。先获取信息,再应用。
首先,创建一个列表,其中每个项目都是由 $unnumbered
中的唯一数字之一和 $unnumbered
中出现该数字的索引组成的列表:
lmap n [lsort -unique $unnumbered] {
list $n [lsearch -all $unnumbered $n]
}
# => {101 {0 1 2}} {102 {3 4}} {103 5} {104 6} {105 {7 8 9}} {106 10}
对于这些项目中的每一个,将项目拆分为 n
= 数字和 indices
= 索引。检查你有多少指数。对于多个索引,添加这样的枚举数:
set i 0
foreach index $indices {
lappend numbered $n.[incr i]
}
对于单个索引,只需添加数字:
lappend numbered $n
整个程序如下所示:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list]
foreach item [lmap n [lsort -unique $unnumbered] {
list $n [lsearch -all $unnumbered $n]
}] {
lassign $item n indices
if {[llength $indices] > 1} {
set i 0
foreach index $indices {
lappend numbered $n.[incr i]
}
} else {
lappend numbered $n
}
}
文档:
> (operator),
foreach,
if,
incr,
lappend,
lassign,
list,
llength,
lmap (for Tcl 8.5),
lmap,
lsearch,
lsort,
set
如果您没有 lmap
,请参阅上面的 link。如果您没有 lassign
,请使用
foreach {n indices} $item break
相反。
ETA 如果可以放宽"no index on singleton numbers"要求,可以这样:
set previous {}
lmap num $unnumbered {
if {$num ne $previous} {
set i 0
}
set previous $num
format %d.%d $num [incr i]
}
另一种变体。这与 Jerry 的第二个建议非常相似,但老实说,直到我要提交这个建议时我才看到那个建议。这个假设 $unnumbered
中没有元素是空字符串。
set numbered [list]
set rest [lassign $unnumbered current next]
set i 0
while 1 {
if {$current eq $next} {
lappend numbered $current.[incr i]
} else {
if {$i > 0} {
lappend numbered $current.[incr i]
set i 0
} else {
lappend numbered $current
}
set current $next
}
if {$next eq {}} break
set rest [lassign $rest next]
}
另一种方法:维护一个 dict 来记录你到目前为止所看到的内容
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set count [dict create]
set numbered {}
foreach num $unnumbered {
dict incr count $num
lappend numbered "$num.[dict get $count $num]"
}
puts $numbered
101.1 101.2 101.3 102.1 102.2 103.1 104.1 105.1 105.2 105.3 106.1
使用数组稍微简单一点:利用 incr
returns 新计数
set numbered {}
array set count {}
foreach num $unnumbered {lappend numbered "$num.[incr count($num)]"}
好的,我错过了单身条目不应该有后缀的要求。有这个,但它可能重新排序初始列表:
set count [dict create]
foreach num $unnumbered {dict incr count $num}
set numbered {}
foreach num [dict keys $count] {
set c [dict get $count $num]
if {$c == 1} {
lappend numbered $num
} else {
for {set i 1} {$i <= $c} {incr i} {
lappend numbered "$num.$i"
}
}
}
puts $numbered
101.1 101.2 101.3 102.1 102.2 103 104 105.1 105.2 105.3 106
或者,这样保持原来的顺序
set count [dict create]
foreach num $unnumbered {dict incr count $num}
foreach key [dict keys $count] {
if {[dict get $count $key] == 1} {
set count [dict remove $count $key]
}
}
set numbered {}
foreach num [lreverse $unnumbered] {
if {![dict exists $count $num]} {
lappend numbered $num
} else {
lappend numbered "$num.[dict get $count $num]"
dict incr count $num -1
}
}
set numbered [lreverse $numbered]
puts $numbered
101.1 101.2 101.3 102.1 102.2 103 104 105.1 105.2 105.3 106
O(n) 解决方案(单循环),我认为它看起来更像您最初想要实现它的方式:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list]
set previous ""
set subcounter 1
foreach current $unnumbered {
if {$previous == ""} {
# First, do nothing except set $current to $previous later below
} elseif {$previous == $current} {
lappend numbered $previous.$subcounter
incr subcounter
} else {
if {$subcounter > 1} {
lappend numbered $previous.$subcounter
} else {
lappend numbered $previous
}
set subcounter 1
}
set previous $current
}
if {$subcounter > 1} {
lappend numbered $current.$subcounter
} else {
lappend numbered $current
}
循环基本上在 numbered
列表中添加了一个迟到的数字,因此最后一个数字需要最后一个 if
。当然,这只有在您知道 unnumbered
已排序时才有效。
编辑:实际上这更近了!由于您已经可以获得 $previous
,因此您可以从列表的下一个元素开始循环,并在最后一个元素之后进行最后一次循环(请注意,如果 lindex
提供了一个 out范围索引,这使这里的事情变得更容易)。
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list]
set previous [lindex $unnumbered 0]
set subcounter 1
for {set i 1} {$i <= [llength $unnumbered]} {incr i} {
if {$previous == [lindex $unnumbered $i]} {
lappend numbered $previous.$subcounter
incr subcounter
} else {
if {$subcounter > 1} {
lappend numbered $previous.$subcounter
} else {
lappend numbered $previous
}
set subcounter 1
}
set previous [lindex $unnumbered $i]
}
我有一个包含以下值的列表:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
我想为后续相同的值添加一个计数器,如下所示:
numbered [ 101.1 101.2 101.3 102.1 102.2 103 104 105.1 105.2 105.3 106]
到目前为止我已经尝试了以下方法:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list ]
set previous [lindex $unnumbered 0]
set subcounter 1
foreach current $unnumbered {
if { $current eq $previous } {
lappend numbered $current.$counter
set previous $current
incr subcounter
} else {
lappend numbered $current
set previous $current
set subcounter 1
}
}
结果差不多就是我需要的了
101.1 101.2 101.3 102 102.1 103 104 105 105.1 105.2 106
对于除第一个值之外的所有值,计数器开始计数到很晚。前 102 个是“.1”
我该如何解决这个问题?
问题是您的代码在将数字添加到 numbered
时没有足够的信息。先获取信息,再应用。
首先,创建一个列表,其中每个项目都是由 $unnumbered
中的唯一数字之一和 $unnumbered
中出现该数字的索引组成的列表:
lmap n [lsort -unique $unnumbered] {
list $n [lsearch -all $unnumbered $n]
}
# => {101 {0 1 2}} {102 {3 4}} {103 5} {104 6} {105 {7 8 9}} {106 10}
对于这些项目中的每一个,将项目拆分为 n
= 数字和 indices
= 索引。检查你有多少指数。对于多个索引,添加这样的枚举数:
set i 0
foreach index $indices {
lappend numbered $n.[incr i]
}
对于单个索引,只需添加数字:
lappend numbered $n
整个程序如下所示:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list]
foreach item [lmap n [lsort -unique $unnumbered] {
list $n [lsearch -all $unnumbered $n]
}] {
lassign $item n indices
if {[llength $indices] > 1} {
set i 0
foreach index $indices {
lappend numbered $n.[incr i]
}
} else {
lappend numbered $n
}
}
文档: > (operator), foreach, if, incr, lappend, lassign, list, llength, lmap (for Tcl 8.5), lmap, lsearch, lsort, set
如果您没有 lmap
,请参阅上面的 link。如果您没有 lassign
,请使用
foreach {n indices} $item break
相反。
ETA 如果可以放宽"no index on singleton numbers"要求,可以这样:
set previous {}
lmap num $unnumbered {
if {$num ne $previous} {
set i 0
}
set previous $num
format %d.%d $num [incr i]
}
另一种变体。这与 Jerry 的第二个建议非常相似,但老实说,直到我要提交这个建议时我才看到那个建议。这个假设 $unnumbered
中没有元素是空字符串。
set numbered [list]
set rest [lassign $unnumbered current next]
set i 0
while 1 {
if {$current eq $next} {
lappend numbered $current.[incr i]
} else {
if {$i > 0} {
lappend numbered $current.[incr i]
set i 0
} else {
lappend numbered $current
}
set current $next
}
if {$next eq {}} break
set rest [lassign $rest next]
}
另一种方法:维护一个 dict 来记录你到目前为止所看到的内容
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set count [dict create]
set numbered {}
foreach num $unnumbered {
dict incr count $num
lappend numbered "$num.[dict get $count $num]"
}
puts $numbered
101.1 101.2 101.3 102.1 102.2 103.1 104.1 105.1 105.2 105.3 106.1
使用数组稍微简单一点:利用 incr
returns 新计数
set numbered {}
array set count {}
foreach num $unnumbered {lappend numbered "$num.[incr count($num)]"}
好的,我错过了单身条目不应该有后缀的要求。有这个,但它可能重新排序初始列表:
set count [dict create]
foreach num $unnumbered {dict incr count $num}
set numbered {}
foreach num [dict keys $count] {
set c [dict get $count $num]
if {$c == 1} {
lappend numbered $num
} else {
for {set i 1} {$i <= $c} {incr i} {
lappend numbered "$num.$i"
}
}
}
puts $numbered
101.1 101.2 101.3 102.1 102.2 103 104 105.1 105.2 105.3 106
或者,这样保持原来的顺序
set count [dict create]
foreach num $unnumbered {dict incr count $num}
foreach key [dict keys $count] {
if {[dict get $count $key] == 1} {
set count [dict remove $count $key]
}
}
set numbered {}
foreach num [lreverse $unnumbered] {
if {![dict exists $count $num]} {
lappend numbered $num
} else {
lappend numbered "$num.[dict get $count $num]"
dict incr count $num -1
}
}
set numbered [lreverse $numbered]
puts $numbered
101.1 101.2 101.3 102.1 102.2 103 104 105.1 105.2 105.3 106
O(n) 解决方案(单循环),我认为它看起来更像您最初想要实现它的方式:
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list]
set previous ""
set subcounter 1
foreach current $unnumbered {
if {$previous == ""} {
# First, do nothing except set $current to $previous later below
} elseif {$previous == $current} {
lappend numbered $previous.$subcounter
incr subcounter
} else {
if {$subcounter > 1} {
lappend numbered $previous.$subcounter
} else {
lappend numbered $previous
}
set subcounter 1
}
set previous $current
}
if {$subcounter > 1} {
lappend numbered $current.$subcounter
} else {
lappend numbered $current
}
循环基本上在 numbered
列表中添加了一个迟到的数字,因此最后一个数字需要最后一个 if
。当然,这只有在您知道 unnumbered
已排序时才有效。
编辑:实际上这更近了!由于您已经可以获得 $previous
,因此您可以从列表的下一个元素开始循环,并在最后一个元素之后进行最后一次循环(请注意,如果 lindex
提供了一个 out范围索引,这使这里的事情变得更容易)。
set unnumbered [list 101 101 101 102 102 103 104 105 105 105 106]
set numbered [list]
set previous [lindex $unnumbered 0]
set subcounter 1
for {set i 1} {$i <= [llength $unnumbered]} {incr i} {
if {$previous == [lindex $unnumbered $i]} {
lappend numbered $previous.$subcounter
incr subcounter
} else {
if {$subcounter > 1} {
lappend numbered $previous.$subcounter
} else {
lappend numbered $previous
}
set subcounter 1
}
set previous [lindex $unnumbered $i]
}