質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

8回答

3922閲覧

ビット列のカウント、比較方法について

tiitoi

総合スコア21956

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

4クリップ

投稿2020/09/15 14:42

編集2020/09/16 15:38

ビット列のカウント、比較について、以下の問題の演算回数を削減する方法がもしあれば教えていただきたいです。

ビットカウントの分割統治法 のようにビット演算を活用してなにか上手くできないかと考えているのですが、なかなか思いつきません。
プログラムは C++ で書いており、下記問題を計算する部分の呼び出し回数が多く、ボトルネックとなっているため、少しでも演算回数を減らしたいというのが質問の背景です。

追記 9/17

沢山のアイデア、サンプルコードありがとうございます。
実行時間を検証した結果、以下のようになりました。

  • C++17

  • Visual Studio 2019

  • ベンチマーク 検証コード

  • テストケース: 今回解きたい全パターン 40万件弱

  • CPU: Core™ i9-9900K 3.6 GHz

  • 40万件*100回=4000万回の計算時間を算出したら以下のようになりました。

合計のカウント1以上の要素数2以上の要素数2の要素数
質問記載コード1358ms1294ms1452ms1458ms
SHOMI さんのコード1074ms (-284)1215ms (-79)1280ms (-172)1256ms (-202)
kazuma-sさんのコード1068ms (-290)1140ms (-154)1137ms (-315)1144ms (-314)
kazuma-sさんのコード2978ms (-380)949ms (-345)1026ms (-426)996ms (-462)
kichirb3 さんのコード957ms (-401)854ms (-440)786ms (-666)986ms (-472)

皆さんにご回答いただいた内容を理解していこうと思いますが、時間がかかると思うので一旦質問はクローズします。
全員 BA としたいのですが仕様上できないので、最初にビット演算のコードを使った回答をしていただいた SHOMI さんを BA に選択します。
ありがとうございました。d

問題設定

各値が 0 ~ 4 である長さが 9 の配列があるとします。
各値の最大値は4 (0b100) で 3bit あれば表せるため、配列全体を 3 * 9 = 27bit 表現します。

  • 1 ~ 3bit: a[0]
  • 3 ~ 6bit: a[1]
  • ...
  • 21 ~ 27bit: a[9]

イメージ説明

例えば、[0, 2, 0, 2, 2, 1, 1, 1, 4] の場合、ビット列での表現は 100|001|001|001|010|010|000|010|000 になります。(| は区切りがわかりやすいように入れています)

言語・環境について

  • 動作環境は C++17 です。
  • コンパイラは Visual Studio 2019 Update6
  • 今は int (32bit) にビット値を格納しています。28~32bitは未使用で0になっています。
  • 計算量削減以外に C++ に特化した最適化方法のアイデア等でも大丈夫です。

サンプルコードが C++/Python ですが、ビット演算はどの言語にもあると思うので、ビット演算を使用した計算法であれば、特に言語を限定した質問ではないです。

質問内容

このときにこのビット列から以下を求めたいのですが、サンプルコードのように各ブロックごとに取り出して加算していくやり方しか思いつきません。ビット演算などを活用し、より少ない演算回数で計算できる方法がもしあれば教えていただきたいです。

  1. 元の配列の各値の合計
  2. 元の配列の各値が1以上の要素数
  3. 元の配列の各値が2以上の要素数
  4. 元の配列の各値が2の要素数
  • どれか1つだけでもよいです。
  • コードでなくとも、アイデアだけでも助かります。

例: [0, 2, 0, 2, 2, 1, 1, 1, 4] (69510160)の場合

  1. 元の配列の各値の合計: 13
  2. 元の配列の各値が1以上の要素数: 7
  3. 元の配列の各値が2以上の要素数: 4
  4. 元の配列の各値が2の要素数: 3

C++

cpp

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 7 // 各値の合計 8 { 9 int cnt = 0; 10 for (int i = 0; i < 9; ++i) 11 cnt += (x >> i * 3) & 0b111; 12 13 std::cout << cnt << std::endl; 14 } 15 16 // 各値が1以上の要素数 17 { 18 int cnt = 0; 19 for (int i = 0; i < 9; ++i) { 20 if ((x >> i * 3) & 0b111) 21 cnt++; 22 } 23 std::cout << cnt << std::endl; 24 } 25 26 // 各値が2以上の要素数 27 { 28 int cnt = 0; 29 for (int i = 0; i < 9; ++i) { 30 if (((x >> i * 3) & 0b111) >= 2) 31 cnt++; 32 } 33 std::cout << cnt << std::endl; 34 } 35 36 // 各値が2の要素数 37 { 38 int cnt = 0; 39 for (int i = 0; i < 9; ++i) { 40 if (((x >> i * 3) & 0b111) == 2) 41 cnt++; 42 } 43 std::cout << cnt << std::endl; 44 } 45}

少し演算回数を減らしたコード

cpp

