Поясніть використання бітового вектора для визначення того, чи всі символи унікальні


150

Мене бентежить питання, як би трохи працював вектор для цього (не надто знайомий з бітовими векторами). Ось наведений код. Може хтось, будь ласка, пройде мене через це?

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

Зокрема, що checkerробиш?


Це в Java, але якщо є щось подібне в C / C ++, це було б для мене корисніше.
користувач1136342

101
Цей код узятий з Cracking The Code Interview
Dejell

2
Ви протестували це? здається, що не вдасться виявити дублікати символів "a", оскільки він встановлений на 0, а зсув ліворуч все одно буде тримати його на 0.
Riz

3
Зауважте, що рішення використовується для нижчих символів az, це означає, що ми використовуємо його для пошуку дублікату для 26 символів. Отже, тут можна використовувати 32 біти. Якщо діапазон був більшим, то рішення не працюватиме.
a3.14_Інфінітій

1
Там, де люди помиляються, це те, що вони плутають синтаксис оператора лівого зсуву - це 1, який переміщується ліворуч на x (= str.charAt (i) - 'a'), місця, де біт X зміщений вліво на 1 місце.
nanosoft

Відповіді:


100

int checkerтут використовується як сховище для бітів. Кожен біт цілого значення може розглядатися як прапор, тому врешті-решт intце масив бітів (flag). Кожен біт у вашому коді визначає, чи був знайдений символ із бітковим індексом у рядку чи ні. Ви можете використовувати бітовий вектор з тієї ж причини замість int. Між ними є дві відмінності:

  • Розмір . intмає фіксований розмір, як правило, 4 байти, що означає 8 * 4 = 32 біта (прапори). Бітовий вектор зазвичай може бути різного розміру або вам слід вказати розмір у конструкторі.

  • API . З бітовими векторами вам буде легше читати код, можливо, щось подібне:

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

    для intвас буде нижній рівень бітового логічного коду:

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

Також, можливо, це intможе бути трохи швидше, тому що операції з бітами дуже низького рівня і можуть виконуватись як є процесором. BitVector дозволяє писати трохи менше криптовалютного коду, а також він може зберігати більше прапорів.

Для подальшої довідки: бітовий вектор також відомий як bitSet або bitArray. Ось декілька посилань на цю структуру даних для різних мов / платформ:


Чи має java клас BitVector? Я не зміг знайти жодної документації на це!
Dejell

Розмір має фіксований розмір, який становить 32 біти. Це означає, що він може перевірити унікальні лише 32 символи? У мене є тест, що ця функція може перевірити "abcdefgZZ" - помилково, але "abcdefg @@" повернути істину.
tli2020

1
Google привів мене сюди. @Dejel Ось структура даних Java, яку ви можете використовувати: docs.oracle.com/javase/7/docs/api/java/util/BitSet.html . Сподіваємось, це допомагає комусь подорожувати міжтрубками.
nattyddubbs

@nattyddubbs, дякую, я додав це та ще декілька посилань на відповідь
Snowbear

222

У мене криється підозра, що ти отримав цей код із тієї самої книги, яку я читаю ... Сам код тут не настільки виразний, як оператори- | =, & та <<, які зазвичай не використовуються нам непростого - автор не потрудився займати додатковий час у поясненні процесу, а також, що насправді тут стосуються механіки. Спочатку я був задоволений попередньою відповіддю на цю тему, але лише на абстрактному рівні. Я повернувся до цього, бо відчув, що потрібно конкретніше пояснення - відсутність одного завжди залишає у мене неприємне почуття.

Цей оператор << - лівий побітний зсув, він приймає двійкове представлення цього числа або операнда і переміщує його, однак безліч місць, визначених операндом чи числом праворуч, як у десяткових числах лише у двійкових колах. Ми множимо на базу 2 - коли ми рухаємось вгору, однак багато місць, які не базуються 10, тому число праворуч є показником, а число ліворуч - базовим кратним 2.

Цей оператор | = візьміть операнда зліва і / або його з операндом праворуч, а цей - '&' і є бітами обох операндів зліва та справа від нього.

Отже, у нас є хеш-таблиця, яка зберігається у 32-бітовому двійковому номері кожного разу, коли шашка отримує or'd ( checker |= (1 << val)) з позначеним бінарним значенням букви, відповідним бітом якого він встановлюється в true. Значення символу є і з контролем ( checker & (1 << val)) > 0) - якщо воно більше 0, ми знаємо, що у нас є dupe - тому що два однакових біта встановлені на true та разом би повернули true або '1' '.

