我想在输入部分输入的同时检查范围列表(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)
你有什么想法吗?
答案 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
介于min
和max
之间且至少有一个n
的情况。这样我们就可以检查number = 12
和min,max = 11225,13355
是否x = 12000 = 12*10^3
介于min
和max
之间。如果此测试检出,则表示结果为True
。
第二个部分将处理number
或min
开始时max
开始的案例。例如,如果number = 12
和min,max = 12325,14555
,则第一次测试将失败,因为12000
不在min
和max
之间(并且将失败所有其他数字)任何12*10^n
n
。但第二次测试会发现12
是12325
的开头并返回True
。
如果第一个x = number*10^n
等于或大于min
,则检查是否小于或等于max
(min <= 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
然后检查min
和x
差异(幅度范围)是否小于1
且大于或等于0
:
0 <= (min-x)/magnitude < 1
因此,如果number
为121
且min
为132125
,则magnitude
为1000
,x = number*magnitude
为121000
。 min - x
提供的132125-121000 = 11125
应小于1000
(否则min
开头会大于121
),因此我们将其与{{1}进行比较通过除以它的值并与magnitude
进行比较。如果1
为min
,则可以,但如果121000
为min
则不行,这就是122000
和0 <=
的原因。
相同的算法适用于< 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)开始,按以下顺序进行:
继续,直到迭代范围与目标范围重叠,或者两个范围不再重叠。
#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
算法的主干基本上是每个输入事件的简单比较:
false和true在这里表达了“到现在为止”的情况,正如问题所要求的那样。
答案 8 :(得分:1)
所有困难情况都是下限比上限少的数字。只需将范围分成两个(或三个)即可。如果AB是集合A和集合B,那么x in AB
意味着x in A
或x 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
为字符串。然后,如果此修改后的输入在Min
和Max
之间词法(即视为字符串),则可以。
伪代码:
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;