Explicar o uso de um vetor de bits para determinar se todos os caracteres são exclusivos


150

Estou confuso sobre como um vetor de bits funcionaria para fazer isso (não estou muito familiarizado com vetores de bits). Aqui está o código fornecido. Alguém poderia me guiar por isso?

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

Particularmente, o que está checkerfazendo?


Está em Java, mas se houver algo semelhante em C / C ++, seria mais útil para mim.
user1136342

101
Este código foi retirado de Cracking The Code Interview
Dejell

2
você já testou isso? Parece que ele vai deixar de detectar 'a' caracteres duplicados uma vez que é definido como 0 e deixou-deslocamento ainda vai mantê-lo em 0.
Riz

3
Observe que a solução é usada para caracteres inferiores az, o que significa que estamos usando-a para encontrar duplicidade de 26 caracteres. Portanto, int tirar 32 bits pode ser usado aqui. Se o intervalo fosse maior, a solução não funcionaria.
A3.14_Infinity

1
Onde as pessoas cometem erros é que elas confundem com a sintaxe do operador shift esquerdo - é 1 que é movido para a esquerda por x (= str.charAt (i) - 'a') coloca os bits de NOT x deslocados para a esquerda em 1 lugar.
nanosoft 27/03

Respostas:


100

int checkeré usado aqui como armazenamento para bits. Cada bit no valor inteiro pode ser tratado como um sinalizador, portanto, eventualmente, inté uma matriz de bits (sinalizador). Cada bit no seu código indica se o caractere com o índice do bit foi encontrado na string ou não. Você pode usar o vetor de bits pelo mesmo motivo, em vez de int. Existem duas diferenças entre eles:

  • Size . inttem tamanho fixo, geralmente 4 bytes, o que significa 8 * 4 = 32 bits (sinalizadores). O vetor de bits geralmente pode ter tamanho diferente ou você deve especificar o tamanho no construtor.

  • API . Com vetores de bits, você terá mais facilidade para ler o código, provavelmente algo como isto:

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

    pois intvocê terá um código lógico de bit de nível inferior:

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

Provavelmente também intpode ser um pouco mais rápido, porque as operações com bits são de nível muito baixo e podem ser executadas como estão pela CPU. O BitVector permite escrever um código um pouco menos enigmático, além de poder armazenar mais sinalizadores.

Para referência futura: o vetor de bit também é conhecido como bitSet ou bitArray. Aqui estão alguns links para essa estrutura de dados para diferentes idiomas / plataformas:


O java tem uma classe BitVector? Não encontrei nenhuma documentação para isso!
DeJell # 03

O tamanho tem tamanho fixo, que é de 32 bits. Isso significa que ele só pode testar apenas 32 caracteres? Eu testei que, esta função poderia testar "abcdefgZZ" é falso, mas "abcdefg @@" retorna true.
precisa saber é o seguinte

1
O Google me levou aqui. @Dejel Aqui está a estrutura de dados java que você pode usar: docs.oracle.com/javase/7/docs/api/java/util/BitSet.html . Espero que isso ajude alguém a viajar pelos intertubos.
Nattyddubbs 20/01/14

@nattyddubbs, obrigado, eu adicionei este e vários outros links para a resposta
Snowbear

222

Suspeito que você tenha esse código do mesmo livro que estou lendo ... O código aqui não é tão enigmático quanto os operadores- | =, & e <<, que normalmente não são usados ​​por nós leigos - o autor não se deu ao trabalho de explicar o processo nem de quais são as mecânicas envolvidas aqui. Eu estava contente com a resposta anterior sobre este tópico no início, mas apenas em um nível abstrato. Voltei a ela porque achava que precisava haver uma explicação mais concreta - a falta de uma sempre me deixa com uma sensação desconfortável.

Este operador << é um shifter bit a bit esquerdo, pega a representação binária desse número ou operando e o desloca sobre quantos lugares especificados pelo operando ou número à direita, como em números decimais, apenas em binários. Estamos multiplicando pela base 2 - quando subimos, no entanto, muitos lugares que não a base 10 -, portanto, o número à direita é o expoente e o número à esquerda é um múltiplo base de 2.

Este operador | = pega o operando à esquerda e o coloca com o operando à direita - e este - '&' e são os bits dos dois operandos à esquerda e à direita dele.

