2012-01-25 11 views
17

Używam java.util.BitSet do przechowywania gęstego wektora bitów.Przesuwanie zestawu bitów Java

Chcę zaimplementować operację, która przesuwa bity o 1, analogicznie do >>> na ints.

Czy istnieje funkcja biblioteki, która przesuwa BitSet s?

Jeśli nie, czy jest lepszy sposób niż poniższy?

public static void logicalRightShift(BitSet bs) { 
    for (int i = 0; (i = bs.nextSetBit(i)) >= 0;) { 
    // i is the first bit in a run of set bits. 

    // Set any bit to the left of the run. 
    if (i != 0) { bs.set(i - 1); } 

    // Now i is the index of the bit after the end of the run. 
    i = bs.nextClearBit(i); // nextClearBit never returns -1. 
    // Clear the last bit of the run. 
    bs.clear(i - 1); 

    // 0000111100000... 
    //  a b 
    // i starts off the loop at a, and ends the loop at b. 
    // The mutations change the run to 
    // 0001111000000... 
    } 
} 
+0

Czekaj, to jest logiczne przesunięcie w lewo, a nie prawe przesunięcie logiczne. Dobrze? –

+0

Myślę, że bit na indeks zero z BitSet jako lewy najbardziej. Nie ma wyraźnej najbardziej lub najmniej znaczącej bitowej drogi z ciągiem bitów, który reprezentuje liczbę całkowitą, więc oznaczanie kierunków jest dowolne. –

Odpowiedz

16

To powinno załatwić sprawę:

BitSet shifted = bs.get(1, bs.length()); 

To da ci bitset równą orginial jeden, ale bez dolnej skrajnej bit.

EDIT:

Uogólniając to n bitów,

BitSet shifted = bs.get(n, Math.max(n, bs.length())); 
+0