1#include <iostream> 2#include <vector> 3 4/** 5 * @brief マスク 6 */ 7const std::vector<int> mask = { 8 7, 7 << 3, 7 << 6, 7 << 9, 7 << 12, 7 << 15, 7 << 18, 7 << 21, 7 << 24, 9}; 10 11/** 12 * @brief 2以上かどうか調べるときのマスク 13 */ 14const std::vector<int> ge2 = { 15 6, 6 << 3, 6 << 6, 6 << 9, 6 << 12, 6 << 15, 6 << 18, 6 << 21, 6 << 24, 16}; 17 18int main() 19{ 20 int x = 69510160; 21 22 // 各値の合計 23 { 24 int cnt = 0; 25 for (int i = 0; i < 9; ++i) 26 cnt += (x >> i * 3) & 0b111; 27 28 std::cout << cnt << std::endl; 29 } 30 31 // 各値が1以上の要素数 32 { 33 int cnt = 0; 34 for (int i = 0; i < 9; ++i) { 35 if (x & mask[i]) 36 cnt++; 37 } 38 std::cout << cnt << std::endl; 39 } 40 41 // 各値が2以上の要素数 42 { 43 int cnt = 0; 44 for (int i = 0; i < 9; ++i) { 45 if (x & ge2[i]) 46 cnt++; 47 } 48 std::cout << cnt << std::endl; 49 } 50 51 // 各値が2の要素数 52 { 53 int cnt = 0; 54 for (int i = 0; i < 9; ++i) { 55 if (((x >> i * 3) & 0b111) == 2) 56 cnt++; 57 } 58 std::cout << cnt << std::endl; 59 } 60}

Python

python

1from functools import reduce 2 3 4def to_bit(key): 5 """ビット表現に変換する。 6 """ 7 return reduce(lambda x, y: x * 8 + y, key[::-1]) 8 9 10def to_str(x): 11 """ビット表現を表す文字列に変換する。(デバッグ用) 12 """ 13 s = f"{x:027b}" 14 s = "|".join([s[i : i + 3] for i in range(0, len(s), 3)]) 15 return s 16 17########### 以下が本題 18 19key = to_bit([0, 2, 0, 2, 2, 1, 1, 1, 4]) # 実際は元の配列はなく、ビット列だけ与えられます 20print(key, to_str(key)) 21# 69510160 100|001|001|001|010|010|000|010|000 22 23 24# 各値の合計 25cnt = 0 26for i in range(9): 27 cnt += (key >> i * 3) & 0b111 28print(cnt) # 13 29 30 31# 各値が1以上の要素数 32cnt = 0 33for i in range(9): 34 val = (key >> i * 3) & 0b111 35 if val: 36 cnt += 1 37print(cnt) # 7 38 39 40# 各値が2以上の要素数 41cnt = 0 42for i in range(9): 43 val = (key >> i * 3) & 0b111 44 if val >= 2: 45 cnt += 1 46print(cnt) # 4 47 48 49# 各値が2の要素数 50cnt = 0 51for i in range(9): 52 val = (key >> i * 3) & 0b111 53 if val == 2: 54 cnt += 1 55print(cnt) # 3
mjk👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2020/09/17 01:23

雑魚の戯言ですが, もしも,ある27bitのデータに対して,1.~4. の全ての値を計測せねばならないのだとすれば, 要素値のヒストグラムを1回作れば(早いかは知りませんけども)「楽」な気はします.
guest

回答8

0

ベストアンサー

ビットカウントの分割統治法 のように

こう?

C++

1int sum(unsigned int bits) 2{ 3 bits = (bits & 0b00000111000111000111000111000111) + (bits >> 3 & 0b00000111000111000111000111000111); 4 bits = (bits & 0b00001111000000001111000000001111) + (bits >> 6 & 0b00000000000000001111000000001111); 5 bits = (bits & 0b00011111000000000000000000011111) + (bits >> 12 & 0b00000000000000000000000000011111); 6 return (bits & 0b00000000000000000000000000111111) + (bits >> 24 & 0b00000000000000000000000000111111); 7} 8int countGreaterThanOrEqualTo1(unsigned int bits) 9{ 10 bits |= (bits & 0b00010010010010010010010010010010)>> 1; 11 bits |= (bits & 0b00100100100100100100100100100100)>> 2; 12 bits = (bits & 0b00000001000001000001000001000001) + (bits >> 3 & 0b00000001000001000001000001000001); 13 bits = (bits & 0b00000011000000000011000000000011) + (bits >> 6 & 0b00000000000000000011000000000011); 14 bits = (bits & 0b00000111000000000000000000000111) + (bits >> 12 & 0b00000000000000000000000000000111); 15 return (bits & 0b00000000000000000000000000001111) + (bits >> 24 & 0b00000000000000000000000000001111); 16} 17int countGreaterThanOrEqualTo2(unsigned int bits) 18{ 19 bits >>= 1; 20 bits |= bits >> 1; 21 bits = (bits & 0b00000001000001000001000001000001) + (bits >> 3 & 0b00000001000001000001000001000001); 22 bits = (bits & 0b00000011000000000011000000000011) + (bits >> 6 & 0b00000000000000000011000000000011); 23 bits = (bits & 0b00000111000000000000000000000111) + (bits >> 12 & 0b00000000000000000000000000000111); 24 return (bits & 0b00000000000000000000000000001111) + (bits >> 24 & 0b00000000000000000000000000001111); 25} 26int count2(unsigned int bits) 27{ 28 bits ^= 0b00010010010010010010010010010010; 29 bits |= (bits & 0b00010010010010010010010010010010) >> 1; 30 bits |= (bits & 0b00100100100100100100100100100100) >> 2; 31 bits = (bits & 0b00000001000001000001000001000001) + (bits >> 3 & 0b00000001000001000001000001000001); 32 bits = (bits & 0b00000011000000000011000000000011) + (bits >> 6 & 0b00000000000000000011000000000011); 33 bits = (bits & 0b00000111000000000000000000000111) + (bits >> 12 & 0b00000000000000000000000000000111); 34 return 10 - ((bits & 0b00000000000000000000000000001111) + (bits >> 24 & 0b00000000000000000000000000001111)); 35}

