与えられたものに40億でない整数を生成する


691

私はこのインタビューの質問を与えられました:

入力ファイルに40億の整数が含まれている場合、ファイルに含まれていない整数を生成するアルゴリズムを提供します。1 GBのメモリがあるとします。メモリが10 MBしかない場合の対処方法をフォローアップします。

私の分析:

ファイルのサイズは4×10 9 ×4バイト= 16 GBです。

外部ソートを実行して、整数の範囲を知らせることができます。

私の質問は、ソートされた大きな整数セットで欠落している整数を検出する最良の方法は何ですか?

私の理解(すべての回答を読んだ後):

32ビット整数について話していると仮定すると、2 32 = 4 * 10 9個の異なる整数があります。

ケース1:1 GB = 1 * 10 9 * 8ビット= 80億ビットのメモリがあります。

解決:

1つの異なる整数を表す1ビットを使用すれば十分です。ソートは必要ありません。

実装:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

ケース2:10 MBのメモリ= 10 * 10 6 * 8ビット= 8000万ビット

解決:

すべての可能な16ビットのプレフィックスのために、2がある16の整数= 65536の数、我々は2必要な16 * 4 * 8 = 2万ビットを。65536バケットを作成する必要があります。最悪の場合、40億の整数はすべて同じバケットに属するため、バケットごとに、すべての可能性を保持する4バイトが必要です。

  1. ファイルの最初のパスで各バケットのカウンターを作成します。
  2. バケットをスキャンして、ヒットが65536未満の最初のバケットを見つけます。
  3. ファイルの2番目のパスを介してステップ2で検出された上位16ビットのプレフィックスを持つ新しいバケットを作成します。
  4. 手順3で作成されたバケットをスキャンし、ヒットしていない最初のバケットを見つけます。

コードは上記のものと非常に似ています。

結論:ファイルパスを増やすことでメモリを減らします。


遅れて到着する場合の説明:質問のように、ファイルに含まれていない整数が1つしかないという質問はありません。少なくとも、ほとんどの人がそれを解釈しているわけではありません。コメントスレッドの多くのコメントがあるものの、タスクの変動について。残念ながら、コメントスレッドに導入されたコメントは後でその作成者によって削除されたため、孤立した返信はすべて誤解されているように見えます。非常に混乱しています。


32
@trashgod、間違っています。4294967295の一意の整数の場合、残りの整数は1つになります。それを見つけるには、すべての整数を合計し、すべての可能な整数の事前計算された合計からそれを引く必要があります。
Nakilon、2011

58
これは「プログラミングパール」の2番目の「パール」であり、この本の説明全体を読むことをお勧めします。books.google.com/…を
Alok Singhal、2011

8
@Richard 64ビットintは、十分な大きさを超えます。
cftarnas 2011

79
int getMissingNumber(File inputFile) { return 4; }参照
ジョニー

14
C / C ++などの言語の整数型は、連想性や通信性などのプロパティを常に保持するため、1から2 ^ 32までのすべての整数の合計を格納できないことは問題ではありません。つまり、合計は正しい答えではありませんが、オーバーフローありの期待値、オーバーフロー付きの実際の合計を計算してから減算すると、結果はまだ正しいです(それ自体がオーバーフローしない場合)。
thedayturns

回答:


529

「整数」が32ビットを意味すると仮定。10MBのスペースは、指定された16ビットプレフィックスを持つ入力ファイル内の数を数えるのに十分です。入力ファイル。バケットの少なくとも1つが2 16回未満ヒットされます。2番目のパスを実行して、そのバケット内のどの番号がすでに使用されているかを確認します。

32ビットを超えるが、サイズに制限がある場合:上記のようにして、(符号付きまたは符号なし、選択した)32ビット範囲外にあるすべての入力番号を無視します。

「整数」が数学的整数を意味する場合:入力を1回読み取り、これまでに見た中で最も長い数字の中で最大の数字の長さを追跡します。完了したら、最大値に1を加え、さらに1桁の乱数を出力します。(ファイル内の数値の1つは、正確に表すために10 MBを超えるbignumである可能性がありますが、入力がファイルの場合、少なくともそれに収まるすべての長さを表すことができます)。


24
完璧です。最初の答えは、ファイルを2回パスするだけです!
corsiKa

47
10 MB bignum?それはかなり極端です。
Mark Ransom

12
@Legate、ただ大きな数をスキップして、それらについては何もしない。とにかく、大量の出力をするつもりはないので、どれを表示したかを追跡する必要はありません。
hmakholmがモニカに残った

12
ソリューション1の良い点は、パスを増やすことでメモリを削減できることです。
Yousf 2011

