【问题标题】:How to generate sequence of numbers in Tcl如何在 Tcl 中生成数字序列
【发布时间】:2016-11-21 00:01:54
【问题描述】:

我正在寻找一种根据输入fromtostep 参数生成数字列表的方法。

使用incr 不好,因为我还想支持floatdouble 号码。

例如,如果from=-0.3to=0.25step=0.1,我想生成列表-0.3 -0.2 -0.1 0 0.1 0.2。我在格式和舍入方面遇到问题。

【问题讨论】:

    标签: list numbers tcl generator sequence


    【解决方案1】:

    您对以下解决方案有何看法:

    proc ::General::Range {Start Stop {Step 1}} {
        if {$Step eq 0} {return Error-'ZeroStep'}
        if {$Start eq $Stop} {return $Start}
        set Range {}
        if {![string in integer $Step]} {
            # Double
            regexp {^\d+\.(\d+)$} $Step FullMatch ToFormat
            while {$Start <= $Stop} {
                lappend Range [string trimright $Start 0]
                set Start [format "%.${ToFormat}f" [expr {$Start + $Step}]]
            }
        } else {
            # Integer
            while {[expr {$Stop > 0 ? [expr {$Start <= $Stop}] : [expr {$Start >= $Stop}]}]} {lappend Range $Start; incr Start $Step}
        }
        return $Range
    }
    

    【讨论】:

    • 如果要和0比较,使用==; 0.0 不是eq 到 0。没有string in 命令,你可能是指string is。非整数步数不能为负数。 string trimright $Start 0 表示您将获得以小数点结尾的数字。从步长值获取格式精度会导致一些奇怪的格式。您不一定能根据Stop 的值来决定计数是向上还是向下。
    • 另外,你过度使用expr 以至于它使你的代码更难阅读。这样做并没有实际的好处。
    • @PeterLewerin:感谢您的见解!
    • @DonalFellows:感谢您的见解!
    【解决方案2】:

    这是一个使用 Tcl 协程按需生成范围内的下一个值的示例

    % proc range {from to step} {
        yield
        set value $from
        while {$value <= $to} {
            yield $value
            set value [expr {$value + $step}]
        }
    }
    % coroutine generator range -0.35 0.25 0.1
    % puts [generator]
    -0.35
    % puts [generator]
    -0.24999999999999997
    % puts [generator]
    -0.14999999999999997
    % puts [generator]
    -0.04999999999999996
    % puts [generator]
    0.050000000000000044
    % puts [generator]
    0.15000000000000005
    % puts [generator]
    
    % puts [generator]
    invalid command name "generator"
    

    正如多纳尔所说,这里我们看到了累积的浮点错误。应用他的方法:

    proc range {from to step} {
        yield
        set i 0
        while 1 {
            set value [expr {$from + $i * $step}]
            yield $value
            if {$value > $to} break
            incr i
        }
    }
    

    我们得到序列

    -0.35
    -0.24999999999999997
    -0.14999999999999997
    -0.04999999999999993
    0.050000000000000044
    0.15000000000000002
    0.2500000000000001
    

    【讨论】:

      【解决方案3】:

      如果您可以自己删除字符串前缀:

      proc genNums {{from 0} {to 1} {step .1} {prec 1}} {
          if {$step < 0} {
              set op ::tcl::mathop::>
          } else {
              set op ::tcl::mathop::<
          }
          for {set n $from} {[$op $n $to]} {set n [expr {$n + $step}]} {
              lappend res [format %.*f $prec $n]
          }
          return $res 
      }
      
      % genNums -0.3 0.25 0.1
      # => -0.3 -0.2 -0.1 0.0 0.1 0.2
      % genNums -0.3 0.25 0.1 2
      # => -0.30 -0.20 -0.10 0.00 0.10 0.20
      

      但如果你愿意,你可以设置它,以便你可以将字符串传递给命令:

      proc genNums args {
          array set params {from 0 to 1 step .1 prec 1}
          array set params [split [string map {= { }} $args]]
          if {$params(step) < 0} {
              set op ::tcl::mathop::>
          } else {
              set op ::tcl::mathop::<
          }
          for {set n $params(from)} {[$op $n $params(to)]} {set n [expr {$n + $params(step)}]} {
              lappend res [format %.*f $params(prec) $n]
          }
          return $res 
      }
      
      genNums from=-0.3 to=0.25 step=0.1
      # => -0.3 -0.2 -0.1 0.0 0.1 0.2
      % genNums from=-0.3 to=0.25 step=0.1 prec=2
      # => -0.30 -0.20 -0.10 0.00 0.10 0.20
      

      文档: + (operator), < (operator), array, expr, for, format, if, lappend, proc, return, set, split, string, Mathematical operators as Tcl commands

      【讨论】:

      • 这个函数不起作用,例如,使用from=-0.35to=0.75step=0.1。它产生-0.3 -0.2 -0.1 -0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7
      • @HardwareEng.:你期待什么序列?请注意,这是一个小数点:将格式设置为 %.2f 可能会更好。
      • @peter,也许添加一个scale=2 参数,您可以在格式命令中使用它。
      【解决方案4】:

      这是classic problem in computing。认真的。

      您需要做的是无论如何都使用整数迭代,然后按步长进行缩放。这样可以最大限度地减少错误。您还需要谨慎使用format

      set from -0.3
      set to 0.25
      set step 0.1
      for {set i 0} true {incr i} {
          set x [expr {$i*$step + $from}]
          if {$x > $to} break
          set printable [format "%.1f" $x]
          puts "$i => $printable"
      }
      

      【讨论】:

      • 这假定step 是正数;如果step 是否定的,那么测试的意义就需要改变。
      • 我是不是应该根据步长分辨率推导小数点位置?
      • 完全正确,但通常最简单的方法是硬编码。 (set decimalPlaces 1; format %.*f $decimalPlaces $x)
      猜你喜欢
      • 2017-02-13
      • 2011-04-14
      • 1970-01-01
      • 2023-01-09
      • 2017-01-17
      • 1970-01-01
      • 2015-12-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多