Spiega l'uso di un vettore di bit per determinare se tutti i caratteri sono unici


150

Sono confuso su come un vettore bit funzionerebbe per fare questo (non troppo familiare con i vettori bit). Ecco il codice fornito. Qualcuno potrebbe guidarmi attraverso questo?

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 particolare, cosa sta checkerfacendo?


È in Java ma se c'è qualcosa di simile in C / C ++ sarebbe più utile per me.
user1136342

101
Questo codice è stato preso da Cracking The Code Interview
Dejell,

2
hai provato questo? sembra che non riuscirà a rilevare i caratteri 'a' duplicati poiché è impostato su 0 e spostato a sinistra lo manterrà comunque a 0.
Riz

3
Si noti che la soluzione viene utilizzata per i caratteri inferiori az, il che significa che la stiamo usando per trovare duplicati per 26 caratteri. Quindi, int prendendo 32 bit può essere usato qui. Se l'intervallo fosse stato maggiore, la soluzione non funzionerà.
a3.14_Infinity

1
Il punto in cui le persone commettono errori è che si confondono con la sintassi dell'operatore di spostamento a sinistra: è 1 che viene spostato a sinistra da x (= str.charAt (i) - 'a') posiziona i bit di NOT x spostati a sinistra di 1 posizione.
nanosoft il

Risposte:


100

int checkerviene utilizzato qui come memoria per bit. Ogni bit in valore intero può essere trattato come un flag, quindi alla fine intè un array di bit (flag). Ogni bit nel codice indica se il carattere con l'indice del bit è stato trovato nella stringa o no. È possibile utilizzare bit vector per lo stesso motivo anziché int. Esistono due differenze tra loro:

  • Dimensioni . intha una dimensione fissa, di solito 4 byte che significa 8 * 4 = 32 bit (flag). Il vettore di bit di solito può avere dimensioni diverse oppure è necessario specificare la dimensione nel costruttore.

  • API . Con i bit bit avrai più facile leggere il codice, probabilmente qualcosa del genere:

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

    poiché intavrai un codice logico di bit di livello inferiore:

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

Inoltre, probabilmente intpotrebbe essere un po 'più veloce, perché le operazioni con bit sono di livello molto basso e possono essere eseguite così come sono dalla CPU. BitVector consente invece di scrivere un po 'meno codice criptico e in più di memorizzare più flag.

Per riferimento futuro: il vettore di bit è anche noto come bitSet o bitArray. Ecco alcuni link a questa struttura di dati per diverse lingue / piattaforme:


Java ha una classe BitVector? Non sono riuscito a trovare alcuna documentazione ad esso!
Dejell,

La dimensione ha una dimensione fissa, che è di 32 bit. Significa che può testare solo 32 caratteri univoci? Ho provato che, questa funzione potrebbe testare "abcdefgZZ" è falso, ma "abcdefg @@" restituisce vero.
tli2020,

1
Google mi ha portato qui. @Dejel Ecco la struttura dei dati java che puoi usare: docs.oracle.com/javase/7/docs/api/java/util/BitSet.html . Spero che questo aiuti qualcuno che viaggia attraverso gli intertubes.
nattyddubbs,

@nattyddubbs, grazie, ho aggiunto questo e molti altri link alla risposta
Snowbear,

223

Ho il sospetto furbo che tu abbia ottenuto questo codice dallo stesso libro che sto leggendo ... Il codice stesso qui non è così criptico come gli operatori- | =, & e << che non sono normalmente usati da noi laici: l'autore non si è preso la briga di dedicare del tempo extra alla spiegazione del processo né di quali siano i meccanici coinvolti. All'inizio ero contento della risposta precedente su questo thread, ma solo a livello astratto. Ci sono tornato perché sentivo che doveva esserci una spiegazione più concreta: la mancanza di uno mi lascia sempre con una sensazione inquieta.

Questo operatore << è uno shifter bit a sinistra, prende la rappresentazione binaria di quel numero o operando e lo sposta su molti punti specificati dall'operando o numero a destra, come in numeri decimali, solo in binari. Stiamo moltiplicando per la base 2 - quando ci spostiamo verso l'alto, tuttavia molti posti non si basano su 10 - quindi il numero a destra è l'esponente e il numero a sinistra è un multiplo di base di 2.

Questo operatore | = prende l'operando a sinistra e lo è con l'operando a destra e questo - '&' ed è i bit di entrambi gli operandi a sinistra e a destra di esso.

Quindi quello che abbiamo qui è una tabella hash che viene memorizzata in un numero binario a 32 bit ogni volta che il checker ottiene o vorrebbe ( checker |= (1 << val)) con il valore binario designato di una lettera il suo bit corrispondente viene impostato su vero. Il valore del personaggio è e sarebbe con il checker ( checker & (1 << val)) > 0) - se è maggiore di 0 sappiamo che abbiamo un dupe- perché due bit identici impostati su true e insieme restituiranno true o '1' '.

