web-dev-qa-db-fra.com

Expliquer l'utilisation d'un vecteur de bits pour déterminer si tous les caractères sont uniques

Je ne comprends pas comment un vecteur de bits fonctionnerait (pas trop familier avec les vecteurs de bits). Voici le code donné. Quelqu'un pourrait-il s'il vous plaît me guider à travers cela?

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;
}

En particulier, que fait la checker?

109
user1136342

int checker est utilisé ici comme stockage de bits. Chaque bit en valeur entière peut être traité comme un indicateur. Ainsi, éventuellement int est un tableau de bits (indicateur). Chaque bit de votre code indique si le caractère avec l'index de bit a été trouvé dans la chaîne ou non. Vous pouvez utiliser un vecteur de bits pour la même raison au lieu de int. Il y a deux différences entre eux:

  • Taille . int a une taille fixe, généralement 4 octets, ce qui signifie 8 * 4 = 32 bits (drapeaux). Le vecteur de bits peut généralement être de taille différente ou vous devez spécifier la taille dans le constructeur.

  • API. Avec les vecteurs bits, vous aurez plus de facilité à lire le code, probablement quelque chose comme ceci:

    vector.SetFlag(4, true); // set flag at index 4 as true

    pour int vous aurez un code logique de bit de niveau inférieur:

    checker |= (1 << 5); // set flag at index 5 to true

Probablement aussi int pourrait être un peu plus rapide, parce que les opérations avec des bits sont de niveau très bas et peuvent être exécutées telles quelles par la CPU. BitVector permet d'écrire un peu moins de code crypté à la place et peut stocker plus de drapeaux.

Pour référence future: le vecteur de bits est également appelé bitSet ou bitArray. Voici quelques liens vers cette structure de données pour différentes langues/plateformes:

88
Snowbear

Je soupçonne que vous avez obtenu ce code dans le même livre que je lis ... Le code lui-même ici n'est pas aussi crypté que les opérateurs- | =, & et << qui ne sont normalement pas utilisés par Nous, profanes, l'auteur n'a pas pris la peine de prendre plus de temps pour expliquer le processus, ni quels sont les mécanismes réels impliqués ici. Je me suis contenté de la réponse précédente sur ce fil au début, mais seulement à un niveau abstrait. J'y suis revenu parce que j'estimais qu'il devait y avoir une explication plus concrète: le manque d'une explication me laisse toujours avec un sentiment de malaise. 

Cet opérateur << est un décaleur de bits à gauche. Il prend la représentation binaire de ce nombre ou de cet opérande et la décale quel que soit le nombre d'emplacements spécifiés par l'opérande ou le nombre à droite, comme dans les nombres décimaux uniquement dans les fichiers binaires. Nous multiplions par la base 2 lorsque nous nous déplaçons vers le haut, mais pas avec la base 10, le nombre à droite est donc l'exposant et le nombre à gauche est un multiple de base de 2. 

Cet opérateur | = prend l'opérande à gauche et ou avec l'opérande à droite - et celui-ci - '&' et les bits des deux opérandes à gauche et à droite de celui-ci. 

Nous avons donc ici une table de hachage qui est stockée dans un nombre binaire de 32 bits chaque fois que le vérificateur obtient or'd (checker |= (1 << val)) avec la valeur binaire désignée d’une lettre, le bit correspondant est mis à la valeur vraie .La valeur du caractère est and avec le vérificateur (checker & (1 << val)) > 0) - s'il est supérieur à 0, nous savons que nous avons un dupe - car deux bits identiques définis sur true et renvoyés ensemble renverront true ou '1' '.

Il y a 26 emplacements binaires, chacun correspondant à une lettre minuscule (l'auteur n'a pas supposé que la chaîne ne contenait que des lettres minuscules), car nous n'avons plus que 6 emplacements supplémentaires (sur un entier de 32 bits) à consommer). avoir une collision

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

Donc, pour une chaîne d'entrée 'azya', au fur et à mesure que nous avançons

chaîne 'a'

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

checker='a' or checker;
// checker now becomes = 00000000000000000000000000000001
checker=00000000000000000000000000000001

a and checker=0 no dupes condition

chaîne 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

checker=z or checker;
// checker now becomes 00000010000000000000000000000001  

