Проверьте, не указано ли число с цифрами в С++

Я хочу проверить (числовой) ввод на список диапазонов (мин, макс.), в то время как ввод частично введен; другими словами, мне нужен элегантный алгоритм для проверки префикса числа против диапазона (без использования регулярных выражений).

Примеры тестовых ящиков:

 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)

Есть ли у вас идеи?

Ответ 1

Я верю, что вход допустим тогда и только тогда, когда:

  • Это префиксная подстрока нижней границы, преобразованная в строку

или

  • Ввод, за которым следует любое количество дополнительных нулей (возможно, нет), попадает в диапазон

Первое правило требуется, например, 13 is in range (135, 140). Второе правило требуется, например, 2 is in range (1000, 3000).

Второе правило может быть эффективно проверено серией умножений на 10, пока масштабированный ввод не превысит верхнюю границу.

Ответ 2

Итеративная формулировка:

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
}

Более простое [edit: и более эффективное; см. ниже] метод состоит в том, чтобы использовать усеченное целочисленное деление:

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

Ответ 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;
}

Он передает все ваши тестовые файлы.

Ответ 4

Одним из тривиальных решений является создание всех префиксов 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

Ответ 5

(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

Ответ 6

Я предпочитаю подход, который использует уже реализованные алгоритмы. В то время как многие другие решения используют рекурсивные деления на 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 (so 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 на конце, имея ту же длину, что и 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), поэтому мы сравниваем его с 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). Кроме того, в конечном решении я бы перешел от float к целочисленной области (magnitude = 10**int(floor(log10(max)) - floor(log10(num)))), а затем выполнил все сравнения без деления, т.е. 0 <= (max-num*magnitude)/magnitude < 10 <= max-num*magnitude < magnitude. Это облегчило бы возможности ошибок округления.

Кроме того, может быть возможно заменить magnitude = 10**(floor(log10(min)) - floor(log10(num))) на magnitude = 10**(floor(log10(min/num))), где log10 вычисляется только один раз. Но я не могу доказать, что он всегда будет давать правильные результаты, и я не могу опровергнуть его. Если бы кто-нибудь мог это доказать, я был бы очень благодарен.

Тесты (в Python): http://ideone.com/N5R2j (вы можете редактировать ввод, чтобы добавить другие тесты).

Ответ 7