11
@バリー:上記の質問は、欠落している数が1つだけであることを示していません。また、ファイル内の数字が繰り返されないということもありません。(実際に尋ねられた質問に従うことは、おそらくインタビューで良い考えだと思いませんか?
;

197

統計情報に基づくアルゴリズムは、決定論的アプローチよりも少ないパスを使用してこの問題を解決します。

非常に大きな整数が許可されている場合、O(1)時間で一意である可能性が高い数値を生成できます。GUIDのような疑似ランダム128ビット整数は、640億の10億のケースのうち1つ未満で、セット内の既存の40億の整数の1つとのみ衝突します

整数が32ビットに制限されている場合、10 MBをはるかに下回る1回のパスで一意である可能性が高い数値を生成できます。疑似ランダム32ビット整数が40億の既存の整数の1つと衝突する確率は、約93%(4e9 / 2 ^ 32)です。1000の疑似ランダム整数がすべて衝突する確率は、12億000億億分の1未満です(1つの衝突の確率^ 1000)。したがって、プログラムが1000個の疑似ランダム候補を含むデータ構造を維持し、既知の整数を反復処理して候補からの一致を排除する場合、ファイルにない少なくとも1つの整数を見つけることはほぼ確実です。


32
私は整数が制限されていると確信しています。そうでない場合、初心者プログラマーでもアルゴリズムを「データを1回通過して最大数を見つけ、それに1を加える」と考えるでしょう
Adrian Petrescu

12
文字通りランダムな出力を推測しても、おそらくインタビューで多くのポイントが得られるわけではありません
ブライアンゴードン

6
@エイドリアン、あなたの解決策は明白に思えます(そして私にはそれでした、私は自分の答えでそれを使用しました)が、それは誰にとっても明白ではありません。明白な解決策を見つけることができるかどうか、または触れるすべてを過度に複雑にするかどうかを確認するための良いテストです。
Mark Ransom 2011

19
@ブライアン:このソリューションは想像力と実用性の両方を備えていると思います。私はこの答えに対して多くの称賛を与えるでしょう。
リチャードH

6
ああここにエンジニアと科学者の間の線があります。良い答えのベン!
TrojanName 2011

142

この問題の詳細については、ジョンベントレーの「コラム1.カキのひび割れ」プログラミング真珠アディソンウェスリーpp.3-10で説明されています。

ベントレーは、外部ソート、複数の外部ファイルを使用したマージソートなど、いくつかのアプローチについて説明していますが、ベントレーが提案する最良の方法は、ビットフィールドを使用するシングルパスアルゴリズムで、ユーモラスに「ワンダーソート」と呼んでいます:)問題の発生、40億数値は次のように表すことができます:

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

ビットセットを実装するコードは簡単です:(ソリューションページから取得)

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

Bentleyのアルゴリズムは、ファイルに対して1回のパスを作成setし、配列の適切なビットを入力してtestから、上記のマクロを使用してこの配列を調べ、欠落している数を見つけます。

利用可能なメモリが0.466 GB未満の場合、Bentleyは、利用可能なメモリに応じて入力を範囲に分割するk-passアルゴリズムを提案します。非常に単純な例として、1バイト(8つの数値を処理するためのメモリ)のみが使用可能で、範囲が0〜31の場合、これを0〜7、8-15、16-22などの範囲に分割します。この範囲を各32/8 = 4パスで処理します。

HTH。


12
私はその本を知りませんが、1ビットのカウンタを備えた単なるバケットソートなので、「ワンダーソート」と呼ぶ理由はありません。
flolo 2011

3
移植性は高くなりますが、このコードは、ハードウェアでサポートされているベクトル命令を利用するように記述れたコードによって消滅します。場合によっては、gccが自動的にコードをベクトル演算を使用するように変換できると思います。
ブライアンゴードン

3
@brian Jon Bentleyがアルゴリズムに関する本にそのようなことを許可していたとは思いません。
デビッドヘファーナン

8
@ BrianGordon、ramで費やされた時間は、ファイルの読み取りに費やされた時間と比較してごくわずかです。それを最適化することを忘れてください。
Ian

1
@BrianGordon:または、最初の未設定ビットを見つけるための最後のループについて話していましたか?はい、ベクトルはそれを高速化しますが、64ビット整数でビットフィールドをループし、!= -1シングルコアで実行されているメモリ帯域幅を飽和させるものを探します(これは、ビットを要素として持つSIMD-in-a-a-register、SWARです)。(最近のIntel / AMD設計用)。それが含まれている64ビットの場所が見つかったら、どのビットが設定されていないかを知る必要があります。(そして、そのためにできますnot / lzcnt。)シングルビットテストでループすることは、最適化されない可能性があるという公平な点です。
Peter Cordes 2015

120

問題は、ファイルにない最小の可能な数を見つける必要があることを指定していないため、入力ファイル自体よりも長い数を生成できます。:)


6
ファイルの最大数がmax int
でなければ、

新しい整数を生成し、それを「使用された整数」ファイルに100回追加する必要がある実際のプログラムでのファイルのサイズはどれくらいでしょうか。
Michael

2
私はこれを考えていました。仮定がintある32ビット、ちょうど出力2^64-1。できました。
イマレット2015年

1
行ごとに1つのintの場合tr -d '\n' < nums.txt > new_num.txt::D
ション

56

1 GB RAMバリアントの場合、ビットベクトルを使用できます。40億ビット== 500 MBのバイト配列を割り当てる必要があります。入力から読み取る数値ごとに、対応するビットを「1」に設定します。完了したら、ビットを反復処理して、まだ「0」である最初のビットを見つけます。そのインデックスが答えです。


4
入力の数値の範囲は指定されていません。入力が80億から160億の間のすべての偶数で構成されている場合、このアルゴリズムはどのように機能しますか?
Mark Ransom

27
@ Mark、0..2 ^ 32の範囲外の入力は無視してください。いずれにせよ、それらを出力することはないので、回避するべきものを覚えておく必要はありません。
hmakholmがモニカを去った

32ビット文字列を実数にマップする方法を決定するために使用するアルゴリズムに@Markを付けるのはあなた次第です。プロセスはまだ同じです。唯一の違いは、それを実際の数値として画面に出力する方法です。
corsiKa

4
代わりに、自分自身を反復するのを使用できbitSet.nextClearBit(0)download.oracle.com/javase/6/docs/api/java/util/...
starblue

3
整数の範囲に関係なく、パスの最後で少なくとも1ビットが0になることが保証されていることに言及しておくと便利です。これは鳩の巣の原理によるものです。
ラファウDowgird

46

それらが32ビット整数である場合(おそらく2 32に近い〜40億の数値の選択から)、40億の数値のリストは、可能な整数の最大93%を占めます(4 * 10 9 /(2 32) )。したがって、各ビットが0に初期化された2 32ビットのビット配列を作成する場合(2 29バイト〜500 MBのRAMを消費します。1バイト= 2 3ビット= 8ビットをてください)、整数リストを読み、各intに対して、対応するビット配列要素を0から1に設定します。次に、ビット配列を読み取り、まだ0である最初のビットを返します。