すみません、上記コードは30bit(a[9])まで使用していました。
上位bitが0で埋まっていれば同じですが、27bitまでの場合は以下コードで。
どちらのコードも上位ビットが0ならbits >> 24のあとの& 0b~は不要です。

C++

1int sum(unsigned int bits) 2{ 3 bits = (bits & 0b00000111000111000111000111000111) + (bits >> 3 & 0b00000000000111000111000111000111); 4 bits = (bits & 0b00001111000000001111000000001111) + (bits >> 6 & 0b00000000000000001111000000001111); 5 bits = (bits & 0b00011111000000000000000000011111) + (bits >> 12 & 0b00000000000000000000000000011111); 6 return (bits & 0b00000000000000000000000000111111) + (bits >> 24 & 0b00000000000000000000000000111111); 7} 8int countGreaterThanOrEqualTo1(unsigned int bits) 9{ 10 bits |= (bits & 0b00000010010010010010010010010010) >> 1; 11 bits |= (bits & 0b00000100100100100100100100100100) >> 2; 12 bits = (bits & 0b00000001000001000001000001000001) + (bits >> 3 & 0b00000000000001000001000001000001); 13 bits = (bits & 0b00000011000000000011000000000011) + (bits >> 6 & 0b00000000000000000011000000000011); 14 bits = (bits & 0b00000111000000000000000000000111) + (bits >> 12 & 0b00000000000000000000000000000111); 15 return (bits & 0b00000000000000000000000000001111) + (bits >> 24 & 0b00000000000000000000000000001111); 16} 17int countGreaterThanOrEqualTo2(unsigned int bits) 18{ 19 bits |= bits >> 1; 20 bits >>= 1; 21 bits = (bits & 0b00000001000001000001000001000001) + (bits >> 3 & 0b00000000000001000001000001000001); 22 bits = (bits & 0b00000011000000000011000000000011) + (bits >> 6 & 0b00000000000000000011000000000011); 23 bits = (bits & 0b00000111000000000000000000000111) + (bits >> 12 & 0b00000000000000000000000000000111); 24 return (bits & 0b00000000000000000000000000001111) + (bits >> 24 & 0b00000000000000000000000000001111); 25} 26int count2(unsigned int bits) 27{ 28 bits ^= 0b00000010010010010010010010010010; 29 bits |= (bits & 0b00000010010010010010010010010010) >> 1; 30 bits |= (bits & 0b00000100100100100100100100100100) >> 2; 31 bits = (bits & 0b00000001000001000001000001000001) + (bits >> 3 & 0b00000000000001000001000001000001); 32 bits = (bits & 0b00000011000000000011000000000011) + (bits >> 6 & 0b00000000000000000011000000000011); 33 bits = (bits & 0b00000111000000000000000000000111) + (bits >> 12 & 0b00000000000000000000000000000111); 34 return 9 - ((bits & 0b00000000000000000000000000001111) + (bits >> 24 & 0b00000000000000000000000000001111)); 35}

投稿2020/09/15 16:17

編集2020/09/16 16:04
SHOMI

総合スコア4079

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 16:30

ご回答ありがとうございます。 コメントいただいた内容について理解、及びベンチマークをとったりするので、すみませんが、しばらく質問はオープンのままにさせてください。
SHOMI

2020/09/15 16:59 編集

不要なビット演算を除去しました
SHOMI

2020/09/15 17:31

ビット演算が間違っていたので修正しました…
SHOMI

2020/09/15 18:38 編集

bits |= (bits & 0b00000010010010010010010010010010) >> 1; bits |= (bits & 0b00000100100100100100100100100100) >> 2; と bits |= (bits >> 1) & 0b00000001001001001001001001001001; bits |= (bits >> 2) & 0b00000001001001001001001001001001; どちらが速いかは確認していません。
tiitoi

2020/09/15 18:42

> 上記コードは30bit(a[9])まで使用していました。 今はビット値を int (32bit) に入れていて 28bit~32bitは0で埋まっているので、上位ビットは気にしなくても動作は大丈夫です。
tiitoi

2020/09/16 15:44