Portanto, o que temos aqui é uma tabela de hash que está sendo armazenada em um número binário de 32 bits toda vez que o verificador obtém ou'd ( checker |= (1 << val)) com o valor binário designado de uma letra, seu bit correspondente está sendo configurado como true. O valor do caractere é and'd com o verificador ( checker & (1 << val)) > 0) - se for maior que 0, sabemos que temos um dupe porque dois bits idênticos são definidos como true e juntos retornarão true ou '1'.

Existem 26 locais binários, cada um dos quais corresponde a uma letra minúscula - o autor afirmou assumir que a string contém apenas letras minúsculas - e isso ocorre porque só temos mais 6 (em número inteiro de 32 bits) mais lugares para consumir - e então ter uma colisão

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

Então, para uma string de entrada 'azya', conforme avançamos passo a passo

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

Agora, ele declara uma duplicata


@ ivan-tichy você testou isso? Parece que ele vai deixar de detectar 'a' caracteres duplicados uma vez que é definido como 0 e deixou-deslocamento ainda vai mantê-lo em 0.
Riz

1
@Riz Não, sempre começando com '1', o algoritmo muda 1 com base na letra. Portanto, se a letra 'a' aparecer uma vez, será 1, que é (.... 000001).
Taylor Halliday

2
@ Ivan Man, eu estava pensando a mesma coisa. Até a resposta selecionada não explicou os operadores. Obrigado pela informação detalhada.
WowBow 6/01/15

Devo assumir que a verificação exclusiva acima funciona apenas com o conjunto de caracteres classificados (abcd ... z)? não com (bcad ...)
abdul rashid

"Eu tenho uma suspeita de que você recebeu esse código do mesmo livro que estou lendo" o mesmo aqui :) me fez rir
espinha dorsal

39

Acho que todas essas respostas explicam como isso funciona, no entanto, senti vontade de dar minha opinião sobre como a via melhor, renomeando algumas variáveis, adicionando outras e adicionando comentários a ela:

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
Ótima explicação. Obrigado!
Hormigas

Limpar explanation..Thank você
Prabhaker A

Ótima explicação. Fácil de entender. Obrigado
Anil Kumar

Esse é o melhor
Vladimir Nabokov

É por essa razão que os comentários foram inventados.
Sr. Suryaa Jha 30/12/19

30

Suponho também que seu exemplo vem do livro Cracking The Code Interview e minha resposta está relacionada a esse contexto.

Para usar esse algoritmo para resolver o problema, temos que admitir que apenas passaremos caracteres de a a z (minúsculas).

Como existem apenas 26 letras e elas são classificadas corretamente na tabela de codificação que usamos, isso nos garante que todas as possíveis diferenças str.charAt(i) - 'a'serão inferiores a 32 (o tamanho da variável int checker).

Como explicado por Snowbear, estamos prestes a usar a checkervariável como uma matriz de bits. Vamos ter uma abordagem por exemplo:

Digamos str equals "test"

  • Primeira passagem (i = t)

verificador == 0 (000000000000000000000000000000000000)

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)
  • Segunda passagem (i = e)

verificador == 524288 (000000000000100000000000000000000000)

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

e assim por diante .. até encontrarmos um bit já definido no verificador para um caractere específico através da condição

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

Espero que ajude


2
Explicação muito melhor do que o restante da IMO, mas uma coisa que ainda não entendo é o verificador = 00000000000010000000000000000000 | 00000000000000000000000000010000 não é esse operador bit a bit | = OR. isso não escolheria um valor ou outro desde então? por que ele usa e define e ambos os bits?
CodeCrack

@ CodeCrack você disse que é OR bit a bit. Ele compara no nível de bits e não no nível de matriz de bits. Nota: int é bit Array
MusicMan 9/16

7

Existem algumas excelentes respostas já fornecidas acima. Portanto, não quero repetir o que já foi dito. Mas queria adicionar algumas coisas para ajudar no programa acima, pois eu apenas trabalhei no mesmo programa e tive algumas perguntas, mas depois de passar algum tempo, tenho mais clareza sobre esse programa.

Antes de mais nada, o "verificador" é usado para rastrear o caractere que já foi percorrido na String para verificar se há algum caractere sendo repetido.

Agora "verificador" é um tipo de dados int, portanto, ele pode ter apenas 32 bits ou 4 bytes (dependendo da plataforma), portanto este programa pode funcionar corretamente apenas para um conjunto de caracteres dentro de um intervalo de 32 caracteres. Essa é a razão, este programa subtrai 'a' de cada caractere para fazer com que este programa seja executado apenas com caracteres minúsculos. No entanto, se você misturar caracteres minúsculos e maiúsculos, não funcionará.

