Ik ben in de war over hoe een bitvector zou werken om dit te doen (niet zo bekend met bitvectoren). Hier is de gegeven code. Kan iemand me hier doorheen leiden?
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;
}
In het bijzonder, wat doet de checker
?
Antwoord 1, autoriteit 100%
int checker
wordt hier gebruikt als opslag voor bits. Elke bit in integerwaarde kan worden behandeld als een vlag, dus uiteindelijk is int
een array van bits (vlag). Elk bit in uw code geeft aan of het teken met de bitindex in string is gevonden of niet. Je zou om dezelfde reden bitvector kunnen gebruiken in plaats van int
. Er zijn twee verschillen tussen hen:
-
Maat.
int
heeft een vaste grootte, meestal 4 bytes, wat 8*4=32 bits (vlaggen) betekent. Bitvector kan meestal een verschillende grootte hebben of u moet de grootte specificeren in de constructor. -
API. Met bitvectoren heb je gemakkelijker te lezen code, waarschijnlijk zoiets als dit:
vector.SetFlag(4, true); // set flag at index 4 as true
voor
int
heb je een lagere bitlogica-code:checker |= (1 << 5); // set flag at index 5 to true
Waarschijnlijk is ook int
misschien een beetje sneller, omdat bewerkingen met bits erg laag zijn en door de CPU kunnen worden uitgevoerd zoals ze zijn. Met BitVector kan in plaats daarvan een beetje minder cryptische code worden geschreven, plus het kan meer vlaggen opslaan.
Voor toekomstig gebruik: bitvector is ook bekend als bitSet of bitArray. Hier zijn enkele links naar deze gegevensstructuur voor verschillende talen/platforms:
- CPP: BitSet
- Java: BitSet
- C#: BitVector32en BitArray
Antwoord 2, autoriteit 91%
Ik heb een heimelijk vermoeden dat je deze code uit hetzelfde boek hebt gehaald als ik aan het lezen ben… De code zelf is hier lang niet zo cryptisch als de operators- |=, &, en << die normaal niet door ons leken worden gebruikt – de auteur nam niet de moeite om de extra tijd te nemen om het proces uit te leggen, noch wat de eigenlijke mechanica hier is. Ik was in het begin tevreden met het vorige antwoord op deze draad, maar alleen op een abstract niveau. Ik kwam erop terug omdat ik vond dat er een meer concrete verklaring moest zijn – het ontbreken ervan geeft me altijd een ongemakkelijk gevoel.
deze operator & lt; & lt; Is een linkse bitwise-shifter het kost de binaire weergave van dat nummer of operand en verschuift het echter op veel plaatsen die zijn opgegeven door de operand of het nummer aan de rechterkant zoals in decimale nummers alleen in binaries. We vermenigvuldigen zich met basis 2 – wanneer we echter opdagen, veel plaatsen niet base 10-, dus het nummer aan de rechterkant is de exponent en het nummer aan de linkerkant is een basis meervoudig van 2.
Deze operator | = Neem de operand aan de linkerkant en of is het met de operand aan de rechter- en deze ‘& amp;’ en zijn de stukjes van beide operanden naar links en rechts daarvan.
Dus wat we hier hebben is een hash-tabel die wordt opgeslagen in een 32-bits binair nummer telkens wanneer de checker krijgt of (checker |= (1 << val)
) Met de aangewezen binaire waarde van een letter is het bijbehorende bit waarop het is ingesteld op TRUE.
De waarde van het personage is en had met de checker (checker & (1 << val)) > 0
) – Als het groter is dan 0, weten we dat we een dupe hebben – omdat twee identieke bits op true zijn en samen true of ‘1’ zullen terugkeren.
Er zijn 26 binaire plaatsen die elk overeenkomen met een kleine letter – de auteur heeft gezegd dat de reeks alleen kleine letters bevat – en dit is omdat we slechts 6 meer hebben (in 32-bits integer) plaatsen om te consumeren en dan krijgen we een botsing
00000000000000000000000000000001 a 2^0
00000000000000000000000000000010 b 2^1
00000000000000000000000000000100 c 2^2
00000000000000000000000000001000 d 2^3
00000000000000000000000000010000 e 2^4
00000000000000000000000000100000 f 2^5
00000000000000000000000001000000 g 2^6
00000000000000000000000010000000 h 2^7
00000000000000000000000100000000 i 2^8
00000000000000000000001000000000 j 2^9
00000000000000000000010000000000 k 2^10
00000000000000000000100000000000 l 2^11
00000000000000000001000000000000 m 2^12
00000000000000000010000000000000 n 2^13
00000000000000000100000000000000 o 2^14
00000000000000001000000000000000 p 2^15
00000000000000010000000000000000 q 2^16
00000000000000100000000000000000 r 2^17
00000000000001000000000000000000 s 2^18
00000000000010000000000000000000 t 2^19
00000000000100000000000000000000 u 2^20
00000000001000000000000000000000 v 2^21
00000000010000000000000000000000 w 2^22
00000000100000000000000000000000 x 2^23
00000001000000000000000000000000 y 2^24
00000010000000000000000000000000 z 2^25
Dus, voor een invoerreeks ‘azya’, zoals we stap voor stap gaan
string ‘a’
a =00000000000000000000000000000001
checker=00000000000000000000000000000000
checker='a' or checker;
// checker now becomes = 00000000000000000000000000000001
checker=00000000000000000000000000000001
a and checker=0 no dupes condition
string ‘az’
checker=00000000000000000000000000000001
z =00000010000000000000000000000000
z and checker=0 no dupes
checker=z or checker;
// checker now becomes 00000010000000000000000000000001
string ‘azy’
checker= 00000010000000000000000000000001
y = 00000001000000000000000000000000
checker and y=0 no dupes condition
checker= checker or y;
// checker now becomes = 00000011000000000000000000000001
string ‘azya’
checker= 00000011000000000000000000000001
a = 00000000000000000000000000000001
a and checker=1 we have a dupe
Nu declareert het een duplicaat
Antwoord 3, autoriteit 57%
Ik denk dat al deze antwoorden verklaren hoe dit werkt, maar ik had zin om mijn input te geven over hoe ik het beter zag, door enkele variabelen te hernoemen, andere toe te voegen en er opmerkingen aan toe te voegen:
public static boolean isUniqueChars(String str) {
/*
checker is the bit array, it will have a 1 on the character index that
has appeared before and a 0 if the character has not appeared, you
can see this number initialized as 32 0 bits:
00000000 00000000 00000000 00000000
*/
int checker = 0;
//loop through each String character
for (int i = 0; i < str.length(); ++i) {
/*
a through z in ASCII are charactets numbered 97 through 122, 26 characters total
with this, you get a number between 0 and 25 to represent each character index
0 for 'a' and 25 for 'z'
renamed 'val' as 'characterIndex' to be more descriptive
*/
int characterIndex = str.charAt(i) - 'a'; //char 'a' would get 0 and char 'z' would get 26
/*
created a new variable to make things clearer 'singleBitOnPosition'
It is used to calculate a number that represents the bit value of having that
character index as a 1 and the rest as a 0, this is achieved
by getting the single digit 1 and shifting it to the left as many
times as the character index requires
e.g. character 'd'
00000000 00000000 00000000 00000001
Shift 3 spaces to the left (<<) because 'd' index is number 3
1 shift: 00000000 00000000 00000000 00000010
2 shift: 00000000 00000000 00000000 00000100
3 shift: 00000000 00000000 00000000 00001000
Therefore the number representing 'd' is
00000000 00000000 00000000 00001000
*/
int singleBitOnPosition = 1 << characterIndex;
/*
This peforms an AND between the checker, which is the bit array
containing everything that has been found before and the number
representing the bit that will be turned on for this particular
character. e.g.
if we have already seen 'a', 'b' and 'd', checker will have:
checker = 00000000 00000000 00000000 00001011
And if we see 'b' again:
'b' = 00000000 00000000 00000000 00000010
it will do the following:
00000000 00000000 00000000 00001011
& (AND)
00000000 00000000 00000000 00000010
-----------------------------------
00000000 00000000 00000000 00000010
Since this number is different than '0' it means that the character
was seen before, because on that character index we already have a
1 bit value
*/
if ((checker & singleBitOnPosition) > 0) {
return false;
}
/*
Remember that
checker |= singleBitOnPosition is the same as
checker = checker | singleBitOnPosition
Sometimes it is easier to see it expanded like that.
What this achieves is that it builds the checker to have the new
value it hasnt seen, by doing an OR between checker and the value
representing this character index as a 1. e.g.
If the character is 'f' and the checker has seen 'g' and 'a', the
following will happen
'f' = 00000000 00000000 00000000 00100000
checker(seen 'a' and 'g' so far) = 00000000 00000000 00000000 01000001
00000000 00000000 00000000 00100000
| (OR)
00000000 00000000 00000000 01000001
-----------------------------------
00000000 00000000 00000000 01100001
Therefore getting a new checker as 00000000 00000000 00000000 01100001
*/
checker |= singleBitOnPosition;
}
return true;
}
Antwoord 4, autoriteit 7%
Het lezen van Ivan’s antwoord hierboven heeft me echt geholpen, hoewel ik het iets anders zou formuleren.
De <<
in (1 << val)
is een bitverschuivende operator. Het duurt 1
(wat in binair getal wordt weergegeven als 000000001
, met zoveel voorafgaande nullen als je wilt / wordt toegewezen door het geheugen) en verschuift het naar links met val
spaties. Aangezien we alleen az aannemen en elke keer a
aftrekken, heeft elke letter een waarde van 0-25, wat de index van die letter van rechts in de checker
zal zijn integer’s booleaanse representatie, aangezien we de 1
naar links zullen verplaatsen in checker
val
keer.
Aan het einde van elke controle zien we de operator |=
. Dit voegt twee binaire getallen samen, waarbij alle 0
‘s worden vervangen door 1
‘en als er een 1
bestaat in een van beide operands op die index. Hier betekent dat dat overal waar een 1
bestaat in (1 << val)
, die 1
zal worden gekopieerd naar checker
, terwijl alle bestaande enen van checker
behouden blijven.
Zoals je waarschijnlijk wel kunt raden, functioneert een 1
hier als een booleaanse vlag voor waar. Als we controleren of een teken al in de tekenreeks voorkomt, vergelijken we checker
, wat op dit moment in wezen een reeks booleaanse vlaggen (1
-waarden) is aan de indexen van tekens die al zijn weergegeven, met in wezen een array van booleaanse waarden met een 1
-vlag bij de index van het huidige teken.
De operator &
voert deze controle uit. Net als bij de |=
, kopieert de operator &
een 1
alleenals beide operanden een 1
bij die index. Dus in wezen worden alleen vlaggen die al aanwezig zijn in checker
en die ook worden weergegeven in (1 << val)
gekopieerd. In dit geval betekent dat alleen als het huidige teken al is weergegeven, er een 1
aanwezig zal zijn ergens in het resultaat van checker & (1 << val)
. En als een 1
ergens in het resultaat van die bewerking aanwezig is, dan is de waarde van de geretourneerde boolean > 0
, en de methode retourneert false.
Dit is, vermoed ik, waarom bitvectoren ook bit-arraysworden genoemd. Omdat, hoewel ze niet van het gegevenstype array zijn, ze kunnen worden gebruikt op dezelfde manier als arrays worden gebruikt om booleaanse vlaggen op te slaan.
Antwoord 5, autoriteit 6%
Er zijn hierboven al een paar uitstekende antwoorden gegeven. Dus ik wil niet herhalen wat alles al is gezegd. Maar ik wilde een paar dingen toevoegen om te helpen met het bovenstaande programma, omdat ik net hetzelfde programma heb doorlopen en een paar vragen had, maar na wat tijd te hebben doorgebracht, heb ik meer duidelijkheid over dit programma.
Allereerst wordt “checker” gebruikt om het teken te volgen dat al in de tekenreeks is doorlopen om te zien of er tekens worden herhaald.
Nu is “checker” een int-gegevenstype, dus het kan slechts 32 bits of 4 bytes hebben (afhankelijk van het platform), dus dit programma kan alleen correct werken voor een tekenset binnen een bereik van 32 tekens. Dat is de reden dat dit programma ‘a’ van elk teken aftrekt om dit programma alleen voor kleine letters te laten werken. Als u echter kleine letters en hoofdletters door elkaar gebruikt, werkt het niet.
Trouwens, als u ‘a’ niet van elk teken aftrekt (zie onderstaande verklaring), dan werkt dit programma alleen correct voor String met hoofdletters of String met alleen kleine letters. Dus de reikwijdte van het bovenstaande programma neemt toe van alleen kleine letters naar hoofdletters, maar ze kunnen niet met elkaar worden gemengd.
int val = str.charAt(i) - 'a';
Ik wilde echter een generiek programma schrijven met Bitwise Operation dat zou moeten werken voor alle ASCII-tekens zonder me zorgen te maken over hoofdletters, kleine letters, cijfers of een speciaal teken. Om dit te doen, moet onze “checker” groot genoeg zijn om 256 tekens op te slaan (ASCII-tekensetgrootte). Maar een int in Java zou niet werken omdat het maar 32 bits kan opslaan. Daarom gebruik ik in het onderstaande programma de BitSet-klasse die beschikbaar is in JDK, waaraan elke door de gebruiker gedefinieerde grootte kan worden doorgegeven tijdens het instantiëren van een BitSet-object.
Hier is een programma dat hetzelfde doet als bovenstaand programma, geschreven met de Bitwise-operator, maar dit programma werkt voor een string met elk willekeurig teken uit de ASCII-tekenset.
public static boolean isUniqueStringUsingBitVectorClass(String s) {
final int ASCII_CHARACTER_SET_SIZE = 256;
final BitSet tracker = new BitSet(ASCII_CHARACTER_SET_SIZE);
// if more than 256 ASCII characters then there can't be unique characters
if(s.length() > 256) {
return false;
}
//this will be used to keep the location of each character in String
final BitSet charBitLocation = new BitSet(ASCII_CHARACTER_SET_SIZE);
for(int i = 0; i < s.length(); i++) {
int charVal = s.charAt(i);
charBitLocation.set(charVal); //set the char location in BitSet
//check if tracker has already bit set with the bit present in charBitLocation
if(tracker.intersects(charBitLocation)) {
return false;
}
//set the tracker with new bit from charBitLocation
tracker.or(charBitLocation);
charBitLocation.clear(); //clear charBitLocation to store bit for character in the next iteration of the loop
}
return true;
}
Antwoord 6, autoriteit 3%
Laten we de code regel voor regel opsplitsen.
int checker = 0;We starten een checker waarmee we dubbele waarden kunnen vinden.
int val = str.charAt(i) – ‘a’;We krijgen de ASCII-waarde van het teken op de ‘i’-positie van de tekenreeks en trekken deze af met de ASCII-waarde van ‘een’. Aangezien de veronderstelling is dat de string alleen uit lagere tekens bestaat, is het aantal tekens beperkt tot 26. De waarde van ‘val’ is dus altijd >= 0.
if ((checker & (1 << val)) > 0) retourneert false;
checker |= (1 << waarde);
Dit is nu het lastige gedeelte. Laten we een voorbeeld bekijken met string “abcda”. Dit zou idealiter false moeten retourneren.
For loop iteratie 1:
Checker: 00000000000000000000000000000000
waarde: 97-97 = 0
1 << 0: 00000000000000000000000000000001
controleur & (1 << waarde): 00000000000000000000000000000000 is niet > 0
Vandaar controle: 00000000000000000000000000000001
For loop iteratie 2:
Checker: 00000000000000000000000000000001
waarde: 98-97 = 1
1 << 1: 00000000000000000000000000000010
controleur & (1 << waarde): 00000000000000000000000000000000 is niet > 0
Vandaar controle: 00000000000000000000000000000011
For loop iteratie 3:
Checker: 00000000000000000000000000000011
waarde: 99-97 = 2
1 << 2: 000000000000000000000000100
controleur & (1 << waarde): 00000000000000000000000000000000 is niet > 0
Vandaar controle: 0000000000000000000000000000000111
For loop iteratie 4:
Checker: 0000000000000000000000000000000111
waarde: 100-97 = 3
1 << 3: 00000000000000000000000000001000
controleur & (1 << waarde): 00000000000000000000000000000000 is niet > 0
Vandaar controle: 00000000000000000000000000001111
For loop iteratie 5:
Checker: 00000000000000000000000000001111
waarde: 97-97 = 0
1 << 0: 00000000000000000000000000000001
controleur & (1 << waarde): 00000000000000000000000000000001 is > 0
Retourneer daarom false.
Antwoord 7, autoriteit 2%
public static void main (String[] args)
{
//In order to understand this algorithm, it is necessary to understand the following:
//int checker = 0;
//Here we are using the primitive int almost like an array of size 32 where the only values can be 1 or 0
//Since in Java, we have 4 bytes per int, 8 bits per byte, we have a total of 4x8=32 bits to work with
//int val = str.charAt(i) - 'a';
//In order to understand what is going on here, we must realize that all characters have a numeric value
for (int i = 0; i < 256; i++)
{
char val = (char)i;
System.out.print(val);
}
//The output is something like:
// !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
//There seems to be ~15 leading spaces that do not copy paste well, so I had to use real spaces instead
//To only print the characters from 'a' on forward:
System.out.println();
System.out.println();
for (int i=0; i < 256; i++)
{
char val = (char)i;
//char val2 = val + 'a'; //incompatible types. required: char found: int
int val2 = val + 'a'; //shift to the 'a', we must use an int here otherwise the compiler will complain
char val3 = (char)val2; //convert back to char. there should be a more elegant way of doing this.
System.out.print(val3);
}
//Notice how the following does not work:
System.out.println();
System.out.println();
for (int i=0; i < 256; i++)
{
char val = (char)i;
int val2 = val - 'a';
char val3 = (char)val2;
System.out.print(val3);
}
//I'm not sure why this spills out into 2 lines:
//EDIT I cant seem to copy this into stackoverflow!
System.out.println();
System.out.println();
//So back to our original algorithm:
//int val = str.charAt(i) - 'a';
//We convert the i'th character of the String to a character, and shift it to the right, since adding shifts to the right and subtracting shifts to the left it seems
//if ((checker & (1 << val)) > 0) return false;
//This line is quite a mouthful, lets break it down:
System.out.println(0<<0);
//00000000000000000000000000000000
System.out.println(0<<1);
//00000000000000000000000000000000
System.out.println(0<<2);
//00000000000000000000000000000000
System.out.println(0<<3);
//00000000000000000000000000000000
System.out.println(1<<0);
//00000000000000000000000000000001
System.out.println(1<<1);
//00000000000000000000000000000010 == 2
System.out.println(1<<2);
//00000000000000000000000000000100 == 4
System.out.println(1<<3);
//00000000000000000000000000001000 == 8
System.out.println(2<<0);
//00000000000000000000000000000010 == 2
System.out.println(2<<1);
//00000000000000000000000000000100 == 4
System.out.println(2<<2);
// == 8
System.out.println(2<<3);
// == 16
System.out.println("3<<0 == "+(3<<0));
// != 4 why 3???
System.out.println(3<<1);
//00000000000000000000000000000011 == 3
//shift left by 1
//00000000000000000000000000000110 == 6
System.out.println(3<<2);
//00000000000000000000000000000011 == 3
//shift left by 2
//00000000000000000000000000001100 == 12
System.out.println(3<<3);
// 24
//It seems that the - 'a' is not necessary
//Back to if ((checker & (1 << val)) > 0) return false;
//(1 << val means we simply shift 1 by the numeric representation of the current character
//the bitwise & works as such:
System.out.println();
System.out.println();
System.out.println(0&0); //0
System.out.println(0&1); //0
System.out.println(0&2); //0
System.out.println();
System.out.println();
System.out.println(1&0); //0
System.out.println(1&1); //1
System.out.println(1&2); //0
System.out.println(1&3); //1
System.out.println();
System.out.println();
System.out.println(2&0); //0
System.out.println(2&1); //0 0010 & 0001 == 0000 = 0
System.out.println(2&2); //2 0010 & 0010 == 2
System.out.println(2&3); //2 0010 & 0011 = 0010 == 2
System.out.println();
System.out.println();
System.out.println(3&0); //0 0011 & 0000 == 0
System.out.println(3&1); //1 0011 & 0001 == 0001 == 1
System.out.println(3&2); //2 0011 & 0010 == 0010 == 2, 0&1 = 0 1&1 = 1
System.out.println(3&3); //3 why?? 3 == 0011 & 0011 == 3???
System.out.println(9&11); // should be... 1001 & 1011 == 1001 == 8+1 == 9?? yay!
//so when we do (1 << val), we take 0001 and shift it by say, 97 for 'a', since any 'a' is also 97
//why is it that the result of bitwise & is > 0 means its a dupe?
//lets see..
//0011 & 0011 is 0011 means its a dupe
//0000 & 0011 is 0000 means no dupe
//0010 & 0001 is 0011 means its no dupe
//hmm
//only when it is all 0000 means its no dupe
//so moving on:
//checker |= (1 << val)
//the |= needs exploring:
int x = 0;
int y = 1;
int z = 2;
int a = 3;
int b = 4;
System.out.println("x|=1 "+(x|=1)); //1
System.out.println(x|=1); //1
System.out.println(x|=1); //1
System.out.println(x|=1); //1
System.out.println(x|=1); //1
System.out.println(y|=1); // 0001 |= 0001 == ?? 1????
System.out.println(y|=2); // ??? == 3 why??? 0001 |= 0010 == 3... hmm
System.out.println(y); //should be 3??
System.out.println(y|=1); //already 3 so... 0011 |= 0001... maybe 0011 again? 3?
System.out.println(y|=2); //0011 |= 0010..... hmm maybe.. 0011??? still 3? yup!
System.out.println(y|=3); //0011 |= 0011, still 3
System.out.println(y|=4); //0011 |= 0100.. should be... 0111? so... 11? no its 7
System.out.println(y|=5); //so we're at 7 which is 0111, 0111 |= 0101 means 0111 still 7
System.out.println(b|=9); //so 0100 |= 1001 is... seems like xor?? or just or i think, just or... so its 1101 so its 13? YAY!
//so the |= is just a bitwise OR!
}
public static boolean isUniqueChars(String str) {
int checker = 0;
for (int i = 0; i < str.length(); ++i) {
int val = str.charAt(i) - 'a'; //the - 'a' is just smoke and mirrors! not necessary!
if ((checker & (1 << val)) > 0) return false;
checker |= (1 << val);
}
return true;
}
public static boolean is_unique(String input)
{
int using_int_as_32_flags = 0;
for (int i=0; i < input.length(); i++)
{
int numeric_representation_of_char_at_i = input.charAt(i);
int using_0001_and_shifting_it_by_the_numeric_representation = 1 << numeric_representation_of_char_at_i; //here we shift the bitwise representation of 1 by the numeric val of the character
int result_of_bitwise_and = using_int_as_32_flags & using_0001_and_shifting_it_by_the_numeric_representation;
boolean already_bit_flagged = result_of_bitwise_and > 0; //needs clarification why is it that the result of bitwise & is > 0 means its a dupe?
if (already_bit_flagged)
return false;
using_int_as_32_flags |= using_0001_and_shifting_it_by_the_numeric_representation;
}
return true;
}
Antwoord 8
Vorige berichten leggen goed uit wat het codeblok doet en ik wil mijn eenvoudige oplossing toevoegen met behulp van de BitSet java-gegevensstructuur:
private static String isUniqueCharsUsingBitSet(String string) {
BitSet bitSet =new BitSet();
for (int i = 0; i < string.length(); ++i) {
int val = string.charAt(i);
if(bitSet.get(val)) return "NO";
bitSet.set(val);
}
return "YES";
}
9
Voor het geval dat iemand op zoek is naar Kotlin-equivalent van unieke tekens in een reeks met behulp van bit vector
fun isUnique(str: String): Boolean {
var checker = 0
for (i in str.indices) {
val bit = str.get(i) - 'a'
if (checker.and(1 shl bit) > 0) return false
checker = checker.or(1 shl bit)
}
return true
}