RAMが少ない(約10 MB)場合は、このソリューションを少し変更する必要があります。10 MB〜83886080ビットは、0から83886079までのすべての数値に対してビット配列を行うのに十分です。したがって、intのリストを読み取ることができます。そして、ビット配列に0から83886079までの#のみを記録します。数値がランダムに分布している場合。圧倒的な確率で(100%異なります約10 -2592069ます)、欠落しているintが見つかります)。実際、1から2048までの数値(256バイトのRAMのみ)を選択した場合でも、時間の圧倒的なパーセンテージ(99.99999999999999999999999999999999999999999999999999999999999995%)が不足していることがわかります。

しかし、約40億の数値を持つ代わりに、あなたは2 32のようなものを持っていました数字と10 MB未満のRAMがありました。そのため、intの小さな範囲は、その数を含まない可能性がほとんどありません。

リスト内の各intが一意であることが保証されている場合は、数値を合計し、1つの#が欠落している合計を減算して全和を求めます(½)(2 32(2 32-1)= 9223372034707292160。 。ただし、intが2回発生した場合、このメソッドは失敗します。

ただし、いつでも分割して征服することができます。素朴な方法は、アレイを介して読み出され、前半(0〜2である数字の数をカウントすることで31 -1)と後半(2 31、2 32)。次に、数値の少ない範囲を選択し、その範囲を半分に分割することを繰り返します。(セイは(2におけるより少ない数の2があった場合は31、2 32)その後、あなたの次の検索は範囲内の数字(2カウントしまう31を、3 * 2 30 -1)、(3 * 2 30、2 32)。キープゼロの数値の範囲が見つかり、答えが得られるまで繰り返します。配列をO(lg N)〜32回読み取る必要があります。

その方法は非効率的でした。各ステップで2つの整数のみ(または4バイト(32ビット)の整数で約8バイトのRAM)を使用しています。より良い方法は、sqrt(2 32)= 2 16 = 65536ビンに分割することです。各ビンには65536個の数値があります。各ビンにはカウントを格納するために4バイトが必要なので、2 18バイト= 256 kB が必要です。(0 = 2〜65535であるビン0はとても16 -1)、ビン1は(2 16 = 65536 2 * 2 16 -1 = 131071)、ビン2である(* 2 2 16 3 * 2 = 131072 16 - 1 = 196607)。Pythonでは次のようなものがあります:

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

〜40億の整数リストを読みます。そして、2 16個のビンのそれぞれに含まれるintの数を数え、すべての65536の数値を持たないincomplete_binを見つけます。次に、40億の整数リストをもう一度読みます。しかし、今回は整数がその範囲内にあるときにのみ気づきます。あなたがそれらを見つけたときに少し弾く。

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break

3
そのような素晴らしい答え。これは実際に機能します。そして結果を保証しました。
ジョナサンディキンソン

@dr jimbob、ビンに番号が1つしかなく、その単一の番号に65535の重複がある場合はどうなりますか?その場合、ビンは引き続き65536をカウントしますが、65536の数値はすべて同じものです。
Alcott、2011年

@Alcott-2 ^ 32-1(またはそれ以下)の数値があると想定したので、鳩の巣の原理により、詳細を確認するために65536カウント未満のビンが1つあることが保証されます。欠落している整数のすべてではなく、1つだけを見つけようとしています。2 ^ 32以上の数値がある場合、不足している整数を保証できず、このメソッドを使用できません(または、不足している整数があることを最初から保証します)。その場合、最善の策はブルートフォースです(たとえば、配列を32回読み、最初の65536#を最初にチェックし、答えが見つかったら停止します)。
ジンボブ博士、2011年

巧妙なupper-16 / lower-16メソッドは、以前にHenningによって投稿されました:stackoverflow.com/a/7153822/224132。ただ、メンバーが1つだけ欠けている一意の整数のセットを追加するというアイデアは気に入りました。
Peter Cordes

3
@PeterCordes-はい、ヘニングのソリューションは私より前のものですが、私の答えはまだ有用だと思います(いくつかのことをより詳細に処理する)。とは言っても、Jon Bentleyの著書 『Programming Pearls』では、スタックオーバーフローが発生する前に、この問題のマルチパスオプション(vine'thの回答を参照)を提案しました(私たちのどちらかが意識的にそこから盗んだとか、Bentleyが最初にこの問題を分析します-それは開発するのにかなり自然な解決策です)。ジャイアントビット配列を使用した1パスソリューションに十分なメモリがないという制限がある場合、2パスは最も自然なようです。
ジンボブ博士、2015

37

なぜそんなに複雑にするのですか?ファイルに存在しない整数を要求しますか?

指定されたルールによると、格納する必要があるのは、ファイル内でこれまでに見つかった最大の整数だけです。ファイル全体が読み取られたら、それよりも1大きい数値を返します。

ルールに従って、アルゴリズムによって返される整数または数値のサイズに制限がないため、maxintなどにヒットするリスクはありません。


4
これは、max intがファイルに含まれていない限り機能しますが、これは完全に可能です...
PearsonArtPhoto

13
ルールでは、32ビットか64ビットかなどが指定されていないため、指定されたルールによると、最大の整数はありません。整数はコンピュータ用語ではなく、正または負の整数を識別する数学用語です。
ピート

確かにそうですが、64ビットの数値であるとか、そのようなアルゴリズムを混乱させるためだけに誰かが最大のint数をこっそりとはしなかったとは限りません。
PearsonArtPhoto 2011

24
プログラミング言語が指定されていない場合、「max int」の概念全体はコンテキストでは無効です。たとえば、長整数のPythonの定義を見てください。無限です。屋根はありません。いつでも追加できます。整数の最大許容値を持つ言語で実装されていると想定しています。
ピート

32

これは、バイナリ検索のバリアントを使用して、非常に小さなスペースで解決できます。

  1. 許容範囲の数値から0まで始め4294967295ます。

  2. 中点を計算します。

  3. ファイルをループして、中点の値よりも小さい、または大きい数値がいくつあるかを数えます。

  4. 等しい数がない場合は、完了です。中間点の数が答えです。

  5. それ以外の場合は、数が最も少ない範囲を選択し、この新しい範囲で手順2から繰り返します。

これには、ファイル全体で最大32の線形スキャンが必要ですが、範囲とカウントを格納するために数バイトのメモリのみを使用します。

これは、16kの代わりに2つのビンを使用することを除いて、本質的にヘニングのソリューションと同じです。


2
これは、与えられたパラメーターの最適化を始める前の最初の段階です。
hmakholmがモニカに残った

@ヘニング:かっこいい。これは、時空間のトレードオフを微調整しやすいアルゴリズムの良い例です。
ハマー2011

@hammar、しかし、複数回出現するそれらの数字がある場合はどうなりますか?
Alcott、2011年

@アルコット:次に、アルゴリズムは疎なビンではなく密なビンを選択しますが、鳩の巣の原理では、完全にいっぱいのビンを選択することはできません。(2つのカウントのうち小さい方は常にビンの範囲より小さくなります。)
Peter Cordes

27

編集 OK、ファイル内の整数が静的分布に従うことを前提としているため、これは十分に検討されていませんでした。どうやら彼らはそうする必要はありませんが、それでも人はこれを試すべきです:


約43億の32ビット整数があります。それらがファイル内でどのように分布しているかはわかりませんが、最悪のケースは、シャノンエントロピーが最も高いケース、つまり均等分布です。この場合、ファイル内で発生しない整数の確率は次のとおりです。

((2³²-1)/2³²)⁴⁰⁰⁰⁰⁰⁰⁰⁰⁰≈.4

シャノンエントロピーが低いほど、この確率は平均的に高くなりますが、この最悪の場合でも、ランダムな整数で5回の推測を行った後に90%の確率で非発生値が見つかる可能性があります。疑似乱数発生器でそのような数値を作成し、リストに格納するだけです。次に、intを次々に読み取って、すべての推測と比較します。一致した場合、このリストエントリを削除します。すべてのファイルを確認した後、おそらく1つ以上の推測が残ることになります。それらのいずれかを使用します。推測が残っていない、まれな(最悪の場合でも10%)イベントでは、ランダムな整数の新しいセットを取得します。おそらく今回はさらに多く(10-> 99%)です。

メモリ消費:数十バイト、複雑さ:O(n)、オーバーヘッド:ほとんどの時間は、とにかくintを比較するのではなく、避けられないハードディスクアクセスに費やされるため、選択可能です。


静的分布を想定し ていない場合の実際の最悪のケースは、すべての整数が最大になることです。なぜなら、その場合、1-4000000000 /2³²≈すべての整数の6%だけがファイルに出現しないからです。したがって、さらに推測が必要になりますが、それでもメモリに大量のコストがかかることはありません。


5
他の誰かがこれについて考えたことをうれしく思いますが、なぜここの一番下にあるのですか?これは1パスのアルゴリズムです。2.5Mの推測には10 MBで十分です。93%^ 2.5M≈10 ^ -79000は、2回目のスキャンが必要になる可能性はほとんどありません。バイナリ検索のオーバーヘッドにより、使用する推測が少ないほど高速になります。これは時間と空間の両方で最適です。
Potatoswatter

1
@Potatoswatter:バイナリ検索について言及しました。5つの推測のみを使用する場合、オーバーヘッドの価値はおそらくありませんが、それは確かに10以上です。2 Mの推測を行うこともできますが、ハッシュセットに格納して、検索用にO(1)を取得する必要があります。
leftaroundabout '

1
@Potatoswatterベンヘイリーの同等の回答がトップ近くにあります
ブライアンゴードン

1
私はこのアプローチが好きですが、メモリ節約の改善を提案します:Nビットの利用可能なインデックス付きストレージと一定のストレージがある場合、構成可能な可逆32ビットスクランブル関数(順列)を定義し、任意の順列を選択して、すべてをクリアしますインデックス付きビット。次に、ファイルから各数値を読み取り、それをスクランブルし、結果がN未満の場合は、対応するビットを設定します。ファイルの最後にビットが設定されていない場合は、そのインデックスのスクランブル機能を逆にします。64KBのメモリがあれば、512,000を超える数値を1回のパスで利用できるかどうかを効果的にテストできます。
スーパーキャット2013年

2
もちろん、このアルゴリズムでは、最悪のケースは、使用しているのと同じ乱数ジェネレーターによって数値が作成された場合です。そうでないことを保証できると仮定すると、最善の戦術は、線形合同乱数ジェネレーターを使用してリストを生成し、疑似ランダムな方法で数値空間を通過することです。つまり、何らかの方法で失敗した場合でも、intの全範囲をカバーするまで(ギャップが見つかった)、努力を繰り返すことなく、数値を生成し続けることができます。
Dewi Morgan

25

[0、2 ^ x -1] の範囲から1つの整数が欠落している場合は、それらすべてをxorするだけです。例えば:

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(私はこれが質問に正確に回答しないことを知っていますが、非常に類似した質問への良い回答です。)


1
はい、1つの整数が欠落している場合に機能0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7することを証明するのは簡単ですが、複数の整数が欠落している場合は失敗することがよくあります。たとえば、は0です。[ 2 xの2乗に対して 2 xを書き込み、xor bに対してa ^ bを書き込むと、すべてのk <2 xのxorはゼロになります-k ^ 〜k =(2 ^ x)- k <2 ^(x-1)の場合は1、j = k + 2 **(x-2)の場合はk ^〜k ^ j ^〜j = 0-つまり、1つの数値を除くすべてのxorが値不足しているものの]
ジェームズウォルドビー-jwpat7

2
ircmaxellの返答についてコメントで述べたように、この問題は「1つの数値が欠落している」ということではなく、ファイル内の40億の数値に含まれていない数値を見つけることを意味しています。32ビット整数を想定すると、約3億の数値がファイルから失われる可能性があります。存在する数のxorが欠落している数と一致する可能性は、約7%にすぎません。
James Waldby-jwpat7

これは、私が最初に質問を読んだときに考えていた答えですが、よく調べてみると、これよりも質問があいまいだと思います。ちなみに、これは私が考えていた質問です:stackoverflow.com/questions/35185/...
リー・ネザートン

18

彼らは、値が大きなセットの一部ではないかどうかを非常に効率的に完全に決定できる確率的ブルームフィルターについて聞いたことがあるかどうかを確認している可能性があります(ただし、セットのメンバーである可能性は高いです)。


4
可能な値のおそらく90%以上が設定されているため、ブルームフィルターはビットフィールドに退化する必要があるため、多くの回答がすでに使用されています。そうしないと、完全に埋められた不要なビット文字列ができてしまいます。
Christopher Creutzig、2011

@Christopherブルームフィルターについての私の理解は、100%に達するまで、ビット配列がいっぱいにならないということです
Paul

...さもなければ、あなたは偽陰性を得るでしょう。
ポール

@Paul塗りつぶされたビット配列は、許可された誤検知を提供します。この場合、ブルームフィルターは、負になる解が偽陽性を返す場合に退行する可能性が最も高くなります。
テイラー

1
@Paul:エントリの数を掛けたハッシュ関数の数がフィールドの長さと同じ大きさになるとすぐに、ビット配列を埋めることができます。もちろん、それは例外的なケースですが、確率は非常に急速に上昇します。
Christopher Creutzig、2011

17

元の質問の現在の表現に基づいて、最も簡単な解決策は次のとおりです。

ファイル内の最大値を見つけ、それに1を加えます。


5
MAXINTがファイルに含まれている場合はどうなりますか?
Petr Peller、2011

@Petr Peller:BIGINTライブラリは本質的に整数サイズの制限を取り除きます。
oosterwal 2011

2
@oosterwal、この回答が許可されていれば、ファイルを読む必要さえありません。できるだけ大きな数を印刷してください。
Nakilon、2011

1
@oosterwal、あなたのランダムな膨大な数があなたが印刷できる最大のものであり、それがファイルにあった場合、このタスクは解決できませんでした。
ナキロン

3
@Nakilon:+1ポイントがとられます。これは、ファイル内の総桁数を計算し、その桁数の数値を出力することとほぼ同じです。
oosterwal 2011

14

使う BitSetます。1バイトあたり8でBitSetにパックされた40億の整数(最大2 ^ 32の整数と仮定)は、2 ^ 32/2 ^ 3 = 2 ^ 29 =約0.5 Gbです。

もう少し詳細を追加するには、数値を読み取るたびに、BitSetで対応するビットを設定します。次に、BitSetを渡して、存在しない最初の数値を見つけます。実際、乱数を繰り返し選択し、それが存在するかどうかをテストすることで、これと同じくらい効果的に行うことができます。

実際、BitSet.nextClearBit(0)は最初の非設定ビットを通知します。

BitSet APIを見ると、それは0..MAX_INTのみをサポートしているように見えるため、+ 've番号用と-'ve番号用の2つのBitSetが必要になる場合がありますが、メモリ要件は変わりません。


1
または、使用したくない場合BitSetは、ビットの配列を試してください。同じことをします;)
jcolebrand '23

12

サイズの制限がない場合、最も簡単な方法は、ファイルの長さを取得し、ファイルの長さ+1ランダムな数字(または単に "11111 ...")を生成することです。利点:ファイルを読み取る必要すらなく、メモリ使用量をほぼゼロに最小化できます。欠点:数十億桁を印刷します。

ただし、唯一の要因がメモリ使用量の最小化であり、他に何も重要でない場合は、これが最適なソリューションになります。それはあなたに「最悪のルール乱用」賞を与えるかもしれません。


11

数値の範囲が常に2 ^ n(2の偶数乗)であると仮定すると、exclusive-orが機能します(別のポスターに示されています)。理由については、それを証明しましょう。

その理論

ある0ベースの整数の範囲が 2^n1つの要素が欠落し要素をされている場合、既知の値を単純にXORして欠落した数値を生成することにより、その欠落要素を見つけることができます。

の証拠

n = 2を見てみましょう。n= 2の場合、4つの一意の整数(0、1、2、3)を表すことができます。ビットパターンは次のとおりです。

  • 0-00
  • 1-01
  • 2-10
  • 3-11

さて、見てみると、すべてのビットが正確に2回設定されています。したがって、これは偶数回設定され、数値の排他的論理和は0を生成します。単一の数値が欠落している場合、排他的論理和は、欠落した数値と排他的論理和を実行すると、したがって、欠落している数と、結果として得られる排他的論理和の数はまったく同じです。2を削除すると、結果のxorは10(または2)になります。

では、n + 1を見てみましょう。各ビットがに設定された回数nxおよび各ビットがに設定された回数を呼び出してみましょうn+1 y。値は、yに等しくなるy = x * 2があるのでx持つ要素n+10へのビットセットとxを有する素子n+11にビット設定し、以降2x常に偶数となり、n+1常に各ビットが偶数回設定されているであろう。

したがって、以来n=2作品、及びn+1作品、XOR方法は、すべての値のために動作しますn>=2

0ベースの範囲のアルゴリズム

これは非常に簡単です。2 * nビットのメモリを使用するため、32以下の範囲の場合、2 32ビット整数が機能します(ファイル記述子によって消費されるメモリは無視されます)。そして、ファイルのシングルパスを作成します。

long supplied = 0;
long result = 0;
while (supplied = read_int_from_file()) {
    result = result ^ supplied;
}
return result;

任意ベースの範囲のアルゴリズム

このアルゴリズムは、合計範囲が2 ^ nに等しい限り、任意の開始番号から任意の終了番号の範囲で機能します。これは、基本的に範囲が0で最小になるように再ベースします。ただし、2パスが必要です。ファイルを介して(最初に最小値を取得し、2番目に不足しているintを計算します)。

long supplied = 0;
long result = 0;
long offset = INT_MAX;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    result = result ^ (supplied - offset);
}
return result + offset;

任意の範囲

すべての範囲が2 ^ nの累乗を少なくとも1回超えるため、この変更された方法を任意の範囲のセットに適用できます。これは、単一の欠落ビットがある場合にのみ機能します。ソートされていないファイルの2つのパスが必要ですが、毎回欠落している番号が1つ見つかります。

long supplied = 0;
long result = 0;
long offset = INT_MAX;
long n = 0;
double temp;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    n++;
    result = result ^ (supplied - offset);
}
// We need to increment n one value so that we take care of the missing 
// int value
n++
while (n == 1 || 0 != (n & (n - 1))) {
    result = result ^ (n++);
}
return result + offset;