A propósito, se você não subtrair 'a' de cada caractere (veja a instrução abaixo), este programa funcionará corretamente apenas para String com caracteres maiúsculos ou String com caracteres minúsculos. Portanto, o escopo do programa acima aumenta de apenas caracteres minúsculos para caracteres maiúsculos, mas eles não podem ser misturados.

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

No entanto, eu queria escrever um programa genérico usando a Operação Bitwise que deve funcionar com qualquer caractere ASCII sem se preocupar com maiúsculas, minúsculas, números ou qualquer caractere especial. Para fazer isso, nosso "verificador" deve ser grande o suficiente para armazenar 256 caracteres (tamanho do conjunto de caracteres ASCII). Mas um int em Java não funcionaria, pois só pode armazenar 32 bits. Portanto, no programa abaixo, estou usando a classe BitSet disponível no JDK, que pode ter qualquer tamanho definido pelo usuário passado ao instanciar um objeto BitSet.

Aqui está um programa que faz a mesma coisa que o programa acima, escrito usando o operador Bitwise, mas este programa funcionará para uma String com qualquer caractere do conjunto de caracteres 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
Eu estava procurando por esta solução, no entanto, não há necessidade de duas variáveis ​​BitSet. Apenas o rastreador é suficiente. Atualizado para código de 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

Ler a resposta de Ivan acima realmente me ajudou, embora eu a expressasse de maneira um pouco diferente.

O <<in (1 << val)é um operador um pouco instável. Ele pega 1(que em binário é representado como 000000001, com tantos zeros anteriores quanto você gosta / é alocado pela memória) e o desloca para a esquerda por valespaços. Como assumimos apenas z e subtraímos a acada vez, cada letra terá um valor de 0 a 25, que será o índice dessa letra à direita na checkerrepresentação booleana do inteiro, uma vez que moveremos a 1para a esquerda em alguns checker valmomentos.

No final de cada verificação, vemos o |=operador. Isso mescla dois números binários, substituindo todos 0's por 1' se 1existe um em qualquer operando nesse índice. Aqui, isso significa que, onde quer que 1exista (1 << val), isso 1será copiado para dentro checker, enquanto todos checkeros 1s existentes serão preservados.

Como você provavelmente pode adivinhar, a 1funciona aqui como um sinalizador booleano para true. Quando verificamos se um caractere já está representado na string, comparamos checker, que neste momento é essencialmente uma matriz de sinalizadores booleanos ( 1valores) nos índices de caracteres que já foram representados, com o que é essencialmente uma matriz de valores booleanos com um 1sinalizador no índice do caractere atual.

O &operador realiza essa verificação. Semelhante ao |=, o &operador copiará sobre um 1 somente se ambos os operandos tiverem 1nesse índice. Portanto, essencialmente, apenas os sinalizadores já presentes nos checkerquais também estão representados (1 << val)serão copiados. Nesse caso, isso significa que apenas se o caractere atual já tiver sido representado, haverá um 1presente em qualquer lugar no resultado de checker & (1 << val). E se a 1estiver presente em qualquer lugar no resultado dessa operação, o valor do booleano retornado será > 0e o método retornará false.

Acho que é por isso que os vetores de bits também são chamados de matrizes de bits . Porque, mesmo que não sejam do tipo de dados da matriz, eles podem ser usados ​​de maneira semelhante à maneira como as matrizes são usadas para armazenar sinalizadores booleanos.


1
Muito útil, obrigado pelas suas informações sobre java.
Bachiri Taoufiq Abderrahman 11/01

4

Explicação simples (com código JS abaixo)

  • Uma variável inteira por código de máquina é uma matriz de 32 bits
  • Todas as operações pouco inteligentes são 32-bit
  • Eles são agnósticos da arquitetura OS / CPU ou do sistema numérico escolhido da linguagem, por exemplo, DEC64para JS.
  • Esta abordagem duplicação achado é semelhante ao armazenamento de caracteres em uma matriz de tamanho 32 , onde, montamos 0thíndice se encontrar ana seqüência, 1stpara be assim por diante.
  • Um caractere duplicado na string terá seu bit correspondente ocupado ou, nesse caso, definido como 1.
  • Ivan já explicou : Como esse cálculo de índice funciona nesta resposta anterior .

