检查拼写的数字是否在C ++范围内

时间:2012-09-24 13:38:48

标签: c++ algorithm numbers

我想在输入部分输入的同时检查范围列表(min,max)的(数字)输入;换句话说,我需要一个优雅的算法来检查一个数字的前缀与一个范围(不使用正则表达式)。

示例测试用例:

 1 is in (  5,   9) -> false
 6 is in (  5,   9) -> true
 1 is in (  5,  11) -> true  (as 10 and 11 are in the range)
 1 is in (  5, 200) -> true  (as e.g. 12 and 135 are in the range)
11 is in (  5,  12) -> true
13 is in (  5,  12) -> false 
13 is in (  5,  22) -> true
13 is in (  5, 200) -> true  (as 130 is in the range)
 2 is in (100, 300) -> true  (as 200 is in the range)

你有什么想法吗?

15 个答案:

答案 0 :(得分:27)

我认为输入是可以接受的,当且仅当:

  • 它是转换为字符串
  • 的下限的前缀子字符串

  • 输入后跟任意数量的附加零(可能没有)都属于
  • 范围

第一条规则是必需的,例如13 is in range (135, 140)。例如,第二条规则是必需的。 2 is in range (1000, 3000)

可以通过一系列乘以10来有效地测试第二条规则,直到缩放的输入超过上限。

答案 1 :(得分:8)

迭代公式:

bool in_range(int input, int min, int max)
{
  if (input <= 0)
    return true;    // FIXME handle negative and zero-prefixed numbers
  int multiplier = 1;
  while ((input + 1) * multiplier - 1 < min)         // min <= [input]999
    multiplier *= 10;    // TODO consider overflow
  return input * multiplier <= max;                  //        [input]000 <= max
}

更简单[编辑:更有效;见下文]方法是使用截断整数除法:

bool in_range(int input, int min, int max)
{
  if (input <= 0)
    return true;
  while (input < min) {
    min /= 10;
    max /= 10;
  }
  return input <= max;
}

测试和分析:

#include <iostream>
#include <chrono>

bool ecatmur_in_range_mul(int input, int min, int max)
{
  int multiplier = 1;
  while ((input + 1) * multiplier - 1 < min)         // min <= [input]999
    multiplier *= 10;    // TODO consider overflow
  return input * multiplier <= max;                  //        [input]000 <= max
}

bool ecatmur_in_range_div(int input, int min, int max)
{
  while (input < min) {
    min /= 10;
    max /= 10;
  }
  return input <= max;
}

bool t12_isInRange(int input, int min, int max)
{
    int multiplier = 1;
    while(input*multiplier <= max)
    {
        if(input >= min / multiplier) return true;
        multiplier *= 10;
    }
    return false;
}

struct algo { bool (*fn)(int, int, int); const char *name; } algos[] = {
{ ecatmur_in_range_mul, "ecatmur_in_range_mul"},
{ ecatmur_in_range_div, "ecatmur_in_range_div"},
{ t12_isInRange, "t12_isInRange"},
};

struct test { int input, min, max; bool result; } tests[] = {
{  1,   5,   9, false },
{  6,   5,   9, true },
{  1,   5,  11, true }, // as 10 and 11 are in the range
{  1,   5, 200, true }, // as e.g. 12 and 135 are in the range
{ 11,   5,  12, true },
{ 13,   5,  12, false },
{ 13,   5,  22, true },
{ 13,   5, 200, true }, // as 130 is in the range
{  2, 100, 300, true }, // as 200 is in the range
{ 13, 135, 140, true }, // Ben Voigt
{ 13, 136, 138, true }, // MSalters
};
int main() {
    for (auto a: algos)
        for (auto t: tests)
            if (a.fn(t.input, t.min, t.max) != t.result)
                std::cout << a.name << "(" << t.input << ", " << t.min << ", " << t.max << ") != "
                    << t.result << "\n";

    for (auto a: algos) {
        std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
        for (auto t: tests)
            for (int i = 1; i < t.max * 2; ++i)
                for (volatile int j = 0; j < 1000; ++j) {
                    volatile bool r = a.fn(i, t.min, t.max);
                    (void) r;
                }
        std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
        std::cout << a.name << ": "
            << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << '\n';
    }
}