基本的に、範囲は0付近に再配置されます。次に、排他的論理和を計算するときに、追加するソートされていない値の数をカウントして追加します。次に、ソートされていない値のカウントに1を追加して、欠損値を処理します(欠損値をカウントします)。次に、nが2の累乗になるまで、毎回1ずつ増分されるn値のxorを実行します。結果は、元のベースに再ベースされます。できました。

PHPでテストしたアルゴリズムは次のとおりです(ファイルではなく配列を使用していますが、同じ概念です)。

function find($array) {
    $offset = min($array);
    $n = 0;
    $result = 0;
    foreach ($array as $value) {
        $result = $result ^ ($value - $offset);
        $n++;
    }
    $n++; // This takes care of the missing value
    while ($n == 1 || 0 != ($n & ($n - 1))) {
        $result = $result ^ ($n++);
    }
    return $result + $offset;
}

値の範囲が含まれる配列(負の値を含めてテストしました)を供給しましたが、その範囲内に欠落しているものが1つあり、毎回正しい値が見つかりました。

別のアプローチ

外部ソートを使用できるので、ギャップをチェックするだけではどうですか?このアルゴリズムの実行前にファイルがソートされていると想定すると、次のようになります。

long supplied = 0;
long last = read_int_from_file();
while (supplied = read_int_from_file()) {
    if (supplied != last + 1) {
        return last + 1;
    }
    last = supplied;
}
// The range is contiguous, so what do we do here?  Let's return last + 1:
return last + 1;