Ci sono 26 posizioni binarie, ognuna delle quali corrisponde a una lettera minuscola - l'autore ha detto di assumere che la stringa contenga solo lettere minuscole - e questo perché abbiamo solo 6 posti in più (nell'intero a 32 bit) da consumare - e di noi ottenere una collisione

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

Quindi, per una stringa di input 'azya', mentre ci spostiamo passo dopo passo

stringa "a"

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

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

a and checker=0 no dupes condition

stringa 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

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

stringa "azy"

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

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

stringa 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

Ora dichiara un duplicato


@ ivan-tichy hai provato questo? sembra che non riuscirà a rilevare i caratteri 'a' duplicati poiché è impostato su 0 e spostato a sinistra lo manterrà comunque a 0.
Riz

1
@Riz No, inizia sempre con '1', l'algoritmo sposta 1 in base alla lettera. Quindi, se la lettera 'a' arriva una volta, sarà 1, che è (.... 000001).
Taylor Halliday,

2
@Ivan Man, stavo pensando la stessa cosa. Anche la risposta selezionata non ha spiegato gli operatori. Grazie per le informazioni dettagliate.
WowBow,

Devo supporre che il controllo univoco sopra funzioni solo con il set di caratteri Ordinati (abcd ... z)? non con (bcad ...)
abdul rashid

"Ho il sospetto furbo che tu abbia ricevuto questo codice dallo stesso libro che sto leggendo" stesso qui :) mi ha fatto ridere
spina dorsale

39

Penso che tutte queste risposte spieghino come funziona, tuttavia mi è sembrato di dare il mio contributo su come l'ho visto meglio, rinominando alcune variabili, aggiungendone altre e aggiungendo commenti:

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

2
Ottima spiegazione Grazie!
Hormigas,

Spiegazione chiara ... Grazie
Prabhaker,

Ottima spiegazione Facile da capire. Grazie
Anil Kumar il

Quello è il migliore
Vladimir Nabokov il

Questo è il motivo per cui sono stati inventati i commenti.
Sig. Suryaa Jha il

30

Presumo anche che il tuo esempio provenga dal libro Cracking The Code Interview e la mia risposta è collegata a questo contesto.

Per utilizzare questo algoritmo per risolvere il problema, dobbiamo ammettere che passeremo solo caratteri dalla a alla z (lettere minuscole).

Poiché ci sono solo 26 lettere e queste sono correttamente ordinate nella tabella di codifica che utilizziamo, questo ci garantisce che tutte le potenziali differenze str.charAt(i) - 'a'saranno inferiori a 32 (la dimensione della variabile int checker).

Come spiegato da Snowbear, stiamo per usare la checkervariabile come una matrice di bit. Diamo un approccio con l'esempio:

Diciamo str equals "test"

  • Primo passaggio (i = t)

checker == 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)
  • Secondo passaggio (i = e)

checker == 524288 (00000000000010000000000000000000)

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

e così via .. fino a quando non troviamo un bit già impostato nel controllo per un carattere specifico tramite la condizione

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

Spero che sia d'aiuto


2
Spiegazione molto migliore rispetto al resto dell'IMO ma una cosa che ancora non capisco è checker = 00000000000010000000000000000000 | 00000000000000000000000000010000 non è bitally | = OR operator. non sceglierebbe un valore o l'altro da allora? perché usa e imposta ed entrambi i bit?
CodeCrack

@CodeCrack hai detto che è OR bit a bit. Confronta a livello di bit non a livello di array di bit. Nota: int is bit Array
MusicMan

7

Ci sono un paio di risposte eccellenti già fornite sopra. Quindi non voglio ripetere ciò che è già stato detto. Ma volevo aggiungere un paio di cose per aiutare con il programma sopra dato che ho appena lavorato allo stesso programma e ho avuto un paio di domande ma dopo aver trascorso un po 'di tempo, ho più chiarezza su questo programma.

Innanzitutto "checker" è usato per tracciare il personaggio che è già attraversato nella stringa per vedere se ci sono personaggi che si stanno ripetendo.

Ora "checker" è un tipo di dati int quindi può avere solo 32 bit o 4 byte (a seconda della piattaforma), quindi questo programma può funzionare correttamente solo per un set di caratteri in un intervallo di 32 caratteri. Questo è il motivo, questo programma sottrae 'a' da ogni carattere per far funzionare questo programma solo per caratteri minuscoli. Tuttavia, se mescoli caratteri minuscoli e maiuscoli, non funzionerebbe.