令人惊讶的是(至少对我而言)迭代分工最快出现:

ecatmur_in_range_mul: 17331000
ecatmur_in_range_div: 14711000
t12_isInRange: 15646000

答案 2 :(得分:3)

bool isInRange(int input, int min, int max)
{
    int multiplier = 1;
    while(input*multiplier <= max)
    {
        if(input >= min / multiplier) return true;
        multiplier *= 10;
    }
    return false;
}

它通过了所有的测试用例。

答案 3 :(得分:2)

一个简单的解决方案是生成范围内的所有N位前缀。因此,对于11 is in ( 5, 12),您需要5到12之间所有数字的两位数前缀。显然,这只是10,11和12。

通常,对于数字X到Y,可以通过以下算法获得可能的N位前缀:

X = MIN(X, 10^(N-1) ) ' 9 has no 2-digit prefix so start at 10
Y = Y - (Y MOD 10^N)  ' 1421 has the same 2 digit prefix as 1400
WHILE (X < Y)
  LIST_PREFIX += PREFIX(N, X) ' Add the prefix of X to the list.
  X += 10^(TRUNCATE(LOG10(X)) - N+1) ' For N=2, go from 1200 to 1300

答案 4 :(得分:2)

(input >= lower_bound) && input <= upper_bound

OR

(f(input) >= lower_bound) && (f(input) <= upper_bound)

OR

(lower_bound - f(input) < pow(10, n_digits_upper_bound - n_digits_input)) && 
(lower_bound - f(input) > 0)

where

f(input) == (input * pow(10, n_digits_upper_bound - n_digits_input))


 1 is in (  5,   9) -> 1 * pow(10,0) -> same                 -> false
 6 is in (  5,   9)                                          -> true
 1 is in (  5,  11) -> 1 * pow(10,1)  -> 10 is in (5,11)     -> true
 1 is in (  5, 200) -> 1 * pow(10,2)  -> 100 is in (5, 200)  -> true
11 is in (  5,  12)                                          -> true
13 is in (  5,  12) -> 13 * pow(10,0) -> same                -> false 
13 is in (  5,  22)                                          -> true
13 is in (  5, 200)                                          -> true
 2 is in (100, 300) -> 2 * pow(10,2) -> 200 is in (100,300)  -> true
 4 is in (100, 300) -> 4 * pow(10,2)  -> 400 is in (100,300) -> false
13 is in (135, 140) -> 135 - 130                             -> true
14 is in (135, 139) -> 135 - 140                             -> false

答案 5 :(得分:2)

我更喜欢使用已经实现的算法的方法。虽然很多其他解决方案使用10的递归除法,但我认为最好使用10基本对数,其复杂度为O(1),因此整个解决方案的复杂性为O(1)

让我们将问题分成两部分。

第一个部分将处理number * 10^n介于minmax之间且至少有一个n的情况。这样我们就可以检查number = 12min,max = 11225,13355是否x = 12000 = 12*10^3介于minmax之间。如果此测试检出,则表示结果为True

第二个部分将处理numbermin开始时max开始的案例。例如,如果number = 12min,max = 12325,14555,则第一次测试将失败,因为12000不在minmax之间(并且将失败所有其他数字)任何12*10^n n。但第二次测试会发现1212325的开头并返回True

第一

如果第一个x = number*10^n等于或大于min,则检查是否小于或等于maxmin <= x <= max, where x is number*10^n for any integer n)。如果它大于max,那么所有其他x es将会更大,因为我们采取最小的。

