web-dev-qa-db-ja.com

JavaScriptで整数のビット数を効率的にカウントする

整数Iがあり、1のカウントをバイナリ形式で取得したいとします。

現在、次のコードを使用しています。

Number(i.toString(2).split("").sort().join("")).toString().length;

これを行うより速い方法はありますか?ビット演算子の使用を考えています。何かご意見は?

注:iは32ビットの制限内です。

13
Ming Huang

この Bit Twiddling Hacks のコレクションの戦略を使用できます。

function bitCount (n) {
  n = n - ((n >> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
  return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
}

console.log(bitCount(0xFF)) //=> 8

上記の戦略は32ビット整数(JavaScriptのビット単位演算子の制限)に対してのみ機能することに注意してください。

より大きな整数のより一般的なアプローチには、32ビットのチャンクを個別にカウントすることが含まれます(インスピレーションを得るために harold に感謝します)。

function bitCount (n) {
  var bits = 0
  while (n !== 0) {
    bits += bitCount32(n | 0)
    n /= 0x100000000
  }
  return bits
}

function bitCount32 (n) {
  n = n - ((n >> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
  return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
}

console.log(bitCount(Math.pow(2, 53) - 1)) //=> 53

正規表現を使用することもできます。

function bitCount (n) {
  return n.toString(2).match(/1/g).length
}

console.log(bitCount(0xFF)) //=> 8
15
gyre

再帰的な非常にいいが遅い方法:

    function count1(n, accumulator=0) {
      if (n === 0) {
        return accumulator
      }
      return count1(n/2, accumulator+(n&1))
    }

console.log(count1(Number.MAX_SAFE_INTEGER));

しかし、非常に速いもの(T.J. Crowderの回答よりも速い)が必要な場合):

    count1s=(n)=>n.toString(2).replace(/0/g,"").length

console.log(count1s(Number.MAX_SAFE_INTEGER));

注:他のソリューションの一部は、ビット整数(> 32ビット)では機能しません。これら2つは機能します!

ここで、32ビットの数値のみを考慮する場合、最速の方法は次のとおりです。

function count1s32(i) {
  var count = 0;
  i = i - ((i >> 1) & 0x55555555);
  i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
  i = (i + (i >> 4)) & 0x0f0f0f0f;
  i = i + (i >> 8);
  i = i + (i >> 16);
  count += i & 0x3f;
  return count;
}

  console.log(count1s32(0xffffffff));

https://jsperf.com/count-1/1

53ビットの比較:

53 bit test

32ビット比較:

32 bit test

ベンチマークはこちら! (jsperfがダウンしていることが多いため)。

function log(data) {
  document.getElementById("log").textContent += data + "\n";
}

benchmark = (() => {

  time_function = function(ms, f, num) {
    var z;
    var t = new Date().getTime();
    for (z = 0;
      ((new Date().getTime() - t) < ms); z++) f(num);
    return (z / ms)
  } // returns how many times the function was run in "ms" milliseconds.

  // two sequential loops
  count1s = (n) => n.toString(2).replace(/0/g, "").length

  // three loops and a function.
  count1j = (n) => n.toString(2).split('').filter(v => +v).length

  /*  Excluded from test because it's too slow :D
  
      function count1(n, accumulator=0) {
          if (n === 0) {
              return accumulator
          }
          return count1(n / 2, accumulator + (n & 1))
      }
  */

  function countOnes(i) {
    var str = i.toString(2);
    var n;
    var count = 0;
    for (n = 0; n < str.length; ++n) {
      if (str[n] === "1") {
        ++count;
      }
    }
    return count;
  } // two sequential loops ( one is the toString(2) )

  function count1sb(num) {
    i = Math.floor(num / 0x100000000);
    //      if (i > 0) {
    i = i - ((i >> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
    i = (i + (i >> 4)) & 0x0f0f0f0f;
    i = i + (i >> 8);
    i = i + (i >> 16);
    count = i & 0x3f;
    i = num & 0xffffffff;
    //      }
    i = i - ((i >> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
    i = (i + (i >> 4)) & 0x0f0f0f0f;
    i = i + (i >> 8);
    i = i + (i >> 16);
    count += i & 0x3f;
    return count;
  }

  function benchmark() {
    function compare(a, b) {
      if (a[1] > b[1]) {
        return -1;
      }
      if (a[1] < b[1]) {
        return 1;
      }
      return 0;
    }



    funcs = [
      [count1s, 0],
      [count1j, 0],
      [count1sb, 0],
      [countOnes, 0]
    ];

    funcs.forEach((ff) => {
      console.log("Benchmarking: " + ff[0].name);
      ff[1] = time_function(2500, ff[0], Number.MAX_SAFE_INTEGER);
      console.log("Score: " + ff[1]);

    })
    return funcs.sort(compare);
  }

  return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();
console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
  log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea cols=80 rows=30 id="log"></textarea>

ベンチマークは10秒間実行されます。

2
Zibri

以下は任意の数で正常に動作します:

_var i=8823475632,count=0;while (i=Math.floor(i)) i&1?count++:0,i/=2
console.log(count); //17_

iを必要な値に変更するか、関数としてラップします

整数が32ビット以内の場合、以下が機能します

var i=10,count=0;while (i) i&1?count++:0,i>>=1
1
Keyang

n = n & (n - 1)を実行すると、数値の最後の1ビットが削除されます。これによると、次のアルゴリズムを使用できます。

function getBitCount(n) {
  var tmp = n;
  var count = 0;
  while (tmp > 0) {
    tmp = tmp & (tmp - 1);
    count++;
  }
  return count;
}

console.log(getBitCount(Math.pow(2, 10) -1));
1
eldarg

配列を作成、並べ替え、結合する場合、文字どおり高速にしたい場合は、退屈な方法で行う方がよいでしょう。

_console.log(countOnes(8823475632));

function countOnes(i) {
  var str = i.toString(2);
  var n;
  var count = 0;
  for (n = 0; n < str.length; ++n) {
    if (str[n] === "1") {
      ++count;
    }
  }
  return count;
}_

(廃止されたブラウザーをサポートする必要がある場合は、_str[n]_ではなくstr.charAt(n)を使用してください。)

L33tや簡潔ではありませんが、 きっと速いそれははるかに速い

enter image description here

...そして同様に、Firefox、IE11(IE11ほどではありません)。

0
T.J. Crowder

Numbersort、2番目のtoStringはスキップできます。 filterを使用して、配列内の1s(真の値)のみを考慮し、lengthで通過した数を取得します。

i.toString(2).split('').filter(v => +v).length
0
Joseph