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]
}