【问题标题】:Is there a better way to determine multiple ranges of character?有没有更好的方法来确定多个字符范围?
【发布时间】:2016-07-13 04:55:24
【问题描述】:

我目前正在用 C 语言编写代码,它从整个 ASCII 可用字符中选择符号和数字。 作为程序员的初学者,我通常是这样做的

if ((i > 25 && i < 50) || (i > 100 && i < 200)) { contents } 

对于变量i 介于 25~50、100~200(不包括)之间以适应条件。

如果我想设置多个范围,如 32~64(! to @) 和 91~96([ to `) 和 123~126({ to ~) 那么会有更好的(意思是更短或更简单的代码)还是我应该坚持这种方法,像上面的代码一样继续添加每个范围?

【问题讨论】:

  • 一个提示,也可以使用 ascii 符号代替数字
  • 好吧,我建议你定义一个函数in_range(int min, int max)并调用这个函数。
  • 我通常使用掩码:if ((chr &amp; mask) == cond) - 它允许选择任何范围:所有奇数符号 (mask = 0x01; cond = 0x01),ASCII table (mask = 0x0F; cond = 0x00) 第一行的所有符号。通过结合这些检查,我可以实现任何过滤器。
  • 如果你想要! to @ 那么为什么不使用'!''@' 而不是32 和64?更具可读性和精确性
  • 我会将您的代码重写为:((25 &lt; i &amp;&amp; i &lt; 50) || (100 &lt; i &amp;&amp; i &lt; 200))。以直观易懂的方式始终如一地编写这种逻辑将减少您的认知负担并使代码更易于阅读。

标签: c range ascii


【解决方案1】:

对于您的具体情况,&lt;ctype.h&gt; collection of functions 可以

if (isprint(i) && !isalpha(i))

额外的好处:它甚至可以在非 ascii 系统上运行。

【讨论】:

  • 希望我能找到在其他需要多范围的情况下做到这一点的方法,但现在这已经足够整洁了。
  • 小心,这些是依赖于语言环境的,这可能是也可能不是实际需要的(例如,如果我正在解析一种编程语言,通常我不希望它是语言环境-依赖)。 OTOH,它们在“C”语言环境中按预期工作,这是您无论如何都应该使用的唯一一种。
【解决方案2】:

您可以编写一个函数来检查该值是否属于任何给定范围:

struct Range {
        int min;
        int max;
};

bool in_ranges(int character, struct Range *ranges, size_t num_ranges) {
        for(size_t i = 0; i < num_ranges; ++i) {
                if(ranges[i].min < character && character < ranges[i].max)
                        return true;
        }
        return false;
}

int main() {
        struct Range rngs[] = {{25,50}, {100,200}};
        bool at_sign_si_in_range = in_ranges('@', rngs, 2);
        return 0;
}

它使编辑范围更加简单并提高了可读性。 此外,如果您继续像示例中那样在条件子句中编写所有范围,请考虑检查范围,如

lower_bound < value && value < upper_bound

它看起来像数学符号 (x &lt; a &lt; y),而且似乎更易于阅读。

【讨论】:

  • 老实说,除了可能效率低下之外,它看起来根本没有可读性;最重要的是,没有任何迹象表明范围是[,](通常是常用的说法)、[,)(通常在编程中)还是(,)(很少使用,但实际上是需要的)在这种情况下)。这就是为什么我主张保持这种逻辑要么非常明确,要么非常本地化——如果你需要在实用函数中不断跳来跳去,逐个查找就更加困难了。
  • @MatteoItalia 调试函数比调试宏更容易。它们也是不安全的:MACRO( i++ , j ) 等...过早的优化是什么...?代码也非常易读,意图清晰。关于包含/排除范围的部分是一个稻草人论点。如果它完全相关,您只需将后缀包含/排除添加到函数名称。
  • @2501:你没有得到它;重点很简单:如果你不追求一般性并且你想要简洁,你可以使用我写的宏;它有所有类型的限制正是,因为它是非常临时的,但它非常简洁并且完全符合这个目的。 i++ 参数适用于任何宏,并且任何半体面的 C 程序员都知道得更好 - 特别是因为它是一个宏并且它的参数被多次评估这一事实非常清楚,因为定义是正确的那里。相反,如果我正在编写像您这样的函数,那是因为 ...
  • 我将把这两件事留在这里:stackoverflow.com/q/652788/4082723#define B(l, h) ((l)&lt;i) &amp;&amp; (i&lt;(h)) || ... if(B(25,50) B(100,200) B(220, 240) 0),让读者决定哪个是最糟糕的。
  • @2501 我正在努力争取通用性 - 这将保留在某种共享标头中,供我在未来的类似计算中使用。现在,我拒绝相信在你的程序中你只会检查(,) 范围(如果有的话,检查次数最多的范围类型是[,))。所以,你会想要in_ranges_inc_incin_ranges_inc_excin_ranges_exc_excin_ranges_exc_inc 或类似的东西。但是现在,你对完整输入表达式有什么好处?你失去了灵活性(OP想要检查的范围实际上更容易用一些......
【解决方案3】:

如果您使用单字节字符,则可以使用一组标志来获得更好的性能,设置单个位或整个字节以指示其中一个范围内的字符值。

如果您正在为支持 SSE 4.2 指令的 Intel 处理器编写代码,您可能需要考虑使用 PCMPISTRI 或类似功能,它可以在一条指令中将最多 16 个单字节字符与最多 8 个不同的范围进行比较。

【讨论】:

    【解决方案4】:

    我的回答是“视情况而定”。 :)

    如果isalpha()ctype.h 的朋友做你想做的事,那么绝对使用他们。

    如果不是……

    如果您只有两个范围,如您的示例 sn-p,我认为它看起来不会太混乱。如果有更多,可以将范围测试放在(内联)函数中以减少一次可见的布尔值:

    if (in_range(val, a1, b1) || in_range(val, a2, b2) || ... )
    

    (如果您觉得需要节省屏幕空间,也可以将其命名为 B(n,a,b)。)

    如果范围可能在运行时发生变化,或者有很多范围,请将限制放在 struct 中并循环遍历这些范围。如果确实有很多,请对列表进行排序并对其进行智能处理,例如对下限(或其他)进行二分搜索。但对于少数人,我不会打扰。

    如果允许值的总范围很小(例如值为 0..255 的无符号字符),但单独的“范围”的数量很大(“所有具有素值的”),则制作一个表(位图) 的值,并对此进行测试。以您喜欢的任何方式生成表格。 (isalpha()大概是这样实现的)

    unsigned char is_prime[256] = {0, 0, 1, 1, 0, 1, 0, 1, 
        ...};
    
    if (is_prime[val]) { ...
    

    【讨论】:

      【解决方案5】:

      你可以写这样的函数:

      bool withinscope(int num, int begin, int end){
          if(num > begin && num < end)
              return true;
          return false;
      }
      

      那么你就可以使用这个功能,保持代码简洁明了。

      【讨论】:

      • 布尔值是普通值,不仅仅是if 守卫时使用的东西。可以直接return (num &gt; begin &amp;&amp; num &lt; end).
      【解决方案6】:

      您可以在宏或内联函数中隐藏 l&lt;x &amp;&amp; x&lt;h 的重复项,但我发现它很少值得这样做 - 它不如 Python 的 l&lt;x&lt;h 语法可读,并且一旦您开始使用它就会很快失控具有所有包容性边界可能性的宏。要么你最终得到一个非常长的命名约定(between_inc_incbetween_inc_exc,......这有点失败了首先考虑检查)或者你让读者想知道你的范围检查(“between(i, 50, 100).. . 它是[,) 范围吗?[,] 一个?(检查代码)不,它是(,)"),如果您正在寻找一个接一个的错误,那就太糟糕了。

      OTOH,众所周知,我滥用“单字母宏”,我准确定义了它们需要的位置和方式,之后立即未定义。尽管它们可能看起来很丑,但关键是它们非常本地化,并且完全按照需要完成,因此不会浪费时间查找它们,没有神秘参数,它们可以分解出大部分的重复计算。

      在你的情况下,如果列表很长,我可以这样做

      #define B(l, h) ((l)<i) && (i<(h)) ||
      
      if(B(25,50) B(100,200) B(220, 240) 0)
      ... 
      #undef B
      

      (永远不要在标题中这样做!)

      更好的可读性是使用字符文字而不是 ASCII 数字:例如,如果您想要 a-z 范围,请执行 'a'&lt;=i &amp;&amp; i&lt;='z'

      您似乎想要排除字母和不可打印字符:您可以使用

      if((' '<=i && i<'A') || (i>'Z' && i<'a') || ('z'<i && i<=126))
      

      【讨论】:

      • 啊,当谈到宏时,你肯定会投反对票!
      • 我认为这是 C 中宏滥用的一个例子。如果可以,请避免使用它。这是一个很好的现代方法:stackoverflow.com/a/38343152/4082723
      • @2501:由于我在上面评论中概述的原因,IMO 这种方法更糟糕。
      • 你的宏会在例如:B(i++,2), B(i&amp;1,2) , B(i?2:3,1) 等时爆炸性地爆炸。这太不安全了,甚至都不好笑。根据定义,这是宏滥用:#define B(l, h) ((l)&lt;i) &amp;&amp; (i&lt;(h)) || 这样的代码:if(B(25,50) B(100,200) B(220, 240) 0) 不会通过任何严肃公司的代码审查。
      • 这太恶心了,千万不要这样做
      【解决方案7】:
      class RangeCollection
      {
          std::vector<int> ranges;
      public:
          void AddRange(int lowerBound, int upperBound)
          {
              vector.push_back(lowerBound);
              vector.push_back(upperBound);
          }
      
          bool IsInRange(int num)
          {
              for(int i=0; i<ranges.size()-1; i+=2)
              {
                  if(num>ranges[i] && num<ranges[i+1])return true;
              }
           return false;
          }
      };
      

      您可以调用 AddRange 来添加任意数量的范围,然后您可以检查一个数字是否在范围内。

      RangeCollection rc;
      rc.AddRange(20,25);
      rc.IsInRange(22);//returns true
      

      【讨论】:

      • 应该将循环 2 增加 2。或者使用 std::pairs。
      • 使用明确的配对类型会是更好的编码实践,因为它明确表明这些数字一起出现,并且更难错误地访问来自不同配对的数字。
      • @zstewart 真诚地这也是我的想法,但我没有花时间更新这个答案。确实,创建一个明确地将这些数字作为对的类型是一种绝对优越的做法!
      • 关于 C 而不是 C++ 的问题,-1
      猜你喜欢
      • 2011-03-22
      • 2020-09-18
      • 2011-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多