chaîne 'azy'

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

checker= checker or y;
// checker now becomes = 00000011000000000000000000000001

chaîne 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

Maintenant, il déclare un dupliquer

162
Ivan Tichy

Je suppose également que votre exemple provient du livre Cracking The Code Interview et ma réponse est liée à ce contexte.

Pour utiliser cet algorithme pour résoudre le problème, nous devons admettre que nous allons seulement passer des caractères de a à z (minuscules). 

Comme il n’ya que 26 lettres et que celles-ci sont correctement triées dans le tableau de codage utilisé, cela nous garantit que toutes les différences de potentiel str.charAt(i) - 'a' seront inférieures à 32 (la taille de la variable int checker).

Comme expliqué par Snowbear, nous sommes sur le point d'utiliser la variable checker comme un tableau de bits. Permet d'avoir une approche par exemple:

Disons str equals "test"

  • Premier passage (i = t)

vérificateur == 0 (00000000000000000000000000000000)

In ASCII, val = str.charAt(i) - 'a' = 116 - 97 = 19
What about 1 << val ?
1          == 00000000000000000000000000000001
1 << 19    == 00000000000010000000000000000000
checker |= (1 << val) means checker = checker | (1 << val)
so checker = 00000000000000000000000000000000 | 00000000000010000000000000000000
checker == 524288 (00000000000010000000000000000000)
  • Deuxième passage (i = e)

vérificateur == 524288 (00000000000010000000000000000000)

val = 101 - 97 = 4
1          == 00000000000000000000000000000001
1 << 4     == 00000000000000000000000000010000
checker |= (1 << val) 
so checker = 00000000000010000000000000000000 | 00000000000000000000000000010000
checker == 524304 (00000000000010000000000000010000)

et ainsi de suite .. jusqu'à trouver un bit déjà défini dans le vérificateur pour un caractère spécifique via la condition 

(checker & (1 << val)) > 0

J'espère que ça aide

28
Alex Bretet

Je pense que toutes ces réponses expliquent comment cela fonctionne, mais j’ai eu envie de donner mon avis sur la façon dont je le voyais mieux, en renommant certaines variables, en ajoutant d’autres et en ajoutant des commentaires:

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;
}
20
Miguel Durazo

Lire la réponse d'Ivan ci-dessus m'a vraiment aidé, bien que je l'énonce de façon un peu différente 

Le << dans (1 << val) est un opérateur à décalage. Il faut 1 (qui en binaire est représenté par 000000001, avec autant de zéros que vous le souhaitez/sont alloués par mémoire) et le décale à gauche de val espaces. Puisque nous supposons que nous ne faisons que z et que nous soustrayons a à chaque fois, chaque lettre aura une valeur de 0-25, ce qui correspondra à son index à partir de la droite dans la représentation booléenne de l'entier checker, puisque nous déplacerons le 1 vers la gauche checkerval times.

À la fin de chaque vérification, nous voyons l'opérateur |=. Ceci fusionne deux nombres binaires, remplaçant tous les 0 par les 1 si un 1 existe dans l'un des opérandes à cet index. Ici, cela signifie que chaque fois que 1 existe dans (1 << val), ce 1 sera copié dans checker, tandis que tous les 1 existants de checker seront préservés.

Comme vous pouvez probablement le deviner, un 1 fonctionne ici comme un indicateur booléen pour true. Lorsque nous vérifions si un caractère est déjà représenté dans la chaîne, nous comparons checker, qui est essentiellement un tableau de drapeaux booléens (valeurs 1) aux index des caractères déjà représentés, avec ce qui est essentiellement un tableau de valeurs booléennes avec un drapeau 1 à l'index du caractère actuel. 

L'opérateur & effectue cette vérification. Comme pour le |=, l'opérateur & copiera un 1 uniquement si les deux opérandes ont un 1 à cet index. Donc, essentiellement, seuls les drapeaux déjà présents dans checker et qui sont également représentés dans (1 << val) seront copiés. Dans ce cas, cela signifie que si le caractère actuel a déjà été représenté, un 1 sera présent n'importe où dans le résultat de checker & (1 << val). Et si un 1 est présent n'importe où dans le résultat de cette opération, la valeur du booléen renvoyé est > 0 et la méthode renvoie false. 