Resumo das operações:

  • Executar operação AND entre checker& indexdo personagem
  • Internamente, ambos são Int-32-Arrays
  • É uma operação pouco a pouco entre esses 2.
  • Verifique se ifa saída da operação foi1
  • E se output == 1
    • A checkervariável possui esse bit de índice-th específico em ambas as matrizes
    • Portanto, é uma duplicata.
  • E se output == 0
    • Esse personagem não foi encontrado até agora
    • Executar uma operação OR entre checker& indexdo personagem
    • Assim, a atualização do bit do índice-th para 1
    • Atribua a saída para checker

Premissas:

  • Assumimos que teremos todos os caracteres minúsculos
  • E esse tamanho 32 é suficiente
  • Portanto, começamos nosso índice contando a partir de 96 como ponto de referência , considerando o código ascii para ais97

Dado a seguir é o código fonte 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

Observe que em JS, apesar de números inteiros serem de 64 bits, uma operação um pouco sábia sempre é feita em 32 bits.

Exemplo: Se a sequência for aa:

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

Vamos quebrar o código linha por linha.

verificador int = 0; Estamos iniciando um verificador que nos ajudará a encontrar valores duplicados.

int val = str.charAt (i) - 'a'; Estamos obtendo o valor ASCII do caractere na posição 'i'th da string e subtraindo-o com o valor ASCII de' a '. Como a suposição é que a sequência é apenas de caracteres inferiores, o número de caracteres é limitado a 26. Hece, o valor de 'val' sempre será> = 0.

if ((verificador & (1 << val))> 0) retorna false;

verificador | = (1 << val);

Agora esta é a parte complicada. Vamos considerar um exemplo com a string "abcda". Idealmente, isso deve retornar falso.

Para iteração de loop 1:

Verificador: 000000000000000000000000000000000000

val: 97-97 = 0

1 << 0: 000000000000000000000000000000000001

verificador & (1 << val): 000000000000000000000000000000000000 não é> 0

Daí verificador: 000000000000000000000000000000000001

Para iteração de loop 2:

Verificador: 000000000000000000000000000000000001

val: 98-97 = 1

1 << 0: 000000000000000000000000000000000010

verificador & (1 << val): 000000000000000000000000000000000000 não é> 0

Daí verificador: 000000000000000000000000000000000011

Para iteração de loop 3:

Verificador: 000000000000000000000000000000000011

val: 99-97 = 0

1 << 0: 000000000000000000000000000000000100

verificador & (1 << val): 000000000000000000000000000000000000 não é> 0

Daí verificador: 000000000000000000000000000000000111

Para iteração de loop 4:

Verificador: 00000000000000000000000000000000111

val: 100-97 = 0

1 << 0: 000000000000000000000000000000001000

verificador & (1 << val): 000000000000000000000000000000000000 não é> 0

Daí verificador: 000000000000000000000000000000001111

Para iteração de loop 5:

Verificador: 0000000000000000000000000000001111

val: 97-97 = 0

1 << 0: 000000000000000000000000000000000001

verificador & (1 << val): 00000000000000000000000000000001 é> 0

Portanto, retorne false.


val: 99-97 = 0 deve ser val: 99-97 = 2 e val: 100-97 = 0 deve ser 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

As publicações anteriores explicam bem o que o bloco de código faz e eu quero adicionar minha solução simples usando a estrutura de dados 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:   }

Do jeito que eu entendi usando Javascript. Assumindo entradavar inputChar = "abca"; //find if inputChar has all unique characters

Vamos começar

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

Acima da linha Encontra o valor binário do primeiro caractere em inputChar, que é a , a = 97 em ascii e, em seguida, converter 97 em binário se torna 1100001 .

Em Javascript, por exemplo: "a".charCodeAt().toString(2) retorna 1100001

checker = 0 // representação binária de 32 bits = 0000000000000000000000000

checker = 1100001 | checker; // o verificador se torna 1100001 (na representação de 32 bits, se torna 000000000 ..... 00001100001)

Mas eu quero que meu bitmask ( int checker) defina apenas um bit, mas o verificador é 1100001

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

Agora, o código acima é útil. Eu apenas subtraio 97 sempre (ASCII val de 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

Permite usar o valque é redefinido

As linhas 5 e 6 estão bem explicadas @Ivan answer


0

Caso alguém esteja procurando o equivalente kotlin de caracteres únicos em uma string usando o vetor de bits

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
}

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.