Nie wiem, jak działałby wektor bitowy, aby to zrobić (niezbyt zaznajomiony z wektorami bitowymi). Oto podany kod. Czy ktoś mógłby mnie przez to przeprowadzić?
public static boolean isUniqueChars(String str) {
int checker = 0;
for (int i = 0; i < str.length(); ++i) {
int val = str.charAt(i) - 'a';
if ((checker & (1 << val)) > 0) return false;
checker |= (1 << val);
}
return true;
}
W szczególności co to checker
robi?
java
string
bit-manipulation
bitvector
user1136342
źródło
źródło
Odpowiedzi:
int checker
jest tutaj używany jako magazyn dla bitów. Każdy bit w wartości całkowitej może być traktowany jako flaga, więc ostatecznieint
jest to tablica bitów (flaga). Każdy bit w twoim kodzie określa, czy znak z indeksem bitu został znaleziony w ciągu, czy nie. Możesz użyć wektora bitowego z tego samego powodu zamiastint
. Są między nimi dwie różnice:Rozmiar .
int
ma stały rozmiar, zwykle 4 bajty, co oznacza 8 * 4 = 32 bity (flagi). Wektor bitowy zwykle może mieć inny rozmiar lub należy określić rozmiar w konstruktorze.API . Dzięki wektorom bitowym będziesz miał łatwiejszy do odczytania kod, prawdopodobnie coś takiego:
vector.SetFlag(4, true); // set flag at index 4 as true
ponieważ
int
będziesz mieć kod logiki bitowej niższego poziomu:checker |= (1 << 5); // set flag at index 5 to true
Prawdopodobnie również
int
może być trochę szybszy, ponieważ operacje na bitach są na bardzo niskim poziomie i mogą być wykonywane przez procesor w takiej postaci, w jakiej są. BitVector pozwala zamiast tego napisać nieco mniej tajemniczy kod, a ponadto może przechowywać więcej flag.Na przyszłość: wektor bitowy jest również znany jako bitSet lub bitArray. Oto kilka linków do tej struktury danych dla różnych języków / platform:
źródło
Podejrzewam, że masz ten kod z tej samej książki, którą czytam ... Sam kod nie jest tak tajemniczy jak operatory- | =, &, i <<, których normalnie nie używa my laicy - autor nie zawracał sobie głowy poświęcaniem dodatkowego czasu na wyjaśnienie procesu ani na temat rzeczywistej mechaniki. Na początku byłem zadowolony z poprzedniej odpowiedzi w tym wątku, ale tylko na poziomie abstrakcyjnym. Wróciłem do tego, bo czułem, że potrzebne jest bardziej konkretne wyjaśnienie - brak jednego zawsze wywołuje we mnie niepokój.
Ten operator << jest lewostronnym przesuwnikiem bitowym, który przyjmuje binarną reprezentację tej liczby lub operandu i przesuwa ją o dowolną liczbę miejsc określonych przez operand lub liczbę po prawej stronie, tak jak w liczbach dziesiętnych tylko w binarnych. Mnożymy przez podstawę 2 - kiedy przesuwamy się w górę, jednak o wiele miejsc nie ma podstawy 10 - więc liczba po prawej stronie jest wykładnikiem, a liczba po lewej jest podstawą wielokrotności 2.
Ten operator | = bierze operand po lewej stronie i lub jest z operandem po prawej - a ten - „&” i jest bitami obu operandów po lewej i po prawej stronie.
Mamy tu więc tablicę skrótów, która jest przechowywana w 32-bitowej liczbie binarnej za każdym razem, gdy kontroler otrzyma or'd (
checker |= (1 << val)
) z wyznaczoną wartością binarną litery, której odpowiadający jej bit jest ustawiany na true. Wartość znaku jest równa i oznaczona za pomocą checker (checker & (1 << val)) > 0
) - jeśli jest większa niż 0, wiemy, że mamy podwójną wartość, ponieważ dwa identyczne bity ustawione na wartość true i razem zwrócą wartość true lub '1' '.Istnieje 26 miejsc binarnych, z których każde odpowiada małej literze - autor powiedział, że łańcuch zawiera tylko małe litery - a to dlatego, że pozostało nam tylko 6 (w 32-bitowej liczbie całkowitej) miejsc do wykorzystania - i niż my dostać kolizję
Tak więc, dla ciągu wejściowego „azya”, przechodząc krok po kroku
ciąg „a”
ciąg „az”
ciąg „azy”
ciąg „azya”
Teraz deklaruje duplikat
źródło
Myślę, że wszystkie te odpowiedzi wyjaśniają, jak to działa, jednak czułem, że mam ochotę dać swój wkład w to, jak widziałem to lepiej, zmieniając nazwy niektórych zmiennych, dodając inne i dodając do nich komentarze:
źródło
Zakładam również, że twój przykład pochodzi z książki Cracking The Code Interview i moja odpowiedź jest związana z tym kontekstem.
Aby użyć tego algorytmu do rozwiązania problemu, musimy przyznać, że będziemy przekazywać tylko znaki od a do z (małe litery).
Ponieważ jest tylko 26 liter i są one odpowiednio posortowane w używanej przez nas tablicy kodowania, gwarantuje nam to, że wszystkie potencjalne różnice
str.charAt(i) - 'a'
będą mniejsze niż 32 (wielkość zmiennej intchecker
).Jak wyjaśnił Snowbear, zamierzamy użyć
checker
zmiennej jako tablicy bitów. Spójrzmy na przykład:Powiedzmy
str equals "test"
i tak dalej ... aż znajdziemy już ustawiony bit w kontrolerze dla określonego znaku poprzez warunek
Mam nadzieję, że to pomoże
źródło
Powyżej podano kilka doskonałych odpowiedzi. Więc nie chcę powtarzać tego, co już zostało powiedziane. Ale chciałem dodać kilka rzeczy, które mogłyby pomóc w powyższym programie, ponieważ właśnie pracowałem nad tym samym programem i miałem kilka pytań, ale po spędzeniu trochę czasu mam więcej jasności co do tego programu.
Przede wszystkim „szachownica” służy do śledzenia znaku, który jest już przekroczony w ciągu znaków, aby sprawdzić, czy jakieś znaki są powtarzane.
Teraz „checker” jest typem danych typu int, więc może mieć tylko 32 bity lub 4 bajty (w zależności od platformy), więc ten program może działać poprawnie tylko dla zestawu znaków z zakresu 32 znaków. To jest powód, dla którego ten program odejmuje „a” od każdego znaku, aby program działał tylko dla małych liter. Jednak jeśli pomieszasz małe i duże litery, to nie zadziała.
Nawiasem mówiąc, jeśli nie odejmiesz „a” od każdego znaku (zobacz poniższą instrukcję), ten program będzie działał poprawnie tylko dla String z dużymi literami lub String z tylko małymi literami. Tak więc zakres powyższego programu zwiększa się od samych małych liter do wielkich liter, ale nie można ich mieszać razem.
Jednak chciałem napisać ogólny program wykorzystujący operację bitową, który powinien działać dla dowolnych znaków ASCII bez martwienia się o wielkie i małe litery, cyfry lub jakiekolwiek znaki specjalne. Aby to zrobić, nasz "checker" powinien być wystarczająco duży, aby pomieścić 256 znaków (rozmiar zestawu znaków ASCII). Ale int w Javie nie zadziała, ponieważ może przechowywać tylko 32 bity. Stąd w poniższym programie używam klasy BitSet dostępnej w JDK, która może mieć przekazany dowolny rozmiar zdefiniowany przez użytkownika podczas tworzenia instancji obiektu BitSet.
Oto program, który robi to samo, co powyższy program napisany przy użyciu operatora bitowego, ale ten program będzie działał dla łańcucha znaków z dowolnym znakiem z zestawu znaków ASCII.
źródło
for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
Przeczytanie odpowiedzi Ivana powyżej bardzo mi pomogło, chociaż ująłbym to nieco inaczej.
<<
W(1 << val)
to operator nieco biegów. Bierze1
(co w systemie binarnym jest reprezentowane jako000000001
, z dowolną liczbą poprzedzających zer / jest przydzielonych przez pamięć) i przesuwa w lewoval
spacjami. Ponieważ zakładamy tylko az i zaa
każdym razem odejmujemy , każda litera będzie miała wartość 0-25, która będzie indeksem tej litery od prawej stronychecker
w logicznej reprezentacji liczby całkowitej, ponieważ będziemy przesuwać1
w lewo wchecker
val
razy.Pod koniec każdego sprawdzenia widzimy
|=
operatora. To scala dwie liczby binarne, zastępując wszystkie0
's1
', jeśli1
istnieje w którymkolwiek operandzie w tym indeksie. Tutaj oznacza to, że wszędzie tam, gdzie1
istnieje(1 << val)
,1
zostanie skopiowane dochecker
, podczas gdy wszystkiechecker
istniejące 1 zostaną zachowane.Jak można się prawdopodobnie domyślić, a
1
funkcjonuje tutaj jako flaga logiczna dla wartości true. Kiedy sprawdzamy, czy znak jest już reprezentowany w ciągu, porównujemychecker
, który w tym momencie jest w zasadzie tablicą flag (1
wartości) logicznych w indeksach znaków, które zostały już reprezentowane, z tym, co jest zasadniczo tablicą wartości logiczne z1
flagą w indeksie bieżącego znaku.&
Operator realizuje ten czek. Podobny do|=
The&
operator skopiuje ponad1
tylko jeśli oba argumenty mają1
w tym indeksie. Zasadniczo więc tylko flagi już obecne w programie,checker
które są również reprezentowane w,(1 << val)
zostaną skopiowane. W tym przypadku oznacza to, że tylko wtedy, gdy aktualny znak był już reprezentowany, będzie1
obecny gdziekolwiek w wynikuchecker & (1 << val)
. A jeśli1
gdziekolwiek w wyniku tej operacji występuje a, to wartość zwróconej wartości logicznej wynosi> 0
, a metoda zwraca fałsz.To jest, jak domyślam się, dlaczego wektory bitowe są również nazywane tablicami bitów . Ponieważ nawet jeśli nie są typu danych tablicowych, mogą być używane podobnie do sposobu, w jaki tablice są używane do przechowywania flag logicznych.
źródło
Proste wyjaśnienie (z kodem JS poniżej)
32-bit
DEC64
Dla JS.0th
indeks, jeśli znajdziemya
w ciągu,1st
dlab
i tak dalej.Podsumowanie operacji:
checker
&index
znakuInt-32-Arrays
if
wynik operacji1
output == 1
checker
Zmienna ma tego konkretnego indeksu-ty bit zestaw w obu tablicachoutput == 0
checker
&index
znaku1
checker
Założenia:
a
is97
Poniżej podano kod źródłowy JavaScript .
Zauważ, że w JS, mimo że liczby całkowite mają 64 bity, nieco mądra operacja jest zawsze wykonywana na 32 bitach.
Przykład: Jeśli ciąg to
aa
:i = 0
i = 1
źródło
Pozwala rozbić kod wiersz po wierszu.
int checker = 0; Rozpoczynamy sprawdzanie, które pomoże nam znaleźć zduplikowane wartości.
int val = str.charAt (i) - 'a'; Pobieramy wartość ASCII znaku na „i” pozycji ciągu i odejmujemy ją od wartości ASCII „a”. Ponieważ założenie jest takie, że łańcuch składa się tylko z mniejszych znaków, liczba znaków jest ograniczona do 26. Hece, wartość „val” zawsze będzie> = 0.
if ((checker & (1 << val))> 0) return false;
checker | = (1 << val);
To jest trudna część. Rozważmy przykład ze stringiem „abcda”. Powinno to w idealnym przypadku zwrócić fałsz.
Dla iteracji pętli 1:
Checker: 00000000000000000000000000000000
val: 97-97 = 0
1 << 0: 00000000000000000000000000000001
checker & (1 << val): 00000000000000000000000000000000 nie jest> 0
Stąd kontroler: 00000000000000000000000000000001
Dla iteracji pętli 2:
Kontroler: 00000000000000000000000000000001
wartość: 98-97 = 1
1 << 0: 00000000000000000000000000000010
checker & (1 << val): 00000000000000000000000000000000 nie jest> 0
Stąd kontroler: 00000000000000000000000000000011
Dla iteracji pętli 3:
Kontroler: 00000000000000000000000000000011
val: 99-97 = 0
1 << 0: 00000000000000000000000000000100
checker & (1 << val): 00000000000000000000000000000000 nie jest> 0
Stąd kontroler: 00000000000000000000000000000111
Dla iteracji pętli 4:
Kontroler: 00000000000000000000000000000111
val: 100-97 = 0
1 << 0: 00000000000000000000000000001000
checker & (1 << val): 00000000000000000000000000000000 nie jest> 0
Stąd kontroler: 00000000000000000000000000001111
Dla iteracji pętli 5:
Kontroler: 00000000000000000000000000001111
val: 97-97 = 0
1 << 0: 00000000000000000000000000000001
checker & (1 << val): 00000000000000000000000000000001 jest> 0
Stąd zwrot fałsz.
źródło
źródło
Poprzednie posty wyjaśniają dobrze, co robi blok kodu i chcę dodać moje proste rozwiązanie za pomocą struktury danych BitSet java:
źródło
Sposób, w jaki rozumiałem używanie Javascript. Zakładając wejście
var inputChar = "abca"; //find if inputChar has all unique characters
Zaczynajmy
Line 4: int val = str.charAt(i) - 'a';
W JavaScript Np .:
"a".charCodeAt().toString(2)
zwraca 1100001checker = 1100001 | checker;
// kontroler staje się 1100001 (w 32-bitowej reprezentacji staje się 000000000 ..... 00001100001)Ale chcę, aby moja
int checker
maska bitowa ( ) ustawiała tylko jeden bit, ale kontroler to 1100001Użyjmy,
val
który jest zresetowanyWiersze 5 i 6 są dobrze wyjaśnione @Ivan answer
źródło
Na wszelki wypadek, jeśli ktoś szuka odpowiednika kotlin dla unikalnych znaków w łańcuchu za pomocą wektora bitowego
Ref: https://www.programiz.com/kotlin-programming/bitwise
źródło