tcl将计数器添加到相同的列表值

时间:2017-10-16 09:07:02

标签: list tcl counter

我有一个包含值的列表:

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”

我该如何解决这个问题?

3 个答案:

答案 0 :(得分:0)

问题是您的代码在将数字添加到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)foreachifincrlappendlassignlistllengthlmap (for Tcl 8.5)lmaplsearchlsortset

如果您没有lmap,请参阅上面的链接。如果您没有lassign,请使用

foreach {n indices} $item break

代替。

ETA 如果可以放宽“无单号码索引”要求,可以这样做:

set previous {}
lmap num $unnumbered {
    if {$num ne $previous} {
        set i 0
    }
    set previous $num
    format %d.%d $num [incr i]
}

另一种变体。这与杰里的第二个建议非常相似,但在我提交这个之前我没有看到这个,诚实。这假设$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]
}

答案 1 :(得分:0)

另一种方法:保持一个字数以保持你目前所看到的数量

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返回新计数的事实

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

答案 2 :(得分:0)

一个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提供了一个空白,我们会得到一个空白范围索引,这使事情变得更容易。)

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