C’est, je suppose, pourquoi les vecteurs bits sont aussi appelés tableaux de bits . En effet, même s'ils ne font pas partie du type de données tableau, ils peuvent être utilisés de la même façon que les tableaux pour stocker des drapeaux booléens. 

6
Matthew Hinea

Quelques excellentes réponses ont déjà été fournies ci-dessus. Donc, je ne veux pas répéter ce que tout a déjà été dit. Mais je voulais ajouter quelques éléments pour aider avec le programme ci-dessus, car je venais de travailler sur le même programme et posais quelques questions, mais après avoir passé du temps, j'ai plus de clarté sur ce programme.

Tout d'abord, "checker" est utilisé pour suivre le caractère qui est déjà traversé dans la chaîne afin de voir s'il y a des caractères en train d'être répétés.

Désormais, "checker" est un type de données int et ne peut donc comporter que 32 bits ou 4 octets (selon la plate-forme). Ce programme ne peut donc fonctionner correctement que pour un jeu de caractères compris entre 32 et 32 ​​caractères. C’est la raison pour laquelle ce programme soustrait "a" à chaque caractère afin de le faire fonctionner uniquement pour les caractères en minuscule. Cependant, si vous mélangez les majuscules et les minuscules, cela ne fonctionnera pas.

En passant, si vous ne soustrayez pas a de chaque caractère (voir la déclaration ci-dessous), alors ce programme fonctionnera correctement pour String uniquement avec des majuscules ou String avec des minuscules seulement. Ainsi, la portée du programme ci-dessus augmente également, des caractères minuscules aux majuscules, mais ils ne peuvent pas être mélangés.

int val = str.charAt(i) - 'a'; 

Cependant, je voulais écrire un programme générique utilisant Bitwise Operation, qui devrait fonctionner pour tous les caractères ASCII sans se soucier des majuscules, des minuscules, des chiffres ou des caractères spéciaux. Pour ce faire, notre "vérificateur" doit être suffisamment grand pour stocker 256 caractères (taille du jeu de caractères ASCII). Mais un int en Java ne fonctionnerait pas car il ne peut stocker que 32 bits. Par conséquent, dans le programme ci-dessous, j'utilise la classe BitSet disponible dans JDK, qui peut avoir n'importe quelle taille définie par l'utilisateur transmise lors de l'instanciation d'un objet BitSet.

Voici un programme qui fait la même chose que le programme ci-dessus écrit en utilisant l'opérateur Bitwise mais ce programme fonctionnera pour une chaîne avec n'importe quel caractère du jeu de caractères ASCII.

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;

}
5
Prabhash Rathore

Explication simple (avec le code JS ci-dessous)

  • Une variable entière par code machine est un tableau 32 bits
  • Toutes les opérations au niveau des bits sont 32-bit
  • Ils sont agnostiques de l’architecture OS/CPU ou du système de numérotation choisi du langage, par exemple. DEC64 pour JS.
  • Cette approche de recherche de duplication est similaire à stocker des caractères dans un tableau de taille 32 où nous définissons 0th index si nous trouvons a dans la chaîne, 1st pour b & etc.
  • Un caractère en double dans la chaîne aura son bit correspondant occupé ou, dans ce cas, mis à 1.
  • Ivan a déjà expliqué: Comment ce calcul d'indice fonctionne-t-il dans cette réponse précédente .

Résumé des opérations:

  • Effectue les opérations ET entre checker & index du caractère
  • En interne, les deux sont Int-32-Arrays
  • C'est une opération un peu sage entre ces 2.
  • Vérifiez if le résultat de l'opération était 1
  • si output == 1
    • La variable checker a cet bit-index particulier défini dans les deux tableaux
    • Donc, c'est un doublon.
  • si output == 0
    • Ce personnage n'a pas encore été trouvé
    • Effectuez une opération OU entre checker & index du caractère
    • De ce fait, mettre à jour le bit index à 1
    • Attribuer la sortie à checker

Hypothèses:

  • Nous avons supposé que nous aurions tous les caractères minuscules
  • Et cette taille 32 suffit
  • Par conséquent, nous avons commencé notre index en comptant à partir de 96 comme point de référence en considérant le code ascii pour a est 97