log(number*10^n) > log(min)
log(number) + log(10^n) > log(min)
log(number) + n > log(min)
n > log(min) - log(number)
n > log(min/number)

要获得要与之比较的数字,我们只计算第一个令人满意的n

n = ceil(log(min/number))

然后计算出数字x

x = number*10^n

第二

我们应该检查我们的数字是否是任一边界的字面开头。

我们只计算x,以与number相同的数字开头,并在末尾填充0 s,与min的长度相同:

magnitude = 10**(floor(log10(min)) - floor(log10(number)))
x = num*magnitude

然后检查minx差异(幅度范围)是否小于1且大于或等于0

0 <= (min-x)/magnitude < 1

因此,如果number121min132125,则magnitude1000x = number*magnitude121000min - x提供的132125-121000 = 11125应小于1000(否则min开头会大于121),因此我们将其与{{1}进行比较通过除以它的值并与magnitude进行比较。如果1min,则可以,但如果121000min则不行,这就是1220000 <=的原因。

相同的算法适用于< 1

伪代码

将所有内容合并到伪代码中给出了这个算法:

max

可以通过避免重复计算def check(num,min,max): # num*10^n is between min and max #------------------------------- x = num*10**(ceil(log10(min/num))) if x>=min and x<=max: return True # if num is prefix substring of min #------------------------------- magnitude = 10**(floor(log10(min)) - floor(log10(num))) if 0 <= (min-num*magnitude)/magnitude < 1: return True # if num is prefix substring of max #------------------------------- magnitude = 10**(floor(log10(max)) - floor(log10(num))) if 0 <= (max-num*magnitude)/magnitude < 1: return True return False 来优化此代码。此外,在最终解决方案中,我将从浮动范围转到整数范围(log10(num)),然后执行所有比较而不进行除法,即magnitude = 10**int(floor(log10(max)) - floor(log10(num))) - &gt; 0 <= (max-num*magnitude)/magnitude < 1。这样可以减少四舍五入错误的可能性。

此外,可以将0 <= max-num*magnitude < magnitude替换为magnitude = 10**(floor(log10(min)) - floor(log10(num))),其中magnitude = 10**(floor(log10(min/num)))仅计算一次。但我不能证明它总会带来正确的结果,我也不能反驳它。如果有人能证明这一点,我将非常感激。

测试(在Python中):http://ideone.com/N5R2j(您可以编辑输入以添加其他测试)。

答案 6 :(得分:2)

给定值 n ,从半开放范围[ n n + 1)开始,按以下顺序进行:

  • [10 n ,10( n + 1))
  • [100 ,100( n + 1))
  • [1000 ,1000( n + 1))
  • ...

继续,直到迭代范围与目标范围重叠,或者两个范围不再重叠。

#include <iostream>

bool overlaps(int a, int b, int c, int d) {
  return a < c && c < b || c < a && a < d;
}

bool intersects(int first, int begin, int end) {
  int last = first + 1;
  ++end;
  while (first <= end) {
    if (overlaps(first, last, begin, end))
      return true;
    first *= 10;
    last *= 10;
  }
  return false;
}

int main(int argc, char** argv) {
  std::cout << std::boolalpha
    << intersects( 1,   5,   9) << '\n'
    << intersects( 6,   5,   9) << '\n'
    << intersects( 1,   5,  11) << '\n'
    << intersects( 1,   5, 200) << '\n'
    << intersects(11,   5,  12) << '\n'
    << intersects(13,   5,  12) << '\n'
    << intersects(13,   5,  22) << '\n'
    << intersects(13,   5, 200) << '\n'
    << intersects( 2, 100, 300) << '\n'
    << intersects(13, 135, 140) << '\n';
}

使用范围是必要的,以防止错过病例。考虑 n = 2且目标范围为[21,199]。 2不在范围内,所以我们乘以10; 20不在范围内,所以我们再乘以10; 200不在范围内,也不在任何更高的数字,因此朴素算法以假阴性终止。

答案 7 :(得分:2)

在考虑@Ben Voigt漂亮解决方案的证据时,我找到了这个新的简单解决方案:

让我们回到小学,我们进行数字比较。 问题是:检查数字“ A ”是否在数字“ B ”和数字“ C ”的范围内

解决方案:在数字的左侧添加必要的零(所以我们在所有数字中都有相同的位数)我们从最左边的数字开始。将它与其他两个数字中的等效数字进行比较。

  • 如果 A 中的数字小于 B 中的数字或 C 中的数字,则 > A 不在范围内。

  • 如果不是,我们将使用 A 中的下一个数字以及来自 B C 的等效数字重复此过程。

重要问题:为什么我们不在那里停下来?为什么我们检查下一个数字?

重要提示:因为 A 中的数字介于 B C 之间的数字是O.K. 到目前为止但还没有足够的理由做出决定! (显然对吧?)

反过来,这意味着可能有一组数字可能会使 A 超出范围。

AND,LIKEWISE

可能有一组数字可以将 A 放在范围内

这是另一种说法 A 可能是该范围内数字的前缀。

这不是我们想要的吗?! :d

算法的主干基本上是每个输入事件的简单比较:

  1. min 的左侧添加一些零(如有必要),以便 min max 的长度相等。
  2. 将输入 A min max 的等效数字进行比较(剪切 min 的相应数字和来自 max 不对
  3. 输入 A &lt; = max 的相应部分 AND &gt; = min 的相应部分? (不:返回false,yes:return true)
  4. false和true在这里表达了“到现在为止”的情况,正如问题所要求的那样。

答案 8 :(得分:1)

所有困难情况都是下限比上限少的数字。只需将范围分成两个(或三个)即可。如果AB是集合A和集合B,那么x in AB意味着x in Ax in B。所以:

13 is in (5, 12) =&gt; 13 is in (5, 9)13 is in (10, 12)

13 is in (5, 120) =&gt; 13 is in (5, 9)13 is in (10, 99)13 is in (100, 120)

然后,截断以匹配长度。即。

13 is in (5, 120) =&gt; 13 is in (5, 9) OR 13 is in (10, 99)13 is in (10 0 , 12 0 )

在第二次重写之后,它变成了一个简单的数字检查。请注意,如果您显示范围10,99,那么您拥有所有可能的2位前缀,并且实际上不需要检查,但这是一种优化。 (我假设我们忽略前缀00-09)

答案 9 :(得分:1)

是另一个答案。对于输入X和界限MIN和MAX

WHILE (X < MIN)
  IF X is a prefix of MIN
    x = 10*x + next digit of MIN
  ELSE
    x = 10*x
RETURN (x>= MIN && x<=MAX)

这可以通过“键入”下一个最低位来实现。所以硬案例13 in (135, 140)增加了5来产生135,而不是零。

答案 10 :(得分:1)

无论您选择哪种实施方法,都应该考虑构建大量的单元测试。因为您正在提出问题,就像您为测试驱动开发(TDD)编写测试一样。所以我建议,在等待合适的算法弹出堆栈溢出时,编写单元测试:

如果您提供的示例不会在示例中产生结果,则使测试失败。写一些其他限制测试用例只是为了确定。然后,如果您碰巧使用错误或错误的算法,您很快就会知道。一旦你的考试通过,你就会知道你已经达到了目标。

另外,它可以保护您免受未来的任何回归

答案 11 :(得分:1)

也许我在思考这个但是假设Min-Max整数范围都是正数(即大于或等于零),这个代码块应该很好地解决这个问题:

bool CheckRange(int InputValue, int MinValue, int MaxValue)
{
    // Assumes that:
    //    1. InputValue >= MinValue 
    //    2. MinValue >= 0
    //    3. MinValue <= MaxValue 
    //
    if (InputValue < 0)         // The input value is less than zero
        return false;
    //
    if (InputValue > MaxValue)  // The input value is greater than max value
        return false;
    //
    if (InputValue == 0 && InputValue < MinValue)
        return false;       // The input value is zero and less than a non-zero min value
    //
    int WorkValue = InputValue; // Seed a working variable
    //
    while (WorkValue <= MaxValue)
    {
        if (WorkValue >= MinValue && WorkValue <= MaxValue)
            return true; // The input value (or a stem) is within range
        else
            WorkValue *= 10; // Not in range, multiply by 10 to check stem again
    }
    //
    return false;
}

答案 12 :(得分:0)

好的,派对有点晚了,但是这里......

请注意,我们在这里讨论用户输入,因此仅仅// TODO: consider overflow是不够的。验证用户输入是一场战争,而偷工减料最终将导致IED爆炸。 (好吧,好吧,也许不那么引人注目,但仍然......)事实上,ecatmur有用的测试工具中只有一个算法正确处理了一个角落案例({23, 2147483644, 2147483646, false}),并且如果t.max太大,测试工具本身会进入无限循环。

唯一通过的是ecatmur_in_range_div,我觉得这很不错。但是,通过添加一些检查可以(稍微)加快它的速度:

bool in_range_div(int input, int min, int max)
{
  if (input > max) return false;
  if (input >= min) return true;
  if (max / 10 >= min) return true;

  while (input < min) {
    min /= 10;
    max /= 10;
  }
  return input <= max;
}

多快“取决于”;如果min和max是编译时常量,那将特别有用。我认为前两项测试是显而易见的;第三个可以用各种方式证明,但最简单的方法就是观察ecatmur循环的行为:当循环结束时,输入是> = min但是&lt; 10 * min,所以如果10 * min&lt;最大值,输入必须小于最大值

用分词表示算术而不是乘法应该是习惯;我知道我们大多数人都认为分工是 sloooooow 并且必须避免。但是,与乘法不同,除法不会溢出。的确,每当你发现自己写作时:

if (a * k < b) ...

for (..., a < b, a *= k)

或这些主题的其他变体,你应该立即将其标记为整数溢出,并将其更改为等效的除法。

实际上,除了一个重要(但很常见)的情况外,同样适用于添加:

if (a + k < b) ...

a += k; if (a < b) ...

也不安全除非 k为1,并且您知道&lt; b在加入之前。这个例外让循环的正常工作没有太多考虑。但请注意这一点,我曾经将其作为面试问题的一部分使用:

// enumerate every kth element of the range [a, b)
assert(a < b);
for (; a < b; a += k) { ... }
可悲的是,很少有候选人得到它。

答案 13 :(得分:0)

我现在删除这个答案,除了它显示失败的方法。

  

检查Str(min).StartWith(input)后,您需要以数字方式检查是否有任何10^n*Val(input)在此范围内,正如Ben Voight当前的答案所示。


尝试失败

由于Ben Voigt的评论而编辑:(我在他当前的答案中错过了他的第一点:前缀匹配到最小值是好的。)

根据@Ben Voigt的见解,我的解决方案是检查当前输入是Min StartsWith。如果不是,PadRight当前输入0的长度为Max为字符串。然后,如果此修改后的输入在MinMax之间词法(即视为字符串),则可以。

伪代码:

 Confirm input has only digits, striping leading 0s
    (most easily done by converting it to an integer and back to a string)

 check = Str(min).StartsWith(input)
 If Not check Then
   testInput = input.PadRight(Len(Str(max)), '0')
   check = Str(min) <= testInput && testInput <= Str(max)
 End If

答案 14 :(得分:-1)

int input = 15;
int lower_bound = 1561;
int upper_bound = 1567;
int ipow = 0;
while (lower_bound > 0) {
    if (lower_bound > input) {
        ++ipow;
        lower_bound = lower_bound / 10;
    } else {
        int i = pow(10, ipow) * input;
        if (i < upper_bound) {
            return true;
        }
        return false;
    }
}
return false;