给定一个开始、结束和增量值,我想要一个向上和向下计数的算法

Posted

技术标签:

【中文标题】给定一个开始、结束和增量值,我想要一个向上和向下计数的算法【英文标题】:Given a start, end, and increment value, I want an algorithm that counts up and down 【发布时间】:2009-09-15 23:54:47 【问题描述】:

好的,我有一个想要淡入淡出(连续)的控件。为了淡化它,我在绘图例程中调整控件的透明度值。所以我设置了一个运行的计时器并调整了 m_transparency 成员。计时器运行,它应该在两个定义的值 m_start、m_end 之间来回扫描。这些可以是 0-255。

例如。 开始 30,结束 55,增量值 = 5。它看起来像:

30、35、40、45、55、50、45、40、35、30、35、40 ......

还应该处理开始 55,结束 30。

无论如何,我写了一个可怕的函数,它基本上测试了每个条件(我是向上还是向下移动,我是否到达了起点、终点、转身点等)。这很丑陋,我很尴尬(三重嵌套 if),但我想不出一种更清洁的方法来测试所有内容。有没有更简单的方法?

【问题讨论】:

@liori: 86 字符: 'f(intv,int x,int y,ints)*s=(*v==x||*v ==y)?-*s:*s;*v=(*v+*sy)?y:*v+*s;' ;) @martin: 70,但我需要一个额外的状态变量:'f(intv,int x,int y,int z,ints)*s= (*s+y)%(2*z);*v=x+(*s>z)?*s:z-*s;'。请参阅答案以获得解释。 或 72 没有状态值作为额外参数:'f(int*v,int x,int y,int z)static int s;s=(s+y)%(2 *z);*v=x+(s>z)?s:z-s;'. 有几个答案似乎还不错。但考虑到它是为了淡化,以及你的计时器触发的精确度,我可能会考虑另一种方法。我可以将正弦波缩放到所需范围,而不是线性上升和下降,并在需要时插入当前时间。 【参考方案1】:

下面的课程相当简单。您需要考虑您希望它在边界情况下的行为方式(例如,这将为 RangeScanner(30, 54, 5) 发出 55,但很容易将其更改为您想要的行为)。

类 RangeScanner