Ci-dessous, le code source JavaScript.

function checkIfUniqueChars (str) {

    var checker = 0; // 32 or 64 bit integer variable 

    for (var i = 0; i< str.length; i++) {
        var index = str[i].charCodeAt(0) - 96;
        var bitRepresentationOfIndex = 1 << index;

        if ( (checker & bitRepresentationOfIndex) > 1) {
            console.log(str, false);
            return false;
        } else {
            checker = (checker | bitRepresentationOfIndex);
        }
    }
    console.log(str, true);
    return true;
}

checkIfUniqueChars("abcdefghi");  // true
checkIfUniqueChars("aabcdefghi"); // false
checkIfUniqueChars("abbcdefghi"); // false
checkIfUniqueChars("abcdefghii"); // false
checkIfUniqueChars("abcdefghii"); // false

Notez qu'en JS, bien que les entiers soient de 64 bits, une opération par bit est toujours effectuée sur 32 bits.

Exemple: Si la chaîne est aa, alors:

// checker is intialized to 32-bit-Int(0)
// therefore, checker is
checker= 00000000000000000000000000000000

i = 0

str[0] is 'a'
str[i].charCodeAt(0) - 96 = 1

checker 'AND' 32-bit-Int(1) = 00000000000000000000000000000000
Boolean(0) == false

// So, we go for the '`OR`' operation.

checker = checker OR 32-bit-Int(1)
checker = 00000000000000000000000000000001

i = 1

str[1] is 'a'
str[i].charCodeAt(0) - 96 = 1

checker= 00000000000000000000000000000001
a      = 00000000000000000000000000000001

checker 'AND' 32-bit-Int(1) = 00000000000000000000000000000001
Boolean(1) == true
// We've our duplicate now
4
DDM

Permet de décomposer le code ligne par ligne.

int checker = 0; Nous lançons un vérificateur qui nous aidera à trouver les valeurs en double.

int val = str.charAt (i) - 'a'; Nous obtenons la valeur ASCII du caractère à la "i" position de la chaîne et nous la soustraisons avec la valeur ASCII de "a". Puisque l'hypothèse est que la chaîne est composée uniquement de caractères inférieurs, le nombre de caractères est limité à 26. Hece, la valeur de 'val' sera toujours> = 0.

if ((checker & (1 << val))> 0) renvoie false;

vérificateur | = (1 << val);

Maintenant, c'est la partie la plus délicate. Permet de considérer un exemple avec la chaîne "abcda". Cela devrait idéalement retourner false.

Pour l'itération de boucle 1: 

Vérificateur: 00000000000000000000000000000000

val: 97-97 = 0

1 << 0: 00000000000000000000000000000001

checker & (1 << val): 00000000000000000000000000000000 n'est pas> 0

Donc vérificateur: 00000000000000000000000000000001

Pour l'itération de boucle 2: 

Vérificateur: 00000000000000000000000000000001

val: 98-97 = 1

1 << 0: 000000000000000000000000000000000010

checker & (1 << val): 00000000000000000000000000000000 n'est pas> 0

Donc vérificateur: 00000000000000000000000000000011

Pour l'itération de boucle 3: 

Vérificateur: 000000000000000000000000000000000011

val: 99-97 = 0

1 << 0: 00000000000000000000000000000100100

checker & (1 << val): 00000000000000000000000000000000 n'est pas> 0

Donc vérificateur: 00000000000000000000000000000111

Pour l'itération de boucle 4: 

Vérificateur: 00000000000000000000000000000111

val: 100-97 = 0

1 << 0: 000000000000000000000000000000001000

checker & (1 << val): 00000000000000000000000000000000 n'est pas> 0

Donc vérificateur: 00000000000000000000000000001111

Pour l'itération de boucle 5: 

Vérificateur: 00000000000000000000000000001111

val: 97-97 = 0

1 << 0: 00000000000000000000000000000001

checker & (1 << val): 00000000000000000000000000000001 is> 0

Par conséquent, retourne faux.

3
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;
}
2
asdf1234

Les publications précédentes expliquent bien le rôle du bloc de code et je souhaite ajouter ma solution simple à l'aide de la structure BitSet Java Data:

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";
}