Є 26 двійкових місць, кожне з яких відповідає малій букві - автор сказав, що припускає, що рядок містить лише малі літери - і це тому, що у нас залишилось лише 6 (у 32 бітових цілих числах) місцях, а також ми отримати зіткнення

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

Отже, для вхідного рядка 'azya', коли ми рухаємося крок за кроком

рядок 'a'

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

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

a and checker=0 no dupes condition

рядок 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

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

рядок 'azy'

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

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

рядок 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

Тепер він оголошує дублікат


@ ivan-tichy це ти перевірив? здається, що не вдасться виявити дублюючі символи "a", оскільки він встановлений на 0, а зсув ліворуч все одно буде тримати його на 0.
Riz

1
@Riz Ні, його завжди починається з "1", алгоритм змінює 1 на основі букви. Отже, якщо буква "a" приходить один раз, це буде 1, що є (.... 000001).
Тейлор Халлідей

2
@Ivan Man, я думав те саме. Навіть вибрана відповідь не пояснила про операторів. Дякую за детальну інформацію.
WowBow

Чи слід припускати, що вище унікальні перевірки працюють лише з сортованим набором символів (abcd ... z)? не з (bcad ...)
abdul rasid

"У мене є підозра, що ти отримав цей код із тієї ж книги, яку я читаю" те саме тут :) змусив мене сміятися
хребет

39

Я думаю, що всі ці відповіді пояснюють, як це працює, однак я відчував, що хочу дати свій внесок у те, як я бачив це краще, перейменувавши деякі змінні, додавши деякі інші та додавши до нього коментарі:

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
Чудове пояснення. Спасибі!
Гормігас

Чітке пояснення ..
Дякую

Чудове пояснення. Легко зрозуміти. Дякую
Аніл Кумар

Той найкращий
Володимир Набоков

Це дуже причина, чому були винайдені коментарі.
Містер Сурья Джа

30

Я також припускаю, що ваш приклад походить з книги « Cracking The Interview Interview», і моя відповідь пов'язана з цим контекстом.

Щоб використовувати цей алгоритм для вирішення проблеми, ми повинні визнати, що ми лише збираємося передати символи від a до z (малі регістри).

Оскільки буває лише 26 букв, і вони належним чином відсортовані в таблиці кодування, яку ми використовуємо, це гарантує нам, що всі потенційні різниці str.charAt(i) - 'a'будуть поступатися 32 (розмір змінної int checker).

Як пояснив Snowbear, ми збираємось використовувати checkerзмінну як масив бітів. Давайте підходимо на прикладі:

Скажімо str equals "test"

  • Перший прохід (i = t)

перевірка == 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)
  • Другий прохід (i = e)

шашка == 524288 (00000000000010000000000000000000)

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

і так далі .. поки ми не знайдемо вже встановлений біт у шасі для конкретного символу через умову

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

Сподіваюся, це допомагає


2
Набагато краще пояснення, ніж решта ІМО, але одне, чого я все одно не отримую, це перевірка = 00000000000010000000000000000000 | 00000000000000000000000000010000 - це не побіто | = АБО оператор. Не став би цей вибір одним чи іншим значенням? чому він використовує та встановлює і обидва біти?
CodeCrack

@CodeCrack Ви сказали, що це біт АБО. Він порівнює на рівні бітів, а не на рівні бітового масиву. Примітка: int bit bit Array
MusicMan

7

Наведено кілька чудових відповідей, описаних вище. Тому я не хочу повторювати все, що вже було сказано. Але я хотів додати кілька речей, щоб допомогти з вищевказаною програмою, оскільки я щойно працював над тією ж програмою і мав пару питань, але, витративши деякий час, я маю більше ясності щодо цієї програми.

Перш за все, "шашка" використовується для відстеження символу, який вже пройдений в рядку, щоб побачити, чи є повторювані символи.

Тепер "шашка" - це тип даних int, тому він може мати лише 32 біти або 4 байти (залежно від платформи), тому ця програма може правильно працювати лише для набору символів у межах 32 символів. Це причина, що ця програма віднімає «а» від кожного символу для того, щоб змусити цю програму працювати лише для малих символів. Однак якщо ви змішуєте малі та великі літери, то це не вийде.