A proposito, se non si sottrae 'a' da ciascun carattere (vedere l'istruzione seguente), questo programma funzionerà correttamente solo per String con caratteri maiuscoli o String con solo caratteri minuscoli. Quindi la portata del programma sopra aumenta anche da caratteri minuscoli a caratteri maiuscoli ma non possono essere mescolati insieme.

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

Tuttavia, volevo scrivere un programma generico usando Bitwise Operation che dovrebbe funzionare con qualsiasi carattere ASCII senza preoccuparmi di maiuscole, minuscole, numeri o caratteri speciali. Per fare ciò, il nostro "correttore" dovrebbe essere abbastanza grande da contenere 256 caratteri (dimensione del set di caratteri ASCII). Ma un int in Java non funzionerebbe in quanto può contenere solo 32 bit. Quindi nel programma seguente, sto usando la classe BitSet disponibile in JDK che può avere qualsiasi dimensione definita dall'utente passata durante l'istanza di un oggetto BitSet.

Ecco un programma che fa la stessa cosa del programma precedente scritto usando l'operatore Bitwise ma questo programma funzionerà per una stringa con qualsiasi carattere dal set di caratteri 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;

}

1
Stavo cercando questa soluzione, tuttavia non sono necessarie due variabili BitSet. È sufficiente solo il tracker. Aggiornato per il codice loop: for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
zambro

7

Leggere la risposta di Ivan sopra mi ha davvero aiutato, anche se la definirei in modo leggermente diverso.

La <<in (1 << val)è un operatore di spostamento bit. Prende 1(che in binario è rappresentato come 000000001, con tutti gli zeri precedenti che desideri / sono allocati dalla memoria) e lo sposta a sinistra di valspazi. Dato che assumiamo solo az e sottraggiamo aogni volta, ogni lettera avrà un valore di 0-25, che sarà l'indice di quella lettera da destra nella checkerrappresentazione booleana dell'intero, poiché sposteremo 1la sinistra a checker valvolte.

Alla fine di ogni controllo, vediamo l' |=operatore. Questo unisce due numeri binari, sostituendo tutti quelli 0con 1'se 1esiste uno dei due operandi in quell'indice. Qui, ciò significa che ovunque 1esiste un oggetto in (1 << val)cui 1verrà copiato checker, mentre tutti checkergli 1 esistenti verranno conservati.

Come probabilmente puoi immaginare, una 1funzione qui come una bandiera booleana è vera. Quando controlliamo per vedere se un carattere è già rappresentato nella stringa, confrontiamo checker, che a questo punto è essenzialmente una matrice di flag ( 1valori) booleani agli indici dei caratteri che sono già stati rappresentati, con ciò che è essenzialmente una matrice di valori booleani con un 1flag nell'indice del carattere corrente.

L' &operatore esegue questo controllo. Simile a |=, l' &operatore copierà su 1 solo se entrambi gli operandi hanno un 1a quell'indice. Quindi, in sostanza, verranno copiate solo le bandiere già presenti in checkerquelle rappresentate (1 << val). In questo caso, ciò significa che solo se il personaggio attuale è già stato rappresentato, ci sarà un 1presente ovunque nel risultato di checker & (1 << val). E se a 1è presente ovunque nel risultato di tale operazione, allora il valore del valore booleano restituito è > 0e il metodo restituisce false.

Questo è, suppongo, il motivo per cui i bit vettori sono anche chiamati array di bit . Perché, anche se non sono del tipo di dati dell'array, possono essere usati in modo simile al modo in cui gli array vengono utilizzati per archiviare flag booleani.


1
Molto utile, grazie per i tuoi spruzzi di informazioni java.
Bachiri Taoufiq Abderrahman,

4

Spiegazione semplice (con il codice JS di seguito)

  • Una variabile intera per codice macchina è un array a 32 bit
  • Tutte le operazioni sagge sono 32-bit
  • Sono indipendenti dall'architettura OS / CPU o dal sistema di numerazione scelto della lingua, ad esempio DEC64per JS.
  • Questo approccio alla ricerca di duplicati è simile alla memorizzazione di caratteri in un array di dimensioni 32 dove, impostiamo l' 0thindice se troviamo anella stringa, 1stper be così via.
  • Un carattere duplicato nella stringa avrà il bit corrispondente occupato o, in questo caso, impostato su 1.
  • Ivan ha già spiegato : come funziona questo calcolo dell'indice in questa risposta precedente .

Riepilogo delle operazioni:

  • Esegue un'operazione AND tra checker& indexdel personaggio
  • Internamente entrambi lo sono Int-32-Arrays
  • È un'operazione un po 'saggia tra questi 2.
  • Verificare che ifl'output dell'operazione sia stato1
  • Se output == 1
    • La checkervariabile ha quel particolare bit di indice impostato in entrambi gli array
    • Quindi è un duplicato.
  • Se output == 0
    • Questo personaggio non è stato trovato finora
    • Esegui un'operazione OR tra checker& indexdel personaggio
    • Quindi, aggiornando l'indice-th bit a 1
    • Assegna l'output a checker

