У меня есть 9-битные поля, каждое битовое поле имеет 9 девяти бит, 9 младших битов целого числа. Я хочу найти позиции битов, которые точно установлены дважды для всех битовых полей.
например:
0.1111.1111
0.0000.1101
0.0001.1101
0.0001.1101
0.0010.1101
0.0010.1101
0.0100.1101
0.0100.1101
0.0000.0010
В этом примере это битовая позиция 1, потому что в этой позиции 1 устанавливается ровно два раза.
Первый раз в первом битовом поле, второй раз в последнем битовом поле.
Сейчас я делаю "positional population count"
на ints.
для каждого возможного битового поля их 512, у меня есть соответствующая длинная битовая маска,
который используется для подсчета частоты установленных битовых позиций.
0.0100.1101 -> 0000.0000.0001.0000.0000.0001.0001.0000.0001
Я суммирую 9 длинных битовых масок 9-битных полей. Таким образом, первые 4 младших бита суммы представляют собой установку бита частоты 0. Вторые 4 бита представляют частоту, в которой установлен бит 1. И так далее, чтобы я мог сканировать частоту 2.
Это работает, но не так быстро, как я надеялся.
Есть ли более быстрый алгоритм, который я могу реализовать на Java?
Мне не нужно знать все битовые позиции с частотой 2, достаточно одной битовой позиции.
По сравнению с полным pos-popcount, здесь есть ярлык.
Вы можете узнать две вещи:
Затем вы можете найти биты, которые установлены ровно дважды, как пересечение битов, которые установлены как минимум дважды, с битами, которые не установлены как минимум 3 раза.
int set_a1 = 0;
int set_a2 = 0;
int set_a3 = 0;
for (int i : values) {
set_a3 |= set_a2 & i;
set_a2 |= set_a1 & i;
set_a1 |= i;
}
int set_twice = set_a2 & ~set_a3;
Затем вы можете использовать Integer.numberOfTrailingZeros
, чтобы получить позицию первого (начиная с младшего) бита, который был установлен дважды.
Кстати, @coder для предварительно вычисленных масок вы также можете использовать Long.expand в достаточно новых версиях Java. Возможно, это не лучше, чем получение расширенной маски из массива, но шанс есть. Некоторые процессоры имеют встроенную инструкцию для этой операции расширения.
интересный метод, потребовалось время, чтобы понять, что он делает, и в конечном итоге найти правильную маску 0b0001_0001_..._0001L
, и действительно, кажется, что он немного быстрее, чем поиск.
Вероятно, вы могли бы использовать Map
для ведения счета. Большая часть изложенного ниже — это просто настройка и визуализация. Это именно то getExclusivePairs
, что вам нужно:
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class BitSets {
public static void main(String[] args) {
int[] bitSets = new int[9];
final int MASK = 0b1_1111_1111;
ThreadLocalRandom rand = ThreadLocalRandom.current();
IntStream.range(0, bitSets.length).forEach(ix -> bitSets[ix] = rand.nextInt() & MASK);
System.out.println(" 8 7654 3210");
System.out.println("-------------");
for (int j = 0; j < bitSets.length; j++) {
System.out.printf("%d %s%n", j, StringUtils.toBinaryStringGrouped(bitSets[j]).substring(28));
}
Map<Integer, List<Integer>> found = getExclusivePairs(bitSets);
found.entrySet().
stream().forEach(e -> System.out.printf("Bit %d is set exactly twice in the following bitsets: %s%n", e.getKey(), e.getValue()));
}
private static Map<Integer, List<Integer>> getExclusivePairs(int[] bitSets) {
Map<Integer, List<Integer>> counter = new HashMap<>();
for(int ixBitset = 0;ixBitset < bitSets.length;ixBitset++) {
for(int ixBit = 0;ixBit < bitSets.length;ixBit++) {
boolean isSet = (bitSets[ixBitset] & (1 << ixBit)) > 0;
if (isSet) {
counter.computeIfAbsent(ixBit, k -> new ArrayList<Integer>()).add(ixBitset);
}
}
}
return counter.entrySet().stream().filter(e -> e.getValue().size() == 2).collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
}
private static class StringUtils {
public static String toBinaryStringGrouped(int n) {
return StringUtils.grouped(StringUtils.toBinaryString(n));
}
private static String grouped(String s) {
StringBuilder sb = new StringBuilder(s);
for (int i = sb.length() - 4; i >= 4; i -= 4) {
sb.insert(i, '_');
}
return sb.toString();
}
public static String toBinaryString(int n) {
StringBuilder sb = new StringBuilder("00000000000000000000000000000000");
for (int bit = 0; bit < 32; bit++) {
if (((n >> bit) & 1) > 0) {
sb.setCharAt(31 - bit, '1');
}
}
return sb.toString();
}
}
}
Это также возвращает индексы двухбитовых полей, что хорошо, но с точки зрения производительности это далеко от других решений.
Значения бит переменной b
суммируются в переменной sum
, но перенос после сложения не добавляется к старшему биту, а суммируется в переменной sumCarry
.
Таким же образом мы можем далее суммировать переносы в других переменных, но нам достаточно зафиксировать лишь факт наличия очередного переноса в переменной anyCarry
.
var sum = 0;
var sumCarry = 0;
var anyCarry = 0;
for (var b : values) {
var c = sum & b;
sum ^= b;
anyCarry |= sumCarry & c;
sumCarry ^= c;
}
var setTwice = ~sum & sumCarry & ~anyCarry;
Также хороший способ сделать это, он немного медленнее (~ 20% на моей машине) по сравнению с решением user555045.
Это элегантное решение, оно не требует предварительно вычисленных длинных масок и на моей машине оно почти на 10% быстрее. Кроме того, это та же самая идея, которую я использовал для поиска позиций, которые точно установлены один раз:
allBits - twiceOrMoreBits
, но мне не удалось распространить ее на случай дважды установленных битов. Так что это также очень хорошо подходит с этой точки зрения.