The [dokumentacja] (http://docs.oracle.com/javase/7/docs/api/java/util/BitSet.html#get (int, int)) na 'get' myli mnie. Nic w "Zwraca nowy zestaw bitów złożony z bitów z tego zestawu bitów z fromIndex (włącznie) do toIndex (wyłączny)". wskazuje, że bit w 'fromIndex' w' this' zamienia na '0' na wyjściu. –

+1

@Mike. Wygląda na to, że działa podobnie do 'String.substring (begin, end)'. Zauważ, że 'begin' w tym przypadku to' 1', a nie '0'. –

+0

@AlexanderPogrebnyak, czy określiłeś to empirycznie, czy też istnieje faktyczna dokumentacja, która gwarantuje, że na wszystkich implementacjach? –

7

Alternatywą, która prawdopodobnie jest bardziej wydajna, byłoby pracować z długością [].

Użyj bitset.toLongArray(), aby uzyskać podstawowe dane. Zmodyfikuj odpowiednio te długie, a następnie utwórz nowy BitSet poprzez BitSet.valueOf(long[]) Musisz być bardzo ostrożny, przesuwając leżące poniżej długie, ponieważ będziesz musiał wziąć bit małego rzędu i przesunąć go do bitu wyższego rzędu na następny długi w tablicy .

Ten powinien pozwala użyć operacji przenoszenia bitów rodzimych na twoim procesorze, aby przesuwać 64 bity na raz, w przeciwieństwie do powtarzania każdego z nich osobno.

EDYCJA: Na podstawie komentarza Louisa Wassermana. Jest to dostępne tylko w języku Java 1.7 API. Nie zdawałem sobie z tego sprawy, kiedy to napisałem.

+1

O rany, nie miałem pojęcia, że ​​dodali to w Javie 7! –

+0

Czy to nie wymaga ręcznego przechwytywania niskiego bitu i propagowania go na koniec poprzedniego długiego? Czy wykonuje to dwie kopie tablicowe? –

+0

@MikeSamuel - Tak do obu tych. Uważam jednak, że nadal będzie to szybsze. Nie jestem pewien, czy to ma znaczenie dla twojego problemu. Patrząc na sugestię Philippa, myślę, że byłby to najprostszy i prawdopodobnie najszybszy. – rfeak

1

Można spojrzeć na BitSet toLongArray i valueOf(long[]).
Zasadniczo uzyskaj tablicę long, przesuń long s i skonstruuj nowy BitSet z przesuniętej tablicy.

7

proszę znaleźć ten blok kodu gdzie BitSet jest „przesunięty w lewo”

/** 
* Shift the BitSet to left.<br> 
* For example : 0b10010 (=18) => 0b100100 (=36) (equivalent to multiplicate by 2) 
* @param bitSet 
* @return shifted bitSet 
*/ 
public static BitSet leftShiftBitSet(BitSet bitSet) { 
    final long maskOfCarry = 0x8000000000000000L; 
    long[] aLong = bitSet.toLongArray(); 

    boolean carry = false; 
    for (int i = 0; i < aLong.length; ++i) { 
     if (carry) { 
      carry = ((aLong[i] & maskOfCarry) != 0); 
      aLong[i] <<= 1; 
      ++aLong[i]; 
     } else { 
      carry = ((aLong[i] & maskOfCarry) != 0); 
      aLong[i] <<= 1; 
     } 
    } 

    if (carry) { 
     long[] tmp = new long[aLong.length + 1]; 
     System.arraycopy(aLong, 0, tmp, 0, aLong.length); 
     ++tmp[aLong.length]; 
     aLong = tmp; 
    } 

    return BitSet.valueOf(aLong); 
} 
3

Funkcje te naśladować < < i >>> operatorów, odpowiednio.

/** 
* Shifts a BitSet n digits to the left. For example, 0b0110101 with n=2 becomes 0b10101. 
* 
* @param bits 
* @param n the shift distance. 
* @return 
*/ 
public static BitSet shiftLeft(BitSet bits, int n) { 
    if (n < 0) 
     throw new IllegalArgumentException("'n' must be >= 0"); 
    if (n >= 64) 
     throw new IllegalArgumentException("'n' must be < 64"); 

    long[] words = bits.toLongArray(); 

    // Do the shift 
    for (int i = 0; i < words.length - 1; i++) { 
     words[i] >>>= n; // Shift current word 
     words[i] |= words[i + 1] << (64 - n); // Do the carry 
    } 
    words[words.length - 1] >>>= n; // shift [words.length-1] separately, since no carry 

    return BitSet.valueOf(words); 
} 

/** 
* Shifts a BitSet n digits to the right. For example, 0b0110101 with n=2 becomes 0b000110101. 
* 
* @param bits 
* @param n the shift distance. 
* @return 
*/ 
public static BitSet shiftRight(BitSet bits, int n) { 
    if (n < 0) 
     throw new IllegalArgumentException("'n' must be >= 0"); 
    if (n >= 64) 
     throw new IllegalArgumentException("'n' must be < 64"); 

    long[] words = bits.toLongArray(); 

    // Expand array if there will be carry bits 
    if (words[words.length - 1] >>> n > 0) { 
     long[] tmp = new long[words.length + 1]; 
     System.arraycopy(words, 0, tmp, 0, words.length); 
     words = tmp; 
    } 

    // Do the shift 
    for (int i = words.length - 1; i > 0; i--) { 
     words[i] <<= n; // Shift current word 
     words[i] |= words[i - 1] >>> (64 - n); // Do the carry 
    } 
    words[0] <<= n; // shift [0] separately, since no carry 

    return BitSet.valueOf(words); 
} 
+0

Dzięki.64 związane na n wydaje się arbitralne, ale ograniczenie to można złagodzić przez pierwsze skopiowanie słów do nowej tablicy przesunięcia o (n/64) we właściwym kierunku. –

+0

Pytanie jest stare, ale nadal chciałbym to skomentować. Wszelkie metody ograniczające przesuwanie n <= 64 są bezużyteczne, powyższy kod jest powolny. Lepiej używać prymitywu 'long', jeśli liczba bitów jest mniejsza niż 64, lub użyj' BigInteger', który ma wbudowane funkcje do przesuwania bitów w lewo i prawo. Jeśli przyklei się do 'BitSet', należy rozważyć przesuwanie' bitIndex' przed umieszczeniem wartości bitów w 'BitSet'. – RaymondM

0

W celu osiągnięcia lepszej wydajności można rozszerzyć java.util.BitSet wdrażania i uniknąć niepotrzebnego kopiowanie tablicy. Oto realizacja (ja w zasadzie ponownego wykorzystania Jeff realizację Piersol):

package first.specific.structure; 

import java.lang.reflect.Field; 
import java.util.BitSet; 

public class BitSetMut extends BitSet { 

    private long[] words; 
    private static Field wordsField; 

    static { 
     try { 
      wordsField = BitSet.class.getDeclaredField("words"); 
      wordsField.setAccessible(true); 
     } catch (NoSuchFieldException e) { 
      throw new IllegalStateException(e); 
     } 
    } 

    public BitSetMut(final int regLength) { 
     super(regLength); 
     try { 
      words = (long[]) wordsField.get(this); 
     } catch (IllegalAccessException e) { 
      throw new IllegalStateException(e); 
     } 
    } 

    public void shiftRight(int n) { 
     if (n < 0) 
      throw new IllegalArgumentException("'n' must be >= 0"); 
     if (n >= 64) 
      throw new IllegalArgumentException("'n' must be < 64"); 

     if (words.length > 0) { 
      ensureCapacity(n); 

      // Do the shift 
      for (int i = words.length - 1; i > 0; i--) { 
       words[i] <<= n; // Shift current word 
       words[i] |= words[i - 1] >>> (64 - n); // Do the carry 
      } 
      words[0] <<= n; // shift [0] separately, since no carry 
      // recalculateWordInUse() is unnecessary 
     } 
    } 

    private void ensureCapacity(final int n) { 
     if (words[words.length - 1] >>> n > 0) { 
      long[] tmp = new long[words.length + 3]; 
      System.arraycopy(words, 0, tmp, 0, words.length); 
      words = tmp; 
      try { 
       wordsField.set(this, tmp); 
      } catch (IllegalAccessException e) { 
       throw new IllegalStateException(e); 
      } 
     } 
    } 
} 
+1

Wydaje się to kruche. Zależy to od pola prywatnego, mającego nie tylko określony typ i semantykę, ale także konkretną nazwę. Ponadto, 'doesCapacity' nie traci relacji aliasów między słowami a super-klasowym polem prywatnym. Nie działa szybko, więc kruchość może być łatwa do opanowania. Jakiego przyspieszenia wydajności dostajesz w zamian za kruchość? –

+0

@Mike masz absolutną rację, jeśli chodzi o metodę surecapacity (n), to mój błąd, więc naprawiłem go. Użyłem tej implementacji BitSetMut jako rejestru przesuwnego z liniowym sprzężeniem zwrotnym w niektórych algorytmach obliczeniowych, takich jak [szyfrowanie] (https://en.wikipedia.org/wiki/Scrambler). BitSetMut daje możliwość uniknięcia niepotrzebnego kopiowania tablic i generowania śmieci, więc ogólne opóźnienia są znacznie niższe. Implementacja Scrambler z BitSetMut jest 2 razy szybsza w porównaniu do Scrambler z BitSet i statyczną metodą shiftRight. – bstorozhuk

2

Można użyć BigInteger zamiast BitSet. BigInteger ma już ShiftRight i ShiftLeft.

+2

Twoja odpowiedź została oznaczona jako brak odpowiedzi, "nie jest najbardziej efektywnym podejściem" jest interesująca, ale powinieneś spróbować pokazać jakiś przykładowy kod przy użyciu klasy BigInteger, w jaki sposób można to osiągnąć ... Z recenzji –

+1

Autor poprawnie wskazuje obecnie wbudowani operatorzy zmian zapewniają dobry powód do korzystania z BI zamiast BS. Oczywiście zawsze można to zrobić, 'BigInteger bi = new BigInteger (bs.toByteArray()); bi.shiftLeft (12); bs = BitSet.valueOf (bi.toByteArray()); 'jeśli jest absolutnie potrzebny. –

0

Java SE8, może być osiągnięte bardziej zwięzły sposób:

BitSet b = new BitSet(); 
b.set(1, 3); 
BitSet shifted = BitSet.valueOf(Arrays.stream(
     b.toLongArray()).map(v -> v << 1).toArray()); 

starałem się dowiedzieć, jak używać LongBuffer to zrobić, ale nie bardzo mam go do pracy. Miejmy nadzieję, że ktoś, kto zna programowanie na niskim poziomie, może wskazać rozwiązanie.

Z góry dziękuję !!!