100万を超える可変長の巨大なセットがあります。
['773', '2267', '8957251', '170597519', '373590109', '982451707', '999999937', ......]
数字の束、たとえば3と7を指定して、これらの数字を含むすべての数値のサブセットを作成するつもりです。あれは:
['773', '373590109', '999999937', ......]
この種類の検索は、異なる数字に対して複数回行われます。したがって、毎回リスト全体を繰り返すことはオプションではありません。
プログラムの起動時に、各桁に1つずつ、10個のサブセットを作成することを検討しています。各セットには、その数字を含むすべての番号が含まれます。次に、各ルックアップで集合交差を使用する予定です。この方法についてどう思いますか?より速い結果を達成するより良い方法はありますか?
これをC++で実装しています。しかし、より簡単な方法が利用できる場合は、Pythonでも同じことを実行できます。
少し前に戻り、これを行う理由を理解してみましょう。数百万の数値のリストにある値の存在を検索できるようにしたいですか?これを何度も行う必要がありますか、それとも1回だけ行う必要がありますか?
私が尋ねる理由は、それが一度だけの場合、最も効率的な方法は、数値をメモリにロードして、配列内にその数値が存在するかどうかについて1つのパスを実行することです。スレッドはこれをさらに効率的にし、作業をより小さな部分に分割します。配列がこれに対して大きすぎる場合は、一度に1つのチャンクをロードできますが、10の異なる配列で数字ごとに1つずつ重複している可能性がある場合、メモリはここでは問題になりません。
繰り返し検索を実行する必要がある場合は、 トライ の使用をお勧めします。検索する最初の桁を含む9つのビンを作成します。その子には、検索する2番目の数字を含む10個のビンがあります。何度でも繰り返してください。あなたが言及した10配列のアプローチに関して、これは間違いなくlessメモリを占有します(ツリー内の非リーフノードは複数の数を表し、一つだけです)。
あなたのアプローチが潜在的に非効率的であるかもしれない理由は、数字を含む数の可能性が数字を追加するごとに著しく増加するためです。 10桁の数字では、特定の数字が表示されない可能性は約(9/10)^ 10または34%です。つまり、66%の確率で10桁の数字が特定のセットに表示される可能性があります。これは、およそ66%のデータの重複について話していることを意味します。数値が小さいほど好ましい場合がありますが、3桁の数値でも重複率は27%です。
メモリが問題ではなかったとしても、数値の複製により、検索アルゴリズムの効率が低下します。
したがって、私の推奨は、ルックアップにトライを使用することです。あなたの質問に答えるために、特定の数字を含むすべての数字のリストを取得するには、あなたのアプローチが最善です。森から木が見えない限り、このアプローチはおそらくあなたが達成したいものではありません。
セット交差は間違いなく可能性です。別の方法は、ビットフィールドを使用して、数字に含まれる数字を記述することです。次に、単純な論理演算を実行して比較できます。たとえば、数値773の場合、ビット7と3が設定されているため、ビットマスクは0x88(バイナリ= 10001000)になります。次に、任意の数値のビットマスクと0x88の論理「AND」を取得できます。結果が0x88の場合、その数値には問題の数字が含まれています。
Pythonでこれを行うのは簡単です。
def find_nums_with_digits(nums, digits):
digits = ''.join(sorted(digits))
char_map = {c: c if c in digits else None for c in '0123456789'}
trans = str.maketrans(char_map)
for num in nums:
if digits in ''.join(sorted(num.translate(trans))):
yield num
nums = ['773', '2267', '8957251', '170597519', '373590109', '982451707', '999999937']
for num in find_nums_with_digits(nums, '73'):
print(num)
ここでの考え方は、数字の文字列を並べ替えて不要な数字を削除すると、条件は部分文字列テストになるということです。
他の回答で述べたように、数字の文字列の同じ大きなセットで数字の多くの組み合わせをテストする場合は、これを行うためのより効率的な方法があります。
セットの共通部分を見つけるには、 ハッシュ結合 のようなものが必要です。設定の多くは事前に計算され、メモリに保持される可能性がありますが、それでも以下のすべてを実行する必要があります。
N
で数字a
とb
を検索するとします。a
を検索し、N[i]
が存在するかどうかを確認しますN[i]
が存在するかどうかを確認しますb
を検索し、N[i]
が存在するかどうかを確認しますN[i]
が存在するかどうかを確認します一方、テーブルをスキップして、その場で確認するための効率的なアルゴリズムを作成した場合、次のことができます。
N
で数字a
とb
を検索するとします。a
およびb
と比較しますそのため、ハッシュ結合の場合、個々のN[i]
ごとにいくつかの複雑な検索機能と場所機能があり、すべてメモリの読み取り、増分、比較操作が必要です。これは、{数値の数} x {ハッシュテーブルの数} x {ハッシュルックアップの行+ハッシュバケットの行}を意味します。彼らはすべて増殖します!一方、ストレートスキャンの場合、数値ごとに2つの低レベル演算(除算+モジュラス演算と比較)があります。それははるかに、はるかに少ない処理です。
パフォーマンスを個別に評価することは困難ですが、ハッシュ結合ソリューションからパフォーマンスが得られたとしても、あまり得られないと思います。実際、選択性が低いために悪化する可能性があります。1〜1,000,000の数値を指定すると、それらの約50%に任意の桁が割り当てられます。その数がはるかに小さい場合、ハッシュテーブルはパフォーマンスをかなり向上させますが、データの半分以上をプルバックしている場合、スキャンはますます見栄えがよくなります。ハッシュテーブルをサポートするために必要なメモリ使用量の増加(したがってワーキングセットの増加)を検討するときは、ハッシュ/交差設計のパフォーマンスが低下することに賭けます。
以下は、一連の数字の存在を効率的にチェックする簡単なコードです。私はDell Precisionラップトップでこのアルゴリズムを使用し、1,000,000秒の数値を0.0120秒でスキャンすることができました。必要に応じて、リストの番号ごとにこの関数を実行します。
int ContainsDigits(int numberToCheck, int digitsToFind[], int digitCount)
{
int result;
int digits[10];
memcpy(digits, digitsToFind, digitCount * sizeof(int));
while (numberToCheck > 0)
{
std::div_t result = std::div(numberToCheck, 10);
for (int i = digitCount -1; i >= 0; i--)
{
if (result.rem == digits[i])
{
if (!--digitCount) return 1;
digits[i] = digits[digitCount];
}
}
numberToCheck = result.quot;
}
return 0;
}
アルゴリズムは、ループ内の最下位桁(n % 10
で指定)をチェックしてから、数値を右にシフトします(n / 10
と同等)。 1つの演算(std::div
)で係数と商を取得できます。
求められる数字は配列に格納されます。数字が見つかると、配列は1つの要素だけ短くなり、最後の要素が見つかった数字の場所に移動するため、リストは圧縮されたままになります。配列が空の場合、すべての数字が見つかりました。ループが終了し、すべての数字が見つからなかった場合、関数はfalseを返します。
元のアイデアに少し似ている別のオプションは、ハッシュが各番号の数字のビットマスク(user1118321の回答で説明されている)である構造のようなハッシュテーブルを作成することです。次に、各数値は1024の可能なセットの1つにバケット化されます。次にルックアップするには、対応するビットマスクとANDキーを使用して、他の回答と同様にセットを検索します。ここでの違いは、セット全体をループするのではなく、最大で1024の値をチェックする必要があることです。
これの1つの利点は、バケット間で重複が発生しないことです。ストレージはNに1024キーを加えたものになります。キーala Neilのソリューションを試してみたい場合は、それも実行できます。