3
問題は、「1つの数値が欠落している」ということではなく、ファイル内の40億の数値に含まれていない数値を見つけることです。32ビット整数を想定すると、約3億の数値がファイルから失われる可能性があります。存在する数のxorが欠落している数と一致する可能性は、約7%にすぎません。
ジェームズウォルドビー-jwpat7 11年

ゼロベースではない連続するが欠落している1つの範囲がある場合は、xorの代わりに追加します。 sum(0..n) = n*(n+1)/2。ですからmissing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[])。(@hammarの回答からのアイデアの合計。)
Peter Cordes

9

不適切に引用されていない限り、トリックの質問。最大の整数を取得するためにファイルを1度読みn、戻りn+1ます。

もちろんn+1、整数オーバーフローが発生する場合に備えて、バックアップ計画が必要です。


3
動作する解決策は次のとおりです...動作しない場合を除きます。有用!:-)
dty

それが不適切に引用されていない限り、質問は整数のタイプ、または使用されている言語にも限界を設けませんでした。最近の言語の多くは、使用可能なメモリによってのみ制限される整数を持っています。ファイル内の最大の整数が10MBを超える場合は、2番目のケースでは困難です。私のお気に入りのソリューション。
ユルゲン・ストローベル

9

出力、その後、入力ファイルのサイズを確認してください任意のある数の大きさというファイルで表現するには大きすぎるし。これは安っぽいトリックのように思えるかもしれませんが、インタビューの問題に対する独創的な解決策であり、メモリの問題をきちんと回避し、技術的にはO(n)です。

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

