46

入力が部分的に入力されている間に、(数値) 入力を範囲 (最小、最大) のリストに対してチェックしたい。言い換えれば、数値のプレフィックスを範囲に対してチェックする洗練されたアルゴリズムが必要です (正規表現を使用しません)。

サンプル テストケース:

 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)

何か考えはありますか?

4

15 に答える 15

27

次のいずれかの場合にのみ、入力が受け入れられることは事実だと思います。

  • 文字列に変換された下限のプレフィックスサブ文字列です

また

  • 入力の後に任意の数の追加のゼロ(おそらくなし)が続く場合は、範囲に含まれます

最初のルールは、たとえばによって必要とされ13 is in range (135, 140)ます。2番目のルールは、たとえばによって必要とされ2 is in range (1000, 3000)ます。

2番目のルールは、スケーリングされた入力が上限を超えるまで、一連の10の乗算によって効率的にテストできます。

于 2012-09-24T13:46:45.210 に答える
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
于 2012-09-24T14:24:48.037 に答える
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;
}

すべてのテストケースに合格します。

于 2012-09-24T13:48:05.037 に答える
2

nが与えられた場合、半開範囲[ nn + 1)から始めて、桁違いに進みます。

  • [10 n、10(n + 1))
  • [100 n、100(n + 1))
  • [1000 n、1000(n + 1))
  • …</li>

繰り返される範囲がターゲット範囲とオーバーラップするか、2つの範囲がオーバーラップできなくなるまで続行します。

#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は範囲内になく、それ以上の数値でもないため、ナイーブアルゴリズムはフォールスネガティブで終了します。

于 2012-09-24T16:55:26.290 に答える
2

@Ben Voigt の美しいソリューションの証明を考えているときに、この新しいシンプルなソリューションにたどり着きました。

数比較をした小学校に戻りましょう。質問は次のようになります: 数値 " A " が数値 " B " と数値 " C "の範囲内にあるかどうかを確認します

解決策: 数字の左側に必要なゼロを追加します (したがって、すべての数字の桁数が同じになります)。左端の数字から始めます。他の 2 つの数値の対応する数字と比較してください。

  • Aの桁がBの桁より小さいか、 Cの桁より大きい場合、A範囲外です。

  • そうでない場合は、 Aの次の数字とBおよびCの同等の数字でプロセスを繰り返します。

重要な質問:ここでやめませんか? なぜ次の数字をチェックするのですか?

重要な回答: Aの桁がBCの同等の桁の間にあるため、これまでは問題ありませんが、決定を下すにはまだ十分な理由ではありません! (当然ですよね?)

これは、Aを範囲外にする可能性のある一連の数字が存在する可能性があることを意味します。

そして、同様に

Aを範囲内に入れることができる数字のセットが存在する可能性があります

これは別の言い方をすれば、 Aは範囲内の数値のプレフィックスである可能性があります。

それは私たちが探していたものではありませんか?! :D

アルゴリズムのバックボーンは、基本的に各入力イベントの単純な比較になります。

  1. minmaxの長さが等しくなるように、 minの左側に (必要に応じて) ゼロを追加します。
  2. 入力Aminmaxの対応する数字と比較します(右からではなくからminmaxの対応する数字を切り取ります)
  3. 入力A <= maxの対応する部分 AND >= minの対応する部分ですか? (いいえ: false を返す、はい: true を返す)

ここでの false と true は、問題が必要とする「今まで」の状況を表しています。

于 2012-09-24T19:29:48.967 に答える
2

私は、すでに実装されているアルゴリズムを使用するアプローチを好みます。他の多くのソリューションでは による再帰的な除算が使用されていますが、複雑な 10 を底とする対数を使用して、ソリューション全体の複雑さが になるよう10にする方がよいと思います。O(1)O(1)

問題を 2 つの部分に分けてみましょう。

最初number * 10^nの部分は、が と の間minにありmax、少なくとも 1 つの の場合を処理しnます。number = 12これにより、たとえばとの間min,max = 11225,13355、つまりx = 12000 = 12*10^3と の間minにあるかどうかを確認できmaxます。このテストがチェックアウトされた場合、結果が であることを意味しTrueます。

2 番目の部分は、 が または のいずれかnumberの始まりの場合を処理します。たとえば、 と の場合、 との間ではないため、最初のテストは失敗します (また、 の他のすべての数値も失敗します)。しかし、2 番目のテストでは、それがと returnの始まりであることがわかります。minmaxnumber = 12min,max = 12325,1455512000minmax12*10^nn1212325True

初め

x = number*10^nと等しいか大きい最初のが(so )minより小さいか等しいかどうかを確認しましょう。よりも大きい場合、他のすべてのes よりも大きくなります。maxmin <= x <= max, where x is number*10^n for any integer nmaxx

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

2番

番号がどちらかの境界の文字通りの始まりであるかどうかを確認する必要があります。

xと同じ数字で始まり、最後に snumberをパディングして、 と同じ長さで計算するだけです。0min

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

そして、minと のx差 (マグニチュード スケール) が より小さく1、大きいか等しいかどうかを確認し0ます。

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

したがって、numberis121minis132125の場合、magnitudeis 1000x = number*magnitudebe になります121000min - x132125-121000 = 11125よりも小さいはずです1000(そうでなければ、開始minは よりも大きくなります121) 。である場合は問題ありませんが、 である場合は問題ありません。magnitude1min121000min1220000 <=< 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 から integer スコープ ( magnitude = 10**int(floor(log10(max)) - floor(log10(num)))) に移動し、除算なしですべての比較を実行します。つまり、0 <= (max-num*magnitude)/magnitude < 1-> 0 <= max-num*magnitude < magnitude. これにより、丸め誤差の可能性が軽減されます。

