获取数组中的下一个键

Getting the next key in an array

我的目标是找到数组中的下一个键...在我的数据下方:

# Index increment may change, there is not necessarily continuity like this example.
# My $index can be 1,2,3,4,8,12,25,32...
# but the size of my array is about 100,000 elements.
for {set index 1} {$index < 100000} {incr index} {
    set refdata($index,Pt,X) [expr {10 + $index}] 
}

我需要知道下一个键才能构建几何线...我没有在帮助中找到允许我找到数组的下一个键的命令所以我在下面创建了自己的函数:

proc SearchNextKeyArrayElement {dataarray mykey} {
    upvar $dataarray myarray

    set mydata [lsort -dictionary [array names myarray]]
    set index  [lsearch $mydata $mykey]

    if {$index > -1} {
        return [lindex $mydata [expr {$index + 1}]]
    }

    return ""
}

foreach k [lsort -dictionary [array names refdata]] {
    if {[string match "*,Pt,*" $k]} {
        set nextkey [SearchNextKeyArrayElement refdata $k]
    }
}

而且需要很长时间...
array nextelement命令也许是解决方案...
但我不明白如何使用它?

这是一个例子:

  • 开始搜索 array startsearch
  • array anymore 为真时循环
  • 使用 array nextelement
  • 获取下一个密钥
  • 整理 array donesearch
  • 为了安全起见使用try {} catch {} finally
# array foreach
# to be subsumed in Tcl 8.7 by `array for`
# https://core.tcl.tk/tips/doc/trunk/tip/421.md
#
# example:
#   array set A {foo bar baz qux}
#   array foreach {key val} A {puts "name=$key, value=$val"}
#
# A note on performance: we're not saving any time with this approach.
# This is essentially `foreach name [array names ary] {...}
# We are saving memory: iterating over the names versus extracting
# them all at the beginning.
#
proc array_foreach {vars arrayName body} {
    if {[llength $vars] != 2} {
        error {array foreach: "vars" must be a 2 element list}
    }
    lassign $vars keyVar valueVar

    # Using the complicated `upvar 1 $arrayName $arrayName` so that any
    # error messages propagate up with the user's array name
    upvar 1 $arrayName $arrayName \
            $keyVar    key \
            $valueVar  value

    set sid [array startsearch $arrayName]
    # If the array is modified while a search is ongoing, the searchID will
    # be invalidated: wrap the commands that use $sid in a try block.
    try {
        while {[array anymore $arrayName $sid]} {
            set key [array nextelement $arrayName $sid]
            set value [set "${arrayName}($key)"]
            uplevel 1 $body
        }
    } trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
        puts stderr [list $e]
        dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
        return -options $e
    } finally {
        array donesearch $arrayName $sid
    }
    return
}

一般来说,Tcl 数组根本没有顺序;他们可以更改对数组或其任何元素的任何修改的顺序。迭代数组的命令(array forarray getarray names 和迭代命令 array startsearch/array nextelement/array anymore)仅有效与当前订单。但是,您可以使用 array names 将元素名称放入 Tcl 列表(保留顺序),对这些元素进行排序以获得您要迭代的顺序,然后使用 foreach那。只要您不添加或删除元素,就没问题。 (添加元素在某种程度上也可以;只是您不会在迭代中看到它们。)

foreach key [lsort -dictionary [array names myarray]] {
    ProcessElement $key $myarray($key)
}

相比之下,尝试从一个元素转到下一个元素会造成很大伤害;该操作未公开。


使用迭代命令是这样完成的:

set s [array startsearch myarray]
while {[array anymore myarray $s]} {
    set key [array nextelement myarray $s]
    ProcessElement $key $myarray($key)
}

请注意,您没有对搜索进行排序的选项。您不会在生产代码中看到这些使用太多;做 array namesarray get 通常更好。现在(好吧,8.7 仍处于 alpha 阶段)你还有 array for:

array for {key value} myarray {
    ProcessElement $key $value
}

对大型数组有效,但仍然不允许排序;支持直接排序需要在数组背面使用不同类型的存储引擎。

这就是速度慢的原因:您为 foreach 命令对数组名称排序一次 ,然后为每个元素再次排序 。排序一次并缓存它,然后你可以更有效地迭代它

set sorted_names [lsort -dictionary [array names refdata -glob {*,Pt,*}]]
set len [llength $sorted_names]
for {set i 0; set j 1} {$i < $len} {incr i; incr j} {
    set this_name [lindex $sorted_names $i]
    set next_name [lindex $sorted_names $j]
    # ...
}