До речі, якщо ви не віднімаєте «a» від кожного символу (див. Нижче твердження), ця програма буде працювати правильно лише для String з великими символами великого регістру або String з лише малими символами. Таким чином, сфера дії вищезгаданої програми збільшується від символів малої літери до великих символів, але їх неможливо змішати.

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

Однак я хотів написати загальну програму за допомогою операції Bitwise, яка повинна працювати для будь-яких символів ASCII, не турбуючись про верхній регістр, малі регістри, цифри чи будь-який спеціальний символ. Для цього наша "шашка" повинна бути достатньо великою, щоб зберігати 256 символів (розмір набору символів ASCII). Але int в Java не буде працювати, оскільки він може зберігати лише 32 біти. Отже, у нижченаведеній програмі я використовую клас BitSet, доступний у JDK, який може передавати будь-який визначений користувачем розмір під час інстанціювання об'єкта BitSet.

Ось програма, яка робить те саме, що описана вище програма, написана за допомогою оператора Bitwise, але ця програма буде працювати для рядка з будь-яким символом з набору символів 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
Я шукав це рішення, проте немає необхідності у двох змінних BitSet. Просто трекера достатньо. Оновлено код циклу: for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
zambro

7

Читання відповіді Івана вище справді мені допомогло, хоча я б сформулював це дещо інакше.