ipotesi:

  • Abbiamo ipotizzato che avremo tutti i caratteri minuscoli
  • E quella dimensione 32 è sufficiente
  • Quindi, abbiamo iniziato il nostro indice contando da 96 come punto di riferimento considerando che il codice ASCIIa è97

Di seguito è riportato il codice sorgente 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

Si noti che in JS, nonostante i numeri interi siano di 64 bit, un'operazione sempre saggia viene sempre eseguita su 32 bit.

Esempio: se la stringa è aaquindi:

// 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

3

Consente di suddividere il codice riga per riga.

int checker = 0; Stiamo avviando un controllo che ci aiuterà a trovare valori duplicati.

int val = str.charAt (i) - 'a'; Stiamo ottenendo il valore ASCII del carattere nella posizione "i" della stringa e sottraendolo con il valore ASCII di "a". Poiché si presume che la stringa contenga solo caratteri inferiori, il numero di caratteri è limitato a 26. Hece, il valore di 'val' sarà sempre> = 0.

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

checker | = (1 << val);

Ora questa è la parte difficile. Consideriamo un esempio con la stringa "abcda". Questo dovrebbe idealmente restituire false.

Per iterazione loop 1:

Controllo: 00000000000000000000000000000000

val: 97-97 = 0

1 << 0: 0000000000000000000000000000000001

checker & (1 << val): 0000000000000000000000000000000000 non è> 0

Quindi checker: 0000000000000000000000000000000001

Per iterazione loop 2:

Controllo: 00000000000000000000000000000001

val: 98-97 = 1

1 << 0: 0000000000000000000000000000000010

checker & (1 << val): 0000000000000000000000000000000000 non è> 0

Quindi checker: 0000000000000000000000000000000011

Per l'iterazione ciclica 3:

Controllo: 00000000000000000000000000000011

val: 99-97 = 0

1 << 0: 0000000000000000000000000000000100

checker & (1 << val): 0000000000000000000000000000000000 non è> 0

Quindi checker: 00000000000000000000000000000011

Per iterazione in loop 4:

Controllo: 00000000000000000000000000000111

val: 100-97 = 0

1 << 0: 00000000000000000000000000001001

checker & (1 << val): 0000000000000000000000000000000000 non è> 0

Quindi checker: 0000000000000000000000000000001111

Per iterazione loop 5:

Controllo: 00000000000000000000000000001111

val: 97-97 = 0

1 << 0: 0000000000000000000000000000000001

checker & (1 << val): 0000000000000000000000000000000001 è> 0

Quindi restituire false.


val: 99-97 = 0 dovrebbe essere val: 99-97 = 2 e val: 100-97 = 0 dovrebbe essere 3
Brosef

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

0

I post precedenti spiegano bene cosa fa il blocco di codice e voglio aggiungere la mia semplice soluzione usando la struttura di dati java BitSet:

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

0
Line 1:   public static boolean isUniqueChars(String str) {
Line 2:      int checker = 0;
Line 3:      for (int i = 0; i < str.length(); ++i) {
Line 4:          int val = str.charAt(i) - 'a';
Line 5:          if ((checker & (1 << val)) > 0) return false;
Line 6:         checker |= (1 << val);
Line 7:      }
Line 8:      return true;
Line 9:   }

Il modo in cui ho capito usando Javascript. Supponendo inputvar inputChar = "abca"; //find if inputChar has all unique characters

Iniziamo

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

Sopra la riga Trova il valore binario del primo carattere in inputChar che è a , a = 97 in ascii, quindi converte 97 in binario diventa 1100001 .

In Javascript, ad esempio: "a".charCodeAt().toString(2) restituisce 1100001

checker = 0 // rappresentazione binaria a 32 bit = 000000000000000000000000000

checker = 1100001 | checker; // il checker diventa 1100001 (nella rappresentazione a 32 bit diventa 000000000 ..... 00001100001)

Ma voglio che la mia maschera di bit ( int checker) imposti solo un bit, ma checker è 1100001

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

Ora il codice sopra è utile. Sottrao sempre 97 sempre (ASCII val di a)

val = 0; // 97 - 97  Which is  a - a
val = 1; // 98 - 97 Which is b - a
val = 1;  // 99 - 97 Which is c - a

Consente di valreimpostare

La riga 5 e la riga 6 sono spiegate bene alla risposta dell'Ivan


0

Nel caso in cui qualcuno stia cercando kotlin equivalenti di caratteri univoci in una stringa usando il vettore bit

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
}

Rif: https://www.programiz.com/kotlin-programming/bitwise

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.