10 ビットカウント -1を出力する必要があります。これは常に2 ビットカウントより大きくなります。技術的には、あなたがビートに合わせて持っている数は2 BITCOUNT(4×10 - 9 - 1)ファイル内の他の整数、さらに完璧な圧縮と、彼らは少なくとも取るよ-あなたがそこに(1 40億)が知っていることから、それぞれ1ビット。


なぜConsole.Write( 1 << bitcount )ループの代わりにだけではないのですか?ファイルにnビットがある場合は、先頭に1が付いた(_n_ + 1)ビットの数値が必ず大きくなります。
Emmet 2013年

@Emmet-ファイルがint(C#では4バイト)のサイズよりも小さい場合を除き、整数オーバーフローが発生します。C ++ではもっと大きなものを使用できるかもしれませんが、C#では、<<演算子で32ビット整数以外は許可されていないようです。どちらの方法でも、独自の巨大な整数型をロールしない限り、ファイルサイズは非常に小さくなります。デモ:rextester.com/BLETJ59067
ジャスティンモーガン、

8
  • 最も簡単な方法は、ファイル内の最小数を見つけ、それより1少ない数を返すことです。これは、O(1)ストレージを使用し、n個の数値のファイルに対してO(n)時間を使用します。ただし、数値範囲が制限されている場合は失敗し、min-1が数値ではなくなる可能性があります。

  • ビットマップを使用する単純で簡単な方法はすでに述べました。その方法はO(n)時間とストレージを使用します。

  • 2 ^ 16カウントバケットを使用する2パス法についても説明しました。2 * n整数を読み取るため、O(n)時間とO(1)ストレージを使用しますが、2 ^ 16を超える数のデータセットを処理できません。ただし、2の代わりに4パスを実行することで(たとえば)2 ^ 60 64ビット整数に簡単に拡張でき、メモリに収まる数のビンのみを使用して、それに応じてパス数を増やすことで、小さなメモリの使用に簡単に適応できます。この場合、ランタイムはO(n)ではなく、O(n * log n)になります。

  • ltn100が指摘したように、これまでにrfrankelによって、ついにircmaxellによって言及されたすべての数値をXORする方法は、stackoverflow#35185で尋ねられた質問に答えます。O(1)ストレージとO(n)ランタイムを使用します。とりあえず、32ビット整数を想定すると、XORは7%の確率で異なる数を生成します。理論的根拠:〜4Gの個別の数値をXORした場合、約 ファイルに300Mはありません。各ビット位置の設定ビット数は、奇数または偶数になる可能性が等しくなります。したがって、2 ^ 32の数値は、XORの結果と同じ確率で発生し、そのうち93%はすでにファイルに登録されています。ファイル内の数値がすべて異なるわけではない場合、XORメソッドの成功確率が高くなることに注意してください。


7

どういうわけか、この問題を読んですぐに、対角化について考えました。私は任意の大きな整数を想定しています。

最初の番号を読んでください。40億ビットになるまで、ゼロビットを左に埋め込みます。最初の(上位)ビットが0の場合、出力1。それ以外の場合は0を出力します(実際に左パディングする必要はありません。数値に十分なビットがない場合は、1を出力します)。2番目のビットを使用することを除いて、2番目の数値で同じことを行います。この方法でファイルを続行します。一度に40億ビットの数値を1ビットずつ出力しますが、その数値はファイル内のどの数値とも同じではありません。証明:n番目の数値と同じで、n番目のビットについては同意しますが、構造上はそうではありません。


創造性のための+1(そして、シングルパスソリューションの最小のワーストケース出力)。
hmakholmがモニカに残った'08

しかし、対角化するビットは40億ビットではなく、32ビットしかありません。リストの最初の32の数値とは異なる32ビットの数値になるだけです。
ブライアンゴードン

@ヘニングこれはシングルパスではありません。それでも、単項からバイナリに変換する必要があります。編集:まあ、それはファイルの1つのパスだと思います。気にしないで。
ブライアンゴードン

@ブライアン、どこに「単項」があるの?答えは、1度に1つのバイナリの答えを作成することであり、入力ファイルを1回だけ読み取るため、シングルパスになります。(10進数の出力が必要な場合、問題が発生します。おそらく、3つの入力数値ごとに1桁の10進数を作成し、出力数の対数の10%増加を受け入れる方が良いでしょう)。
hmakholmがモニカに残った

2
@Henningこの問題は、多くの人が指摘しているように、最大​​の数を見つけて1を追加するか、ファイル自体から非常に長い数を作成するのは簡単なため、任意の大きな整数には意味がありません。この対角化ソリューションは特に不適切です。なぜなら、ithビットで分岐するのではなく、1ビットを40億回出力し、最後に余分な1をスローするだけだからです。アルゴリズムに任意の大きな整数を使用しても問題ありませんが、問題は不足している32ビット整数を出力することだと思います。それ以外の意味はありません。
ブライアンゴードン

6

ビットフラグを使用して、整数が存在するかどうかをマークできます。

ファイル全体をトラバースした後、各ビットをスキャンして、数値が存在するかどうかを判断します。

各整数が32ビットであると仮定すると、ビットフラグが設定されている場合、1 GBのRAMに都合よく収まります。


バイトを4ビットに再定義していない限り、0.5 Gb ;-)
dty