Учитывая значение n, начните с полуоткрытого диапазона [n, n + 1) и продолжайте на порядки:

  • [10 n, 10 (n + 1))
  • [100 n, 100 (n + 1))
  • [1000 n, 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 не находится в диапазоне или не имеет большего числа, поэтому наивный алгоритм заканчивается ложным отрицанием.

Ответ 8

Я пришел к этому новому простому решению, думая о доказательстве для красивого решения @Ben Voigt:

Вернемся в начальную школу, где мы провели сравнение чисел. Вопрос будет таким: проверьте, находится ли число "A" в диапазоне числа "B" и число "C"

Решение: добавление нужных нулей в левую часть чисел (так что у нас есть равное количество цифр во всех числах). Мы начинаем с самой левой цифры. сравните его с эквивалентной цифрой в двух других числах.

  • если цифра от A меньше цифры от B или больше цифры от C, тогда A не находится в диапазоне.

  • если мы не повторяем процесс со следующей цифрой от A и эквивалентами от B и C.

ВАЖНЫЙ ВОПРОС: Почему бы нам не остановиться прямо сейчас? Почему мы проверяем следующие цифры?

ВАЖНЫЙ ОТВЕТ: Поскольку цифра из A находится между эквивалентами из B и C, является O.K. до сих пор, но еще недостаточно причин для принятия решения! (очевидно, правильно?)

Это, в свою очередь, означает, что может быть набор цифр, которые могли бы вывести A из диапазона.

И, LIKEWISE

Может существовать набор цифр, которые могут помещать A внутри диапазона

, который является другим способом сказать, что A может быть префиксом числа в диапазоне.

Разве это не то, что мы искали?!: D

Основой алгоритма будет в основном простое сравнение для каждого входного события:

  • Добавьте нулевой (если необходимо) слева от min так, чтобы длина min и max стала равной.
  • сравнить вход A с эквивалентными цифрами от min и max (вырезать соответствующие цифры min и max от слева и не правы)
  • Вход A <= соответствующая часть max AND >= соответствующая часть min? (нет: return false, yes: return true)

false и true здесь выражают ситуацию "до сих пор", поскольку проблема требует.

Ответ 9

Все тяжелые случаи - это ситуации, в которых нижняя граница имеет меньше цифр, чем верхнюю границу. Просто сломайте диапазон в два (или три). Если AB - объединение множеств A и B, то из x in AB следует x in A или x in B. Итак:

13 is in (5, 12) = > 13 is in (5, 9) ИЛИ 13 is in (10, 12)

13 is in (5, 120) = > 13 is in (5, 9) ИЛИ 13 is in (10, 99) ИЛИ 13 is in (100, 120)

Затем обрезать для соответствия длинам. То есть.

13 is in (5, 120) = > 13 is in (5, 9) ИЛИ 13 is in (10, 99) ИЛИ 13 is in (10 0 , 12 0 )

После этого второго переписывания он становится простой числовой проверкой. Обратите внимание, что если у вас есть диапазон 10,99, то у вас есть все возможные двухзначные префиксы и на самом деле не нужно проверять, но это оптимизация. (Я предполагаю, что мы игнорируем префиксы 00-09)

Ответ 10

Да другой ответ. Для ввода 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, а не ноль.

Ответ 11

Независимо от выбранного метода реализации, вы должны рассмотреть возможность создания множества модульных тестов. Потому что вы задаете вопрос так же, как вы бы написали тест для тестовой разработки (TDD). Поэтому я предлагаю, чтобы, пока вы ожидаете, что подходящий алгоритм выйдет из, напишите свои юнит-тесты:

Сделайте тест неудачным, если приведенные вами примеры не дают результатов в ваших примерах. Для того, чтобы быть уверенным, напишите пару других тестов. Затем, если вы используете неправильный или ошибочный алгоритм, вы скоро это узнаете. Как только ваш тест пройдет, вы узнаете, что достигли своей цели.

Кроме того, он защищает вас от любой регрессии в будущем

Ответ 12

Возможно, я слишком задумываюсь об этом, но предполагая, что диапазон целых чисел 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;
}

Ответ 13

ОК, немного поздно для вечеринки, но здесь идет...

Обратите внимание, что здесь речь идет о пользовательском вводе, поэтому этого недостаточно для // TODO: consider overflow. Подтверждение ввода пользователя - это война, и резка углов в конечном итоге приведет к детонации ИЭУ. (Ну, хорошо, может быть, не так драматично, но все же...) На самом деле, только один из алгоритмов полезной тестовой жгуты 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, но < 10 * мин, поэтому, если 10 * мин < max, вход должен быть меньше max.

Выражение арифметики в терминах деления, а не умножения должно быть привычкой; Я знаю, что большинство из нас выросло с идеей о том, что разделение является sloooooow и его следует избегать. Однако деление, в отличие от умножения, не будет переполняться. Действительно, всякий раз, когда вы пишете:

if (a * k < b) ...

или

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

или другие варианты этих тем, вы должны немедленно отметить это как целочисленное переполнение и изменить его на эквивалентное деление.

Собственно, то же самое касается добавления, кроме одного важного (но общего) случая:

if (a + k < b) ...

или

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

также небезопасны , если k не является одним, и вы знаете, что a < b перед добавлением. Это исключение позволяет нормальному для цикла работать без лишних размышлений. Но следите за этим, что я использовал в качестве части вопроса для интервью:

// enumerate every kth element of the range [a, b)
assert(a < b);
for (; a < b; a += k) { ... }

К сожалению, очень немногие кандидаты получили его.

Ответ 14

Теперь я удалю этот ответ, за исключением того, что он показывает неудачный подход.

После проверки Str(min).StartWith(input) вам нужно проверить численно, если какой-либо 10^n*Val(input) находится в пределах диапазона, как говорит текущий ответ Ben Voight.


Неудачная попытка

Отредактировано из-за комментария Бена Фойгта: (Я пропустил свой первый пункт в его текущем ответе: минимальные совпадения до минимума в порядке.)

Мое решение, основанное на знаниях @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

Ответ 15

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;