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á checker
fazendo?
java
string
bit-manipulation
bitvector
user1136342
fonte
fonte
Respostas:
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 deint
. Existem duas diferenças entre eles:Size .
int
tem 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
int
você 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
int
pode 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:
fonte
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
Então, para uma string de entrada 'azya', conforme avançamos passo a passo
string 'a'
string 'az'
string 'azy'
string 'azya'
Agora, ele declara uma duplicata
fonte
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:
fonte
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 intchecker
).Como explicado por Snowbear, estamos prestes a usar a
checker
variável como uma matriz de bits. Vamos ter uma abordagem por exemplo:Digamos
str equals "test"
e assim por diante .. até encontrarmos um bit já definido no verificador para um caractere específico através da condição
Espero que ajude
fonte
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.
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.
fonte
for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
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 pega1
(que em binário é representado como000000001
, com tantos zeros anteriores quanto você gosta / é alocado pela memória) e o desloca para a esquerda porval
espaços. Como assumimos apenas z e subtraímos aa
cada vez, cada letra terá um valor de 0 a 25, que será o índice dessa letra à direita nachecker
representação booleana do inteiro, uma vez que moveremos a1
para a esquerda em algunschecker
val
momentos.No final de cada verificação, vemos o
|=
operador. Isso mescla dois números binários, substituindo todos0
's por1
' se1
existe um em qualquer operando nesse índice. Aqui, isso significa que, onde quer que1
exista(1 << val)
, isso1
será copiado para dentrochecker
, enquanto todoschecker
os 1s existentes serão preservados.Como você provavelmente pode adivinhar, a
1
funciona aqui como um sinalizador booleano para true. Quando verificamos se um caractere já está representado na string, comparamoschecker
, que neste momento é essencialmente uma matriz de sinalizadores booleanos (1
valores) nos índices de caracteres que já foram representados, com o que é essencialmente uma matriz de valores booleanos com um1
sinalizador no índice do caractere atual.O
&
operador realiza essa verificação. Semelhante ao|=
, o&
operador copiará sobre um1
somente se ambos os operandos tiverem1
nesse índice. Portanto, essencialmente, apenas os sinalizadores já presentes noschecker
quais 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á um1
presente em qualquer lugar no resultado dechecker & (1 << val)
. E se a1
estiver presente em qualquer lugar no resultado dessa operação, o valor do booleano retornado será> 0
e 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.
fonte
Explicação simples (com código JS abaixo)
32-bit
DEC64
para JS.0th
índice se encontrara
na seqüência,1st
parab
e assim por diante.Resumo das operações:
checker
&index
do personagemInt-32-Arrays
if
a saída da operação foi1
output == 1
checker
variável possui esse bit de índice-th específico em ambas as matrizesoutput == 0
checker
&index
do personagem1
checker
Premissas:
a
is97
Dado a seguir é o código fonte JavaScript .
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
:i = 0
i = 1
fonte
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.
fonte
fonte
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:
fonte
Do jeito que eu entendi usando Javascript. Assumindo entrada
var inputChar = "abca"; //find if inputChar has all unique characters
Vamos começar
Line 4: int val = str.charAt(i) - 'a';
Em Javascript, por exemplo:
"a".charCodeAt().toString(2)
retorna 1100001checker = 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 é 1100001Permite usar o
val
que é redefinidoAs linhas 5 e 6 estão bem explicadas @Ivan answer
fonte
Caso alguém esteja procurando o equivalente kotlin de caracteres únicos em uma string usando o vetor de bits
Ref: https://www.programiz.com/kotlin-programming/bitwise
fonte