2
@dty 1Gbにはたくさんのスペースがあるので、彼は「快適に」という意味だと思います。
corsiKa

6

ファイルから空白と数字以外の文字を取り除き、1を追加します。これで、ファイルには、元のファイルにリストされていない単一の番号が含まれています。

CarbonetcによるRedditから。


大好きです!それは彼が探していた答えではありませんが…:D
ヨハン・デュ・トワ

6

完全を期すために、もう1つの非常に単純なソリューションを示します。これは、実行に非常に長い時間がかかる可能性がありますが、メモリの使用量は非常に少ないです。

すべての可能な整数をからint_minまでの範囲としint_maxbool isNotInFile(integer)ファイルに特定の整数が含まれていない場合はtrueを返し、それ以外の場合はfalseを返します(その特定の整数をファイル内の各整数と比較することにより)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}

問題は、isNotInFile関数のアルゴリズムに関するものでした。回答する前に、質問を理解しておいてください。
Aleks G 2011

2
いいえ、問題は「整数がファイルにありません」であり、「ファイルに整数xはありません」ではありませんでした。後者の質問への回答を決定する関数は、たとえば、ファイル内のすべての整数を問題の整数と比較して、一致するとtrueを返すだけです。
2011

これは正解だと思います。I / Oを除いて、必要なのは1つの整数とブールフラグだけです。
ブライアンゴードン

@Aleks G-これが間違っているとマークされている理由がわかりません。すべての中で最も遅いアルゴリズムであることに私たちは皆同意します:-)が、それは機能し、ファイルを読み取るのに4バイトしか必要ありません。たとえば、元の質問では、ファイルを一度だけ読み取ることができるとは規定されていません。
Simon Mourier、2011

1
@Aleks G-そうです。私もあなたがそのように言ったとは言いませんでした。IsNotInFileはファイルのループを使用して簡単に実装できると言います:Open; While Not Eof {Read Integer; Return False if Integer = i; Else Continue;}。必要なメモリは4バイトだけです。
Simon Mourier、2011

5

10 MBのメモリ制約の場合:

  1. 数値をバイナリ表現に変換します。
  2. 左= 0および右= 1のバイナリツリーを作成します。
  3. バイナリ表現を使用して、ツリーに各数値を挿入します。
  4. すでに番号が挿入されている場合、リーフはすでに作成されています。

終了したら、要求された数を作成するために、以前に作成されていないパスを選択します。

40億の数値= 2 ^ 32、つまり10 MBでは十分でない場合があります。

編集する

2つのエンドリーフが作成され、共通の親がある場合、最適化が可能です。それらを削除して、親にソリューションではないというフラグを付けることができます。これは枝を切り、メモリの必要性を減らします。

