Кой е най-ефективният начин за намиране на индекса на левия/десния най-незададен бит в Java?

Да приемем, че имаме int x = 371, което е в двоичен формат 101110011. Искам да намеря индекса на най-левия ненастроен бит (в този случай 7) и индекса на най-десния ненастроен бит (в този случай 2). Кой е най-ефективният начин да го направите?

Ето какво имам:

public class BitOperatons {

    public static int setBit(int x, int i) {
        int y = x | (1 << i);
        return y;
    }

    public static boolean isBitSet(int x, int i) {
        int y = setBit(0, i);
        return y == (x & y);
    }    

    public static int findLeftMostSetBit(int x) {
        for (int i = 31; i >= 0; i--) {
            if (isBitSet(x, i))
                return i;
        }
        return -1;
    }

    public static int findRightMostUnsetBit(int x) {
        for (int i = 0; i <= 31; i++) {
            if (! isBitSet(x, i))
                return i;
        }
        return -1;
    }

    public static int findLeftMostUnsetBit(int x) {
        int k = findLeftMostSetBit(x);
        for (int i = k; i >= 0; i--) {
            if (! isBitSet(x, i))
                return i;
        }
        return -1;
    }

    public static1+ void main(String[] args) {
        int x = 
            (1 << 0) |
            (1 << 1) |
            (1 << 4) |
            (1 << 5) |
            (1 << 6) |
            (1 << 8);
        System.out.println(findLeftMostUnsetBit(x));
        System.out.println(findRightMostUnsetBit(x));
    }

}

Ако не греша, текущата ми реализация отнема линейно време. Можем ли да се справим по-добре?


person MarcoS    schedule 27.06.2011    source източник
comment
Не е възможно да се направи по-добре от линейно.   -  person toto2    schedule 27.06.2011
comment
@toto, това не е вярно, забравих, вижте Hacker's Delight 5-3 (например) (водещи нули, което е част от Integer.class)   -  person bestsss    schedule 27.06.2011
comment
@bestsss, добре, ако имаш предвид машинни инструкции, както е предложено от flolo. Ако знаете за подлинеен алго, бих искал да знам.   -  person toto2    schedule 27.06.2011
comment
@toto, не, това не е инструкция от асемблер или процесор. това е двоично търсене вместо линейно :). Сериозно се опитайте да намерите копие на Hacker's Delight (книга) и го проверете. Ще публикувам как да направя част с водещи нули. Сигурен съм, че дори SO ще има някои неща.   -  person bestsss    schedule 27.06.2011
comment
@bestsss Да, точно сега си мислех за това. Исках да го напиша като отговор. Благодаря. Все още е по-добре да използвате Java API. (Редактиране: току-що проверих кода на Integer.numberOfTrailingZeros и това е двоично търсене.)   -  person toto2    schedule 27.06.2011
comment
graphics.stanford.edu/~seander/bithacks.html е интересна статия за този предмет. Не всички от представените алгоритми обаче са подходящи за Java.   -  person Paul Cager    schedule 27.06.2011


Отговори (2)


В класа Integer има налични методи.

Integer.numberOfTrailingZeros(Integer.lowestOneBit(~yourValue)) би го направил за най-ниския ненастроен бит, за най-високия е малко по-сложно, тъй като първо трябва да определим най-високия зададен бит.

int leadingZeroBits = Integer.numberOfLeadingZeros(Integer.highestOneBit(yourValue));
result = Integer.
       numberOfTrailingZeros(Integer.highestOneBit((~yourValue)<<leadingZeroBits)
      -leadingZeroBits;`

Трябва да го направя за най-високия ненастроен бит.

И това може да е по-бързо от линейното време, тъй като процесорите често имат машинни инструкции за бързо определяне на водещия/крайния нулев бит (но не съм сигурен дали vm ги използва РЕДАКТИРАНЕ: вече съм сигурен ;-).

РЕДАКТИРАНЕ: Изглежда са добавили използването на asm вътрешности за водещи/следващи нули в 1.6.0_18, ID 6823354

person flolo    schedule 27.06.2011
comment
хм, наистина присъщите изглеждат като инструкция на процесора, където е възможно. - person bestsss; 27.06.2011

По-долу е изходният код на Integer.numberOfLeadingZeros. Както беше посочено, взето е от HD (Hacker's Delight от Henry S. Warren, Jr)

Основната идея е да се използва двоично търсене, вместо да се повтарят битовете един по един. Моля, проверете книгата, ако се интересувате от превъртане на битове. Това е прекрасно произведение на изкуството.

public static int numberOfLeadingZeros(int i) {
    // HD, Figure 5-6
    if (i == 0)
        return 32;
    int n = 1;
    if (i >>> 16 == 0) { n += 16; i <<= 16; }
    if (i >>> 24 == 0) { n +=  8; i <<=  8; }
    if (i >>> 28 == 0) { n +=  4; i <<=  4; }
    if (i >>> 30 == 0) { n +=  2; i <<=  2; }
    n -= i >>> 31;
    return n;
}
person Community    schedule 27.06.2011
comment
Внедреният код в библиотеката Sun (Oracle) е малко по-различен, но еквивалентен. - person toto2; 27.06.2011
comment
@bestsss: numberOfLeadingZeros е по-ефективна реализация на моето findLeftMostSetBit. Не виждам обаче как да го използвам, за да отговоря на моя въпрос, който е намирането на индекса на най-левия/десния ненастроен бит. Ако имаме 0000000000000000000000101110011, тогава искам да намеря индекса на най-левия ненастроен бит, който е 7, и индекса на най-десния ненастроен бит, който е 2. Как бихте направили това? (благодаря ви, че препоръчахте Hacker's Delight от Henry S. Warren, Jr: изглежда много интересна книга и ще я сложа в списъка си с желания :) ) - person MarcoS; 28.06.2011
comment
@MarcoS, погледни отговора на flolo за малко прозрение, този отговор беше насочен към забележката на toto в коментарите, най-вече. Коментарът на Paul Cager също съдържа много полезна колекция от C (може да се пренесе и в java) битови хакове. - person bestsss; 28.06.2011
comment
@bestsss: Разбирам! ... така че трябва да избера и твоя пост, и този на flolo като отговори, но не мога. Така че ще избера flolo като отговор, но все пак гласувайте за вашия. - person MarcoS; 28.06.2011
comment
@MarcoS, поставих го като уики на общността с причина. И все пак въпросът за HD, ако се интересувате от въртене на битове, това е основен източник на идеи; танцът на битовете ме очароваше, когато бях дете (правех асемблер) - person bestsss; 28.06.2011