また、は 1 回だけ計算さmagnitude = 10**(floor(log10(min)) - floor(log10(num))) れるに置き換えることもできます。しかし、それが常に正しい結果をもたらすことを証明することも、反証することもできません。誰かがそれを証明できるなら、私はとても感謝しています.magnitude = 10**(floor(log10(min/num)))log10

テスト (Python): http://ideone.com/N5R2j (入力を編集して別のテストを追加できます)。

于 2012-09-24T14:57:18.907 に答える
2

One trivial solution is to generate all N-digit prefixes in range. So, for 11 is in ( 5, 12) you want the two-digit prefixes of all numbers between 5 and 12. Obviously that's just 10, 11 and 12.

In general, for numbers X to Y, the possible N-digit prefixes can be obtained by the following algorithm:

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
于 2012-09-24T14:16:12.390 に答える
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
于 2012-09-24T14:39:14.157 に答える
1

選択した実装方法が何であれ、多くの単体テストを作成することを検討する必要があります。テスト駆動開発 (TDD) 用のテストを作成する場合と同じように、質問をしているからです。したがって、適切なアルゴリズムがスタック オーバーフローから飛び出すのを待っている間に、単体テストを作成することをお勧めします。

与えられた例が例の結果をもたらさない場合は、テストを失敗させます。念のため、他にもいくつかの制限テスト ケースを作成してください。次に、間違ったアルゴリズムやバグのあるアルゴリズムを使用した場合は、すぐにわかります。テストに合格すると、目標に到達したことがわかります。

さらに、将来の回帰からあなたを守ります

于 2012-09-24T15:56:53.310 に答える
1

多分私はこれを過小評価していますが、最小値から最大値までの整数の範囲がすべて正 (ゼロ以上) であると仮定すると、このコード ブロックはうまく機能するはずです。

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;
}
于 2012-09-25T21:50:17.593 に答える
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)では、0 ではなく 5 を追加して 135 を生成します。

于 2012-09-24T15:00:02.867 に答える
1

すべての困難なケースは、下限の桁数が上限の桁数よりも少ない状況です。範囲を 2 つ (または 3 つ) に分割するだけです。AB が集合 A と B の和集合である場合、orをx in AB意味します。そう:x in Ax 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 (100 , 120)

この 2 回目の書き換えの後、単純な数値チェックになります。範囲10,99が表示されている場合は、可能なすべての 2 桁のプレフィックスがあり、実際に確認する必要はありませんが、これは最適化です。(プレフィックス 00-09 を無視すると仮定しています)

于 2012-09-24T14:25:18.150 に答える
0

失敗したアプローチを示していることを除いて、この回答を削除します。

を確認した後、Ben Voightの現在の回答が示すように、範囲内にあるStr(min).StartWith(input)かどうかを数値で確認する必要があります。10^n*Val(input)


失敗しました

Ben Voigt のコメントのために編集されました: (私は彼の現在の回答で彼の最初のポイントを見逃していました: 最小限の接頭辞の一致は問題ありません。)

@Ben Voigt の洞察に基づく私の解決策はMin StartsWith、現在の入力かどうかを確認することです。そうでない場合は、文字列としての長さまでPadRightの現在の入力。次に、この変更された入力が字句的に(つまり、文字列として扱われる) と の間であればOK です。0MaxMinMax

擬似コード:

 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
于 2012-09-26T00:50:11.667 に答える
0

OK、パーティーには少し遅れましたが、ここで...

ここではユーザー入力について話しているので、 だけでは不十分であることに注意してください// TODO: consider overflow。ユーザー入力の検証は戦争であり、手抜きは最終的に IED の爆発につながります。(そうですね、それほど劇的ではないかもしれませんが、それでも...) 実際、ecatmur の便利なテスト ハーネスのアルゴリズムの1 つだけ{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 がコンパイル時の定数である場合は特に便利です。最初の 2 つのテストは明らかだと思います。3 つ目はさまざまな方法で証明できますが、最も簡単なのは ecatmur のループの動作を観察することです。ループが終了すると、入力は >= min ですが < 10*min です。したがって、10*min < max の場合、入力は最大未満であること。

算術を掛け算ではなく割り算で表現することは、習慣にすべきです。私たちのほとんどは、分裂は遅く、避けなければならないという考えで育ったことを知っています. ただし、除算は乗算とは異なり、オーバーフローしません。実際、自分が書いていることに気付いたときはいつでも:

if (a * k < b) ...

また

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

またはそれらのテーマの他のバリエーションは、整数オーバーフローとしてすぐにフラグを立て、同等の除算に変更する必要があります。

実際、1 つの重要な (しかし一般的な) ケースを除いて、同じことが足し算にも当てはまります。

if (a + k < b) ...

また

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

もk が 1 でない限り安全ではなく、加算前に a < b であることがわかっています。その例外により、あまり考えなくても通常の for ループが機能します。しかし、私がインタビューの質問の一部として使用していたこれに注意してください。

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

悲しいことに、それを取得した候補者はほとんどいませんでした。

于 2012-09-24T23:31:11.477 に答える
-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;
于 2012-09-24T14:20:22.193 に答える