編集II

完全にツリーを構築する必要もありません。数が似ている場合にのみ、深いブランチを構築する必要があります。枝を切る場合も、このソリューションは実際に機能する可能性があります。


6
...そしてそれはどのように10 MBに収まりますか?
hmakholmがモニカを去った

方法:BTreeの深さを10MBに収まるものに制限します。これは、セット{false positive | そして、他の手法を使用して値を見つけることができます。
ジョナサンディキンソン

5

私は1 GBバージョンに答えます:

質問には十分な情報がないため、最初にいくつかの前提を述べておきます。

整数は32ビットで、範囲は-2,147,483,648〜2,147,483,647です。

疑似コード:

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}

4

私たちが創造的な答えをしている限り、ここに別のものがあります。

外部ソートプログラムを使用して、入力ファイルを数値でソートします。これは、メモリの量に関係なく機能します(必要に応じてファイルストレージを使用します)。ソートされたファイルを読み、欠落している最初の数値を出力します。


3

ビット除去

1つの方法はビットを削除することですが、実際には結果が得られない可能性があります(そうでない場合があります)。擬似コード:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

ビット数

ビット数を追跡​​します。そして、最小の量のビットを使用して値を生成します。この場合も、正しい値が生成される保証はありません。

範囲ロジック

リストの順序付けされた範囲(開始順)を追跡します。範囲は次の構造で定義されます。

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

ファイルの各値を調べ、現在の範囲から削除してみてください。このメソッドにはメモリの保証はありませんが、かなりうまくいくはずです。


3

2 128 * 10 18 + 1((2 816 * 10 18 + 1です)-今日の普遍的な答えになりませんか?これは、16 EBファイルで保持できない数値を表しています。これは、現在のファイルシステムでの最大ファイルサイズです。


そして、どのように結果を印刷しますか?ファイルに入れることはできません。画面に印刷するには数十億年かかります。今日のコンピュータで達成できる可能性が高い稼働時間ではありません。
vsz 2011

結果をどこにでも印刷する必要があるとは言われず、「生成」するだけです。したがって、それは生成によって何を意味するかによって異なります。とにかく、私の答えは、実際のアルゴリズムの作成を回避するためのトリックにすぎません:)
Michael Sagalovich '29

3

これは解決された問題(上記を参照)だと思いますが、質問される可能性があるため、覚えておかなければならない興味深いサイドケースがあります。

繰り返しのない正確に4,294,967,295(2 ^ 32-1)32ビット整数があり、1つだけ欠けている場合、簡単な解決策があります。

積算合計をゼロから開始し、ファイル内の整数ごとに、その整数を32ビットオーバーフローで追加します(効果的には、runningTotal =(runningTotal + nextInteger)%4294967296)。完了したら、現在の合計に4294967296/2を追加します。これも32ビットオーバーフローです。4294967296からこれを引くと、結果は整数が欠落します。

「欠落している整数が1つだけ」の問題は、1回の実行と、データ専用の64ビットRAM(現在の合計では32ビット、次の整数を読み取るには32ビット)で解決できます。

結果:整数の結果に必要なビット数を気にしない場合、より一般的な仕様は非常に簡単に一致します。与えられたファイルに含めることができないほど大きな整数を生成するだけです。繰り返しますが、これは絶対に最小限のRAMを使用します。疑似コードを参照してください。

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}

@NakilonとTheDayTurnsは、元の質問へのコメントでこれを指摘しました
ブライアン・ゴードン

3

ライアンが基本的に言ったように、ファイルをソートしてから整数を調べ、値がスキップされるとそれが得られます:)

EDIT downvoters時:OPはこれが有効な方法であるようにファイルをソートすることができることを述べました。


重要な部分の1つは、あなたが行くときにそれを行うべきであり、そのため、一度だけ読む必要があるということです。物理メモリへのアクセスが遅い。
Ryan Amos

@ryan外部ソートはほとんどの場合マージソートなので、最後のマージでチェックを行うことができます:)
ラチェットフリーク

データがディスク上にある場合は、メモリにロードする必要があります。これは、ファイルシステムによって自動的に行われます。1つの数値を見つける必要がある場合(問題は他に述べていません)、ソートされたファイルを一度に1行ずつ読み取るのが最も効率的な方法です。メモリをほとんど使用せず、何よりも遅くなることはありません。ファイルを読み取る必要があります。
Tony Ennis

メモリが1 GBしかない場合、40億の整数をどのように並べ替えますか?仮想メモリを使用する場合、メモリブロックが物理メモリにページインおよびページアウトされるため、かなりの時間がかかります。
KlasLindbäck

4
@klas マージソートはそのために設計されています
ラチェットフリーク

2

32ビットの制約を想定していない場合は、ランダムに生成された64ビットの数値(悲観論者の場合は128ビット)を返します。衝突の可能性は1 in 2^64/(4*10^9) = 4611686018.4(約40億分の1)です。ほとんどの場合、あなたは正しいでしょう!

(冗談...ちょっと)


私はこれがすでに提案されているのを見る:)それらの人々のための賛成票
ピーターギブソン

誕生日のパラドックスは、ランダムな推測が実際に有効な答えであるかどうかを確認するためにファイルをチェックせずに、この種類のソリューションをリスクに見合わないものにします。(この場合、誕生日のパラドックスは適用されませんが、この関数を繰り返し呼び出して新しい一意の値を生成すると、誕生日のパラドックスが発生します。)
Peter Cordes

@PeterCordesランダムに生成された128ビットの数値は、UUIDの正確な動作方法です。WikipediaのUUIDページで
Peter Gibson

バリアント:セット内の最大値を見つけ、1を追加します
Phil

私は元の配列(追加のストレージなし)をクイックソートしてから、配列全体を行進し、最初の「スキップされた」整数を報告します。できました。質問に答えました。
レベル42
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.