全テストケースで正解で、速度も自分が書いたものよりだいぶ早くなりました。アルゴリズムについてはこれから勉強していこうと思います。 回答していただきありがとうございました。
guest

0

以下のように、データ内の'1'のビット数を数えるビットカウント問題に変換してしまう案はどうでしょうか。

  1. 元の配列の各値の合計

ビットマスクして9個のデータのMSBのみを残す。ビットカウントして4倍する。
ビットマスクして9個のデータの真ん中のみを残す。ビットカウントして2倍する。
ビットマスクして9個のデータのLSBのみを残す。ビットカウントして1倍する。
これらを合計すると求める答えになる。

  1. 元の配列の各値が1以上の要素数
  2. 元の配列の各値が2以上の要素数

元データを左シフトしてビットORをとる。その後に9個のデータのMSBのみを残してビットカウントする。

  1. 元の配列の各値が2の要素数

3ビットのデータのうち、「真ん中のみ1でMSBとLSBは0」のようなマスクを用意する。
元データとビットANDをとってからビットカウントする。

これなら、いかにビットカウント部分を高速化するかに注力できます。
ビットカウントはCPU内蔵のpopcnt命令を使えば十分高速とは思いますが「faster population count」とかで検索すると
AVX命令を使うとより速くなるみたいな記事がいろいろと出てきます。

c++

1#include <iostream> 2#include <x86intrin.h> // gcc 3using namespace std; 4 5int calc_a1(unsigned int x); 6int calc_a2(unsigned int x); 7int calc_a3(unsigned int x); 8int calc_a4(unsigned int x); 9 10int main() { 11 unsigned int x = 69510160; 12 cout << calc_a1(x) << endl; 13 cout << calc_a2(x) << endl; 14 cout << calc_a3(x) << endl; 15 cout << calc_a4(x) << endl; 16} 17 18int calc_a1(unsigned int x) 19{ 20 return _popcnt32(x & 0x4924924) * 4 + _popcnt32(x & 0x2492492) * 2 + _popcnt32(x & 0x1249249); 21} 22 23int calc_a2(unsigned int x) 24{ 25 x |= (x << 1); 26 x |= (x << 1); 27 return _popcnt32(x & 0x4924924); 28} 29 30int calc_a3(unsigned int x) 31{ 32 x |= (x << 1); 33 return _popcnt32(x & 0x4924924); 34} 35 36int calc_a4(unsigned int x) 37{ 38 unsigned bit = x & 0x5b6db6d; 39 bit |= (bit >> 1); 40 bit |= (bit << 1); 41 bit &= 0x2492492; 42 return _popcnt32((x & ~bit) & 0x2492492); 43} 44

投稿2020/09/16 09:33

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/16 09:59 編集

ご回答いただきありがとうございます。 #include <x86intrin.h> が GCC にしかないため、 64bit 開発環境での windows の場合、#include <intrin.h> を見ると、 __MACHINEX86_X64(unsigned int __popcnt(unsigned int)) __MACHINEX86_X64(unsigned short __popcnt16(unsigned short)) __MACHINEX64(unsigned __int64 __popcnt64(unsigned __int64)) __MACHINEX86_X64(int _mm_popcnt_u32(unsigned int)) __MACHINEX64(__int64 _mm_popcnt_u64(unsigned __int64)) があり、_popcnt32 っぽいものがなかったので __popcnt64() に回答いただいたコードを置き換えて、値は正しく計算されたのですが、質問のコードを動かす上での意図とあっていますでしょうか?
退会済みユーザー

退会済みユーザー

2020/09/16 11:12

