Найти позиции битов, которые точно установлены дважды в нескольких битовых полях

У меня есть 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, достаточно одной битовой позиции.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
152
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

По сравнению с полным pos-popcount, здесь есть ярлык.

Вы можете узнать две вещи:

  • какие биты установлены хотя бы дважды
  • какие биты установлены минимум 3 раза

Затем вы можете найти биты, которые установлены ровно дважды, как пересечение битов, которые установлены как минимум дважды, с битами, которые не установлены как минимум 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, чтобы получить позицию первого (начиная с младшего) бита, который был установлен дважды.

Это элегантное решение, оно не требует предварительно вычисленных длинных масок и на моей машине оно почти на 10% быстрее. Кроме того, это та же самая идея, которую я использовал для поиска позиций, которые точно установлены один раз: allBits - twiceOrMoreBits, но мне не удалось распространить ее на случай дважды установленных битов. Так что это также очень хорошо подходит с этой точки зрения.

coder 21.07.2024 09:30

Кстати, @coder для предварительно вычисленных масок вы также можете использовать Long.expand в достаточно новых версиях Java. Возможно, это не лучше, чем получение расширенной маски из массива, но шанс есть. Некоторые процессоры имеют встроенную инструкцию для этой операции расширения.

user555045 22.07.2024 00:02

интересный метод, потребовалось время, чтобы понять, что он делает, и в конечном итоге найти правильную маску 0b0001_0001_..._0001L, и действительно, кажется, что он немного быстрее, чем поиск.

coder 22.07.2024 19:29

Вероятно, вы могли бы использовать 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();
        }
    }
}

Это также возвращает индексы двухбитовых полей, что хорошо, но с точки зрения производительности это далеко от других решений.

coder 21.07.2024 09:53

Значения бит переменной b суммируются в переменной sum, но перенос после сложения не добавляется к старшему биту, а суммируется в переменной sumCarry.

Таким же образом мы можем далее суммировать переносы в других переменных, но нам достаточно зафиксировать лишь факт наличия очередного переноса в переменной anyCarry.

количество битов 0 1 2 3 4 или более сумма 0 1 0 1 - суммаCarry 0 0 1 1 - AnyCarry 0 0 0 0 1 setTwice 0 0 1 0 0
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.

coder 21.07.2024 09:31

Другие вопросы по теме