<<У (1 << val)це біт - оператор зсуву. Він займає 1(який у двійковій формі представлений як 000000001, із стільки попередніх нулів, скільки вам подобається / виділяється пам'яттю) і зміщує його вліво на valпробіли. Оскільки ми припускаємо лише az і віднімаємо aкожен раз, кожна буква матиме значення 0-25, що буде індексом цієї ліворуч справа в checkerбулевому поданні цілого числа, оскільки ми будемо переміщувати 1ліворуч ліворуч checker val.

В кінці кожної перевірки ми бачимо |=оператора. Це об'єднує два двійкові числа, замінюючи всі 0's на 1', якщо 1існує в будь-якому операнді в цьому індексі. Тут це означає, що там, де 1існує (1 << val), це 1буде скопійовано, в checkerтой час як усі checkerіснуючі 1 з них будуть збережені.

Як ви, напевно, здогадуєтесь, 1тут функціонує як булевий прапор для істини. Коли ми перевіряємо, чи символ вже представлений у рядку, ми порівнюємо checker, що в даний момент є по суті масивом булевих прапорів ( 1значень) в індексах символів, які вже були представлені, з тим, що по суті є масивом булеві значення з 1прапором в індексі поточного символу.

&Оператор виконує цю перевірку. За аналогією з |=, то &оператор буде копіювати більше 1 тільки якщо обидва операнда мають 1за цим індексом. Таким чином, по суті, копіюватимуться лише прапори, які вже є у checkerцьому зображенні (1 << val). У цьому випадку це означає, що лише в тому випадку, якщо поточний персонаж уже представлений, буде присутній 1подарунок десь у результаті checker & (1 << val). І якщо a 1є результатом цієї операції де-небудь, то значення повернутого булевого значення є > 0, а метод повертає false.

Це, я здогадуюсь, чому бітові вектори також називають бітовими масивами . Оскільки, хоча вони не є типом даних масиву, вони можуть використовуватися аналогічно тому, як масиви використовуються для зберігання булевих прапорів.


1
Дуже корисно, дякую за вашу інформацію про Java.
Бачірі Тауфік Абдеррахман

4

Просте пояснення (з кодом JS нижче)

  • Ціла змінна на машинний код - це 32-бітний масив
  • Всі трохи розсудливі операції 32-bit
  • Вони є агностиком архітектури ОС / CPU або вибраної системи числення мови, наприклад, DEC64для JS.
  • Цей підхід пошуку дублювання схожий на зберігання символів у масиві розміром 32 де, ми встановлюємо 0thіндекс, якщо знаходимо aв рядку, 1stдля b& так далі.
  • Дублікат символу в рядку буде займати відповідний біт, або, у цьому випадку, встановити значення 1.
  • Іван уже пояснив : як працює цей обчислення індексу в цій попередній відповіді .

Підсумок операцій:

  • Виконання і операції між checkerі indexхарактеру
  • Внутрішні обидва є Int-32-Arrays
  • Це трохи розумна операція між цими 2.
  • Перевірте, чи ifбув вихід операції1
  • якщо output == 1
    • checkerМінлива має цей конкретний індекс-й біт набір в обох масивах
    • Таким чином, це дублікат.
  • якщо output == 0
    • Цей персонаж поки що не знайдено
    • Виконайте АБО операції між checker& indexхарактеру
    • Тим самим оновлення індексу-го біта до 1
    • Призначте вихід checker

Припущення:

  • Ми припустили, що отримаємо всі малі регістри
  • І цього розміру 32 досить
  • Таким чином, ми почали наш індекс підрахунок від 96 до якості опорної точки , враховуючи ASCii код aIS97

Нижче наведено вихідний код 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

Зауважте, що в JS, незважаючи на те, що цілі числа становлять 64 біти, операція з розумом завжди виконується на 32 бітах.

Приклад: Якщо рядок є 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

Дозволяє розбивати код за рядком.

int checker = 0; Ми ініціюємо перевірку, яка допоможе нам знайти повторювані значення.

int val = str.charAt (i) - 'a'; Ми отримуємо значення ASCII символу в 'i'-й позиції рядка і віднімаємо його зі значенням ASCII' a '. Оскільки припущення полягає в тому, що рядок є лише нижчими символами, кількість символів обмежена 26. Хесе, значення 'val' завжди буде> = 0.

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

шашка | = (1 << вал);

Зараз це складна частина. Розглянемо приклад із рядком "abcda". Це в ідеалі має повернути помилкове.

Для ітерації циклу 1:

Шашка: 00000000000000000000000000000000

val: 97-97 = 0

1 << 0: 00000000000000000000000000000001

checker & (1 << val): 00000000000000000000000000000000 не> 0

Звідси перевірка: 00000000000000000000000000000001

Для ітерації циклу 2:

Шашка: 00000000000000000000000000000001

val: 98-97 = 1

1 << 0: 00000000000000000000000000000010

checker & (1 << val): 00000000000000000000000000000000 не> 0

Звідси перевірка: 00000000000000000000000000000011

Для циклу ітерації 3:

Шашка: 00000000000000000000000000000011

val: 99-97 = 0

1 << 0: 00000000000000000000000000000100

checker & (1 << val): 00000000000000000000000000000000 не> 0

Звідси перевірка: 00000000000000000000000000000111

Для циклу ітерації 4:

Шашка: 00000000000000000000000000000111

val: 100-97 = 0

1 << 0: 00000000000000000000000000001000

checker & (1 << val): 00000000000000000000000000000000 не> 0

Звідси перевірка: 00000000000000000000000000001111

Для циклу ітерації 5:

Шашка: 00000000000000000000000000001111

val: 97-97 = 0

1 << 0: 00000000000000000000000000000001

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

Отже повернення хибне.


val: 99-97 = 0 має бути val: 99-97 = 2, а val: 100-97 = 0 має бути 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

Попередні публікації добре пояснюють, що робить блок коду, і я хочу додати своє просте рішення за допомогою структури даних BitSet Java:

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

Те, як я зрозумів за допомогою Javascript. Припускаючи введенняvar inputChar = "abca"; //find if inputChar has all unique characters

Давайте розпочнемо

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

Наведений вище рядок Знаходить двійкове значення першого символу в inputChar, яке є a , a = 97 в ascii, а потім перетворити 97 в двійкове стає 1100001 .

У Javascript Eg: "a".charCodeAt().toString(2) повертає 1100001

checker = 0 // двійкове 32-бітове представлення = 0000000000000000000000000

checker = 1100001 | checker; // шашка стає 1100001 (У 32-бітовому поданні вона стає 000000000 ..... 00001100001)

Але я хочу, щоб моя бітмаска ( int checker) встановила лише один біт, але шашка - 1100001

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

Тепер вище код стане у нагоді. Я просто віднімаю 97 завжди (ASCII val 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

Дозволяє використовувати valскидання

Рядок 5 та рядок 6 добре пояснено у відповідь @Ivan


0

Про всяк випадок, якщо хтось шукає котлін-еквівалент унікальних символів у рядку, використовуючи бітовий вектор

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
}

Посилання: https://www.programiz.com/kotlin-programming/bitwise

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.