すみません、Visual Studioは使ってないので分からないというのが正直な答えです。別の方の記事になってしまいますが、この方の場合、_mm_popcnt_u64を使っているようです。(https://qiita.com/Seizh/items/26eef63af739ba48e36b)
tiitoi

2020/09/16 15:40

_mm_popcnt_u64 で試してみました。計測した中では最速でした。 SIMD については名前しか聞いたことがないので、これから勉強していこうと思います。 ありがとうございました。
guest

0

合計だけですが、

C++

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 // (1)各値の合計 7 { 8 int cnt = 0; 9 for (int i = 0; i < 27; i += 3) 10 cnt += x >> i & 0b111; 11 12 std::cout << cnt << std::endl; 13 } 14 // (2)各値の合計 15 { 16 int cnt = (x >> 3 & 07070707) + (x & 07070707); 17 cnt = (cnt >> 6 & 0170017) + (cnt & 0170017); 18 cnt = (cnt >> 12 & 037) + (cnt & 037); 19 cnt += x >> 24; 20 std::cout << cnt << std::endl; 21 } 22}

演算回数
(1) = 2回、< 10回、+= 18回、>> 9回、& 9回
(2) = 3回、>> 4回、& 6回、+ 3回、+= 1回

追記
各値が2の要素数

C++

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 // (1)各値が2の要素数 7 { 8 int cnt = 0; 9 for (int i = 0; i < 27; i += 3) { 10 if ((x >> i & 0b111) == 2) 11 cnt++; 12 } 13 std::cout << cnt << std::endl; 14 } 15 // (2)各値が2の要素数 16 { 17 int cnt = x ^ 0555555555; 18 cnt &= cnt >> 2 & cnt >> 1; 19 cnt = (cnt >> 3 & 01010101) + (cnt & 01010101) + (cnt >> 24); 20 cnt = (cnt >> 6 & 030003) + (cnt & 030003); 21 cnt = (cnt >> 12 & 7) + (cnt & 7); 22 std::cout << cnt << std::endl; 23 } 24}

演算回数
(1) = 2回、< 10回、+= 9回、>> 9回、& 9回、== 9回、++ 0~9回
(2) = 4回、^ 1回、+= 1回、&= 1回、>> 6回、& 7回、+ 4回

追記2
各値が1以上の要素数

C++

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 // (1)各値が1以上の要素数 7 { 8 int cnt = 0; 9 for (int i = 0; i < 27; i += 3) { 10 if (x >> i & 0b111) 11 cnt++; 12 } 13 std::cout << cnt << std::endl; 14 } 15 // (2)各値が1以上の要素数 16 { 17 int cnt = x >> 2 | x >> 1 | x; 18 cnt = (cnt >> 3 & 01010101) + (cnt & 01010101) + (cnt >> 24 & 1); 19 cnt = (cnt >> 6 & 030003) + (cnt & 030003); 20 cnt = (cnt >> 12 & 7) + (cnt & 7); 21 std::cout << cnt << std::endl; 22 } 23}

演算回数
(1) = 2回、< 10回、+= 9回、>> 9回、& 9回、!=0 9回、++ 0~9回
(2) = 4回、| 2回、>> 6回、& 7回、+ 4回

各値が2以上の要素数

C++

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 // 各値が2以上の要素数 7 { 8 int cnt = 0; 9 for (int i = 0; i < 27; i += 3) { 10 if (x >> i & 0b110) 11 cnt++; 12 } 13 std::cout << cnt << std::endl; 14 } 15 // 各値が2以上の要素数 16 { 17 int cnt = x >> 2 | x >> 1; 18 cnt = (cnt >> 3 & 01010101) + (cnt & 01010101) + (cnt >> 24 & 1); 19 cnt = (cnt >> 6 & 030003) + (cnt & 030003); 20 cnt = (cnt >> 12 & 7) + (cnt & 7); 21 std::cout << cnt << std::endl; 22 } 23}

演算回数
(1) = 2回、< 10回、+= 9回、>> 9回、& 9回、!=0 9回、++ 0~9回
(2) = 4回、| 1回、>> 6回、& 7回、+ 4回

追記3
表検索のため演算回数が少ない高速版

C++

1#include <iostream> 2 3#define A3(x) x,x+1,x+2,x+3,x+4,x+5,x+6,x+7 4#define A6(x) A3(x),A3(x+1),A3(x+2),A3(x+3),A3(x+4),A3(x+5),A3(x+6),A3(x+7) 5#define A9(x) A6(x),A6(x+1),A6(x+2),A6(x+3),A6(x+4),A6(x+5),A6(x+6),A6(x+7) 6char a[512] = { A9(0) }; 7 8#define B3(x) x,x+1,x+1,x+1,x+1,x+1,x+1,x+1 9#define B6(x) B3(x),B3(x+1),B3(x+1),B3(x+1),B3(x+1),B3(x+1),B3(x+1),B3(x+1) 10#define B9(x) B6(x),B6(x+1),B6(x+1),B6(x+1),B6(x+1),B6(x+1),B6(x+1),B6(x+1) 11char b[512] = { B9(0) }; 12 13#define C3(x) x,x,x+1,x+1,x+1,x+1,x+1,x+1 14#define C6(x) C3(x),C3(x),C3(x+1),C3(x+1),C3(x+1),C3(x+1),C3(x+1),C3(x+1) 15#define C9(x) C6(x),C6(x),C6(x+1),C6(x+1),C6(x+1),C6(x+1),C6(x+1),C6(x+1) 16char c[512] = { C9(0) }; 17 18#define D3(x) x,x,x+1,x,x,x,x,x 19#define D6(x) D3(x),D3(x),D3(x+1),D3(x),D3(x),D3(x),D3(x),D3(x) 20#define D9(x) D6(x),D6(x),D6(x+1),D6(x),D6(x),D6(x),D6(x),D6(x) 21char d[512] = { D9(0) }; 22 23int main() 24{ 25 int x = 69510160; 26 int cnt; 27 28 cnt = a[x>>18 & 0777] + a[x>>9 & 0777] + a[x & 0777]; 29 std::cout << cnt << std::endl; // 各値の合計 30 31 cnt = b[x>>18 & 0777] + b[x>>9 & 0777] + b[x & 0777]; 32 std::cout << cnt << std::endl; // 各値が1以上の要素数 33 34 cnt = c[x>>18 & 0777] + c[x>>9 & 0777] + c[x & 0777]; 35 std::cout << cnt << std::endl; // 各値が2以上の要素数 36 37 cnt = d[x>>18 & 0777] + d[x>>9 & 0777] + d[x & 0777]; 38 std::cout << cnt << std::endl; // 各値が2の要素数 39}

投稿2020/09/15 16:54

編集2020/09/16 01:52
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 18:32

ご回答ありがとうございます。 検証や理解に時間がかかりそうなので、取り急ぎお礼まで。
tiitoi

2020/09/16 15:43

高速版はビット演算のみのコードでは最速でした。 回答していただきありがとうございました。
guest

0

ちょうど別言語でテストしていましたが
一番早かったのはデータ型を変換した配列に一度移し替えて計算する方式でした。
もし値自体で計算を行うのでしたら
最も速い数値型
に変換されるのが良いでしょう
ビットシフト演算も早いかもしれません

ビットシフト演算が遅い言語の場合の手段です

  1. 各バイト用の辞書配列を先に用意(private変数)

例 byte Dec[4][256][4];
// 00010000 → 16 
Dec[0][16][0] = 0; // 000
Dec[0][16][1] = 2; // 010
Dec[0][16][2] = 0; // 000
// 10100100 → 164 
Dec[1][164][0] = 0; // 0
Dec[1][164][1] = 2; // 010
Dec[1][164][2] = 2; // 010
Dec[1][164][3] = 1; // 1
// 10100101 → 165 
Dec[1][165][0] = 4; // 1
Dec[1][165][1] = 2; // 010
Dec[1][165][2] = 2; // 010
Dec[1][165][3] = 1; // 1

  1. 元の値がintとしてbyte[4]に分割(memset使用)

  2. 辞書配列から各値を取得し別の配列に再設定

以降2.~3.の繰り返し

処理回数が少ないと辞書を作成する時間のほうがかかりますが
意外と使えます。

あと確認できていないのですが
ビットフィールド
指定した構造体にmemsetでコピーがうまくいけば早いかもしれません。

投稿2020/09/15 18:05

編集2020/09/15 18:28
kuma_kuma_

総合スコア2506

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 18:33

ご回答ありがとうございます。 検証や理解に時間がかかりそうなので、取り急ぎお礼まで。
guest

0

本題の前に、提示のC++コードには演算子の優先順位が意図通りになっていないバグがあります。

diff

1--- hoge.cpp 2020-09-16 02:09:33.346257663 +0900 2+++ hoge1.cpp 2020-09-16 01:57:43.685888192 +0900 3@@ -27,7 +27,7 @@ 4 { 5 int cnt = 0; 6 for (int i = 0; i < 9; ++i) { 7- if ((x >> i * 3) & 0b111 >= 2) 8+ if (((x >> i * 3) & 0b111 )>= 2) 9 cnt++; 10 } 11 std::cout << cnt << std::endl; 12@@ -37,7 +37,7 @@ 13 { 14 int cnt = 0; 15 for (int i = 0; i < 9; ++i) { 16- if ((x >> i * 3) & 0b111 == 2) 17+ if (((x >> i * 3) & 0b111 )== 2) 18 cnt++; 19 } 20 std::cout << cnt << std::endl;

提示コードが、どの程度実際のコードを反映しているのかわかりませんが、
3ビット値を何回も取り出すよりも
1度だけ3ビット値を取り出してからその値に対して集計処理を行ったほうが良さそうな気がします。
(ベンチマークとかアセンブラコード出力して検証をしたりはしていません。)

cpp

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 int sum = 0; 7 int over1 = 0; 8 int over2 = 0; 9 int equal2 = 0; 10 11 for ( int i = 0 ; i < 9 ; ++i ) { 12 int v; 13 14 v = (x >> i * 3) & 0b111; 15 16 // 各値の合計 17 sum += v; 18 19 // 各値が1以上の要素数 20 if ( v ) { 21 ++over1; 22 } 23 24 // 各値が2以上の要素数 25 if ( v >= 2 ) { 26 ++over2; 27 } 28 29 // 各値が2の要素数 30 if ( v == 2 ) { 31 ++equal2; 32 } 33 } 34 35 std::cout << sum << std::endl; 36 std::cout << over1 << std::endl; 37 std::cout << over2 << std::endl; 38 std::cout << equal2 << std::endl; 39 40 return 0; 41}

あと、可読性は落ちますが、ifを通る回数を減らしたり、xをxworkにコピーして破壊しながら3ビットずつ取り出すとかすればちょっと速くなるかもしれません。

cpp

1#include <iostream> 2 3int main() 4{ 5 int x = 69510160; 6 int sum = 0; 7 int over1 = 0; 8 int over2 = 0; 9 int equal2 = 0; 10 int xwork; 11 12 xwork = x; 13#if 0 /* 0になった時点で(xから取り出すものがなくなった時点で)終了できるがなんか弊害があるかも */ 14 while ( xwork != 0 ) 15#else 16 for ( int i = 0 ; i < 9 ; ++i ) 17#endif 18 { 19 int v; 20 21 v = xwork & 0b111; 22 xwork >>= 3; 23 24 // 各値の合計 25 sum += v; 26 27 // 各値が1以上の要素数 28 if ( v ) { 29 ++over1; 30 31 // 各値が2以上の要素数 32 if ( v >= 2 ) { 33 ++over2; 34 35 // 各値が2の要素数 36 if ( v == 2 ) { 37 ++equal2; 38 } 39 } 40 } 41 } 42 43 std::cout << sum << std::endl; 44 std::cout << over1 << std::endl; 45 std::cout << over2 << std::endl; 46 std::cout << equal2 << std::endl; 47 48 return 0; 49} 50

投稿2020/09/15 17:25

編集2020/09/15 18:22
hidezzz

総合スコア1248

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 18:32

ご回答ありがとうございます。 検証や理解に時間がかかりそうなので、取り急ぎお礼まで。
tiitoi

2020/09/15 18:36

演算子の優先順位については修正しました。ビット演算は普段あまり使わないのであやふやになっていました。 ご指摘ありがとうございます。
tiitoi

2020/09/16 15:55

> ifを通る回数を減らしたり 実際のコードの中でループの中で1以上と2以上は両方チェックする部分はあるので、そこは入れ子にして削減できそうです。アイデアをくださりありがとうございます。
guest

0

#※間違いがあるので参考リンク以外を非表示にしておきます。


参考:ビット演算

参考:ビット演算 (bit 演算) の使い方を総特集! 〜 マスクビットから bit DP まで 〜

投稿2020/09/15 16:01

編集2020/09/15 21:02
mjk

総合スコア303

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 16:23 編集

ご回答ありがとうございます。 コメントいただいた内容について理解、検証してみます。
mjk

2020/09/15 21:24 編集

010 100 011 010 010 010 例えば==2を判定する場合、ANDを取っても2なのか3なのかはっきりしないので他条件でも同様にこの方法では判定出来ないと気づきました。 テストケースが小さくて見落としていました。このままだと間違いなので一旦取り下げておきます。 失礼しました。
mjk

2020/09/15 22:01

蛇足だとは思いますがシステム的に許されるなら(0-4)の全5^9パターンの答えを作っておいて参照させるのはダメですよね?膨大なメモリ消費すると思いますが速度優先とのことなのでもしかしたらと思いました。
tiitoi

2020/09/16 15:50

> システム的に許されるなら(0-4)の全5^9パターンの答えを作っておいて参照させるのはダメですよね 全然ありです。 質問の事がやりたかった背景として、以下の麻雀の和了り判定のプログラムを作っていて、その中ですでに5^9のうち、200MBぐらい使って必要な全パターンをテーブル化しています。 https://github.com/nekobean/mahjong-cpp 「a[0]とa[9]が2個ずつあるか」のようにチェックしたい項目が多数あるため全部を記録しておくことは難しいですが、mjk さんがおっしゃっていただいたように合計数とか計算頻度の高いものは記録しておくこともありだと思いました。 貴重なアイデアをいただきありがとうございます。
guest

0

言語を限定した質問ではない

という事なので・・・cとc++限定ですが・・・題意に沿わないようなら無視して下さい。

c

1#include <stdio.h> 2 3typedef struct { 4 unsigned val1 : 3; 5 unsigned val2 : 3; 6 unsigned val3 : 3; 7 unsigned val4 : 3; 8 unsigned val5 : 3; 9 unsigned val6 : 3; 10 unsigned val7 : 3; 11 unsigned val8 : 3; 12 unsigned val9 : 3; 13} bitField; 14 15int main(void) 16{ 17 bitField bf = {0, 2, 0, 2, 2, 1, 1, 1, 4}; 18 19 printf("%d\n", bf.val1); 20 printf("%d\n", bf.val2); 21 printf("%d\n", bf.val3); 22 printf("%d\n", bf.val4); 23 printf("%d\n", bf.val5); 24 printf("%d\n", bf.val6); 25 printf("%d\n", bf.val7); 26 printf("%d\n", bf.val8); 27 printf("%d\n", bf.val9); 28 29 return 0; 30}

結果
usr ~/Project/c % ./a.out
0
2
0
2
2
1
1
1
4
・・・ビットフィールドに配列は使えない^^;
[追記]・・・初期化にしかビットフィールド使ってない^^;力任せw

c++

1#include <iostream> 2 3typedef struct { 4 unsigned val1 : 3; 5 unsigned val2 : 3; 6 unsigned val3 : 3; 7 unsigned val4 : 3; 8 unsigned val5 : 3; 9 unsigned val6 : 3; 10 unsigned val7 : 3; 11 unsigned val8 : 3; 12 unsigned val9 : 3; 13} bitField; 14 15int main(void) 16{ 17 bitField bf = {0, 2, 0, 2, 2, 1, 1, 1, 4}; 18 19 unsigned int cnt[8] = {0}; 20 cnt[bf.val1]++; 21 cnt[bf.val2]++; 22 cnt[bf.val3]++; 23 cnt[bf.val4]++; 24 cnt[bf.val5]++; 25 cnt[bf.val6]++; 26 cnt[bf.val7]++; 27 cnt[bf.val8]++; 28 cnt[bf.val9]++; 29 // 30 int sum = bf.val1 + bf.val2 + bf.val3 + bf.val4 + bf.val5 + bf.val6 + bf.val7 + bf.val8 + bf.val9; 31 int nz = 0; 32 int u2 = 0; 33 // 34 for(size_t i = 1; i < 8; i++) { 35 // std::cout << cnt[i] << std::endl; 36 nz += cnt[i]; 37 if(i > 1) { 38 u2 += cnt[i]; 39 } 40 } 41 std::cout << "元の配列の各値の合計:" << sum << std::endl; 42 std::cout << "元の配列の各値が1以上の要素数:" << nz << std::endl; 43 std::cout << "元の配列の各値が2以上の要素数:" << u2 << std::endl; 44 std::cout << "元の配列の各値が2の要素数:" << cnt[2] << std::endl; 45 // 46 return 0; 47}

結果
元の配列の各値の合計:13
元の配列の各値が1以上の要素数:7
元の配列の各値が2以上の要素数:4
元の配列の各値が2の要素数:3

投稿2020/09/15 15:17

編集2020/09/16 01:05
cateye

総合スコア6851

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 15:29

回答してくださりありがとうございます。 C++ なので、言語は大丈夫です。 ビットフィールドの機能は使ったことがなかったので、少し調べてみます。
cateye

2020/09/16 01:14 編集

効率&メモリ効率考えていません^^;・・・for文の中のif判定がどれほどのものか? ビットフィールドはsizeofが使えないので、 即値(8)になっています。
tiitoi

2020/09/16 15:52

ビットフィールドと union 使えば、個々に要素を取り出したり、1つのビット値として取り出しすこともできそうなので、いろいろ使えそうな気がしてきました。 アイデアをくださりありがとうございます。
cateye

2020/09/16 16:07 編集

ビットフィールドと(例えば)intで取り出した時の、ビット毎の配置が同一という保証はなかったと思います。(未確認)・・・処理系依存?・・・だと思うのでご注意下さい。
tiitoi

2020/09/16 16:14 編集

> 処理系依存?・・・だと思うのでご注意下さい。 なるほど。ビットフィールドのレイアウトが特に仕様で規程されていないようですね。 以下の SO みていけるのかなと思ってたのですが、よく見たらコメントでツッコミが入っていました。 https://stackoverflow.com/questions/2468708/converting-bit-field-to-int
guest

0

ビット列を8進数に変換すると
該当の配列になることを利用することが何等かの突破口かなとも思いましたが、
pythonの組み込み関数ですし、そもそもこういう回答を求められているのか自信ありませんが・・・

python

1a = 0b100001001001010010000010000 2 3b = str(oct(a)) 4 5>> 0o411122020 6 7sum(int(i) for i in b[2:]) 8 9>> 13

投稿2020/09/15 15:05

編集2020/09/15 15:10
sfdust

総合スコア1135

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2020/09/15 15:22

回答してくださりありがとうございます。 質問の説明が不足しており申し訳ないです。実際は C++ で最適化を目指しており、演算回数を1回でも減らしたいと考えています。 oct() 内部で8進数に変換する処理が走っているのではないかと思うので、計算量はループして取り出す方法と変わらないのではないでしょうか。
sfdust

2020/09/16 02:36 編集

すみません。勘違いしていました。 後から振り返ると、基準とすべき「演算回数」の定義が不明確な気がしました。 ここでの「回数」とは何でしょうか。 たとえば元のpythonコードに於いて、与えられたビット列(を整数値に直した「key」)をループで3分割する処理を9回行っていますが、 この「9回」を下回れ、ということでしょうか。 では、その中の、ビットシフト(>>)と乗算*と、ビット演算&についてはまとめてループ中の1回としてよいのでしょうか。 それとも例示コードのpythonの中間言語や、cをコンパイルした後のアセンブラレベルでの、「計算量」が少なくなるように、という意図でしょうか。 たしかに私の示したコードでは、generatorがサブプロシージャとして分離される分、tiitoiさんが示したシンプルなコードよりも、実際の「計算量」は若干多くなっています。(dis.disで確認)
tiitoi

2020/09/15 15:59 編集

> ここでの「回数」とは何でしょうか。 質問内容に曖昧な部分があり、申し訳ないです。後ほど訂正します。 「効率的」の意味は、コードの簡潔さや行数ではなく、四則演算、ビット演算などの演算回数のことを意図しておりました。 C++ で高速化を目指しており、このビット演算が絡む部分がボトルネックとなっているため、アセンブラレベルの比較までは考えてなかったですが、 コード上で見たとき、この計算の演算回数が少しでも減らす方法がないかという意図での質問でした。 最適化なので、コードが汚くなり、難読になるとは思いますがそれは犠牲になっても大丈夫です。
sfdust

2020/09/15 16:16

和についてこれ以上効率化となると、3桁ごとに4桁にパッキングしてintrinsic使ってSIMDとかですかね。3桁⇒4桁のところはどうしても演算が発生しますが。(具体的なコードは知らない)
tiitoi

2020/09/15 16:19

コメントありがとうございます。教えていただいたキーワードについて調べてみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問