上市:
    RangeScanner(int start, int end, unsigned int inc):
        值(开始),
        较低(标准::分钟(开始,结束)),
        上(标准::最大值(开始,结束)),
        增量(INC),
        递增(开始 = 上 || 值 
    

【讨论】:

“递增”变量可以编码为“递增”变量的符号。 真的吗?负增量表示减量是一个很常见的习语。对于像这样简单的事情,我个人认为无关的变量会分散注意力。 YMMV。 作为一般规则,我更喜欢将不同的对象状态分开。它让一些白痴更难出现并在以后破坏它,并减少所需的错误检查量 - 考虑有人误解了参数并试图创建 RangeScanner(55, 30, -5)。【参考方案2】:

这就是我的处理方式,一个基于 C 的解决方案(当然可以在 C++ 中工作,但没有任何 C++ 功能):

int nextVal (int curr, int min, int max, int *pStep) 
    // Handle situations where you want to turn around.

    if ((curr == min) || (curr == max)) 
        *pStep = -(*pStep);
        return curr + *pStep;
    

    // Handle situation where you would exceed your bounds (just
    //   go to the bound).

    if (curr + *pStep < min) return min;
    if (curr + *pStep > max) return max;

    // Otherwise, just move within the bounds.

    return curr + *pStep;

 

int main(void) 
    int i;

    // Set up your variables here.

    int min = 30;
    int max = 55;
    int step = 5;

    // This is the running current value and we need to negate the step
    //   to force initial turn-around.

    int curr = min;
    step = -step;

    // Simple loop to demonstrate.

    printf ("%d\n", curr);
    for (i = 19; i > 0; i--) 
        curr = nextVal (curr, min,max,&step);
        printf ("%d\n", curr);
    

    return 0;

这个输出:

30, 35, 40, 45, 50, 55, 50, 45, 40, 35, 30, 35, 40, 45, 50, 55, 50, 45, 40, 35

为您的测试用例。它还可以智能地处理范围不是增量的精确倍数的序列(例如,[7,40,10]):

7, 17, 27, 37, 40, 30, 20, 10, 7, 17, 27, 37, 40, 30, 20, 10, 7, 17, 27, 37

【讨论】:

if ((curr = max)) 没必要,@ralu,该值永远不允许超出界限,如果出于某种奇怪的原因,调用者在这些界限之外开始使用它,第一步将把它带回来。 【参考方案3】:

以下是作为time 函数的线性上升-下降的基本方法。

int triangle(int time) 
    int d = m_end - m_start;
    return m_start + abs(((m_increment * time + d) % 2*d) - d);

【讨论】:

【参考方案4】:

如何使用正弦函数来获得更自然的渐变效果...

#include <math.h>

class SineFader

public:
  SineFader(int min, int max)
    : base((double)min + ((double)(max - min) / 2))
    , range((double)(max - min) / 2)
    , theta(4.71)
    , speed(0.1)
   

  int getValue()
  
    theta += speed;
    return (int)(base + (range * sin(theta)));
  

private:
  double base, theta, range, speed;
;

这是您在代码中使用它的方式:

SineFader myfade(0, 55);

void onTimer()

   setTransparency(myfade.getValue());

【讨论】:

好主意,但你应该检查你的数学。当 theta = 4.7 时会发生什么? 这就是我从袖口敲出代码的结果。感谢您发现那个 bk1e。我已经修复了一点,但它使构造函数更加复杂。 有没有以毫秒为单位设置脉冲周期而不是速度? 这取决于您调用 onTimer() 的频率以及您设置的速度值。公式是每闪一次 (timer_interval * ((2 * PI) / speed))。【参考方案5】:

我的版本重复了限制值:... 50, 55, 55, 50, ..., 35, 30, 30, 35, ...

struct UpDown 
    int lo_val, hi_val, curr_val;
    int step;
;

void fade_in_out(struct UpDown *x) 
    x->curr_val += x->step;
    if ((x->curr_val > x->hi_val) || (x->curr_val < x->lo_val)) 
        x->step *= -1;
        x->curr_val += x->step;
    


int main(void) 
    struct UpDown myControl = 30, 55, 30, 5;
    for (;;) 
        fade_in_out(&myControl);
    
    return 0;

【讨论】:

【参考方案6】:

这个很灵活,您可以从范围内的任何位置开始,也可以通过将 dir 设置为 1 或 -1 来指定要开始的方向。

class Fader

public:
 Fader(int min, int max, int start, int dir = 1)
  : _min(min)
  , _max(max)
  , _val(start)
  , _dir(dir)
  

 int getValue()
 
   if(_val <= min || _val >= _max)
     _dir = -_dir;

   _val += _dir;
   return _val;
  

private:
  int _min, _max, _dir, _val;
;

【讨论】:

这实际上是相当脆弱的。试试推子 (0, 10, 20, 2)。另外,我希望 Fader(0, 10, 0, 1).getValue() 返回 0,但它返回 1(即“start”不是返回的第一个值)。 确实如此,但如果初始值合理,它就可以正常工作。您可以在构造函数中添加对错误初始值的检查。【参考方案7】:

我就是这样做的。看起来很简单。

int computeTransparency(int start, int end, int& increment, int oldVal)

    if (start > end)
    
        swap(start, end);
    

    int newVal = oldVal + increment;
    bool goingUp = increment > 0;

    if (goingUp)
    
        if (newVal >= end)
        
            newVal = end;
            increment = -increment;
        
    
    else
    
        if (newVal <= start)
        
            newVal = start;
            increment = -increment;
        
    

    return newVal;

【讨论】:

【参考方案8】:

这是另一种方法,使用辅助函数在 start 和 start+span 之间以增量的增量弹跳:

// Goes from start to start + span, and repeats.
int ramp(start, increment, span) 
  static int lastval = start;
  lastval += increment;
  lastval = lastval % (span + 1);
  return lastval;


// Goes from start up to start+span and back down, and repeats.
int bounce(start, increment, span) 
  val = ramp(0, increment, 2*span - 1);
  if (val > span) val = span - val;
  return val + start;

本质上,这是在创建一条不断增加的线,然后使用 MOD 函数将其切割成锯齿,然后将锯齿的上半部分向下反射以形成锯齿形。

请注意,与所介绍的所有其他实现不同(除非我错过了一个),即使增量不均分 - 例如,对于 0 ,跨度为 10,步长为 3,它将上升 3 到 3,上升 3 到 6,上升 3 到 9,上升 1,下降另一个 2 到 8,下降 3 到 5,下降 3 到 2,向下 2 向上 1 到 1,依此类推 - 3, 6, 9, 8, 5, 2, 1, 4, 7, 10, 7, 4, 1, .... 它还保证即使增量远大于跨度或为负数,数字始终在正确的范围内并做正确的事情。

(您可能应该重写它以将诸如 start、increment 和 span 之类的东西放在静态存储中而不是函数参数中,因为如果它们发生变化,这会变得相当混乱,但我对此感到很懒惰。而且,真的,它仍然会落在正确的范围内,并且如果它们发生变化,它会做一些明智的事情——你可以在任何时候改变增量,它会加速或减速而不跳动。)

【讨论】:

以上是关于给定一个开始、结束和增量值,我想要一个向上和向下计数的算法的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的函数向返回未定义的对象数组添加键和增量值

想要文本框中的向上和向下箭头来更改列表框选择

是否可以在网页滚动视图中有一个指示器 - 我的意思是我想要一个可以按住并向上/向下滚动的支架/按钮?

2021-06-04:给定三个参数:二叉树的头节点head,树上某个节点target,正数K,从target开始,可以向上走或者向下走。返回与target的距离是K的所有节点。

使用javascript在dom树中向上或向下移动一个元素

使用向上和向下的文本框键在 datagridview 中移动