Inkrementieren von 'maskierten' Bitsets

80

Ich bin gerade dabei, einen Baum-Enumerator zu schreiben, bei dem ich auf folgendes Problem gestoßen bin:

Ich betrachte maskierte Bitsets, dh Bitsets, bei denen die gesetzten Bits eine Teilmenge einer Maske sind, dh 0000101mit Maske 1010101. Was ich erreichen möchte, ist das Inkrementieren des Bitsets, jedoch nur in Bezug auf die maskierten Bits. In diesem Beispiel wäre das Ergebnis 0010000. Um es etwas klarer zu machen, extrahieren Sie nur die maskierten Bits, dh 0011erhöhen Sie sie auf 0100und verteilen Sie sie erneut auf die Maskenbits 0010000.

Sieht jemand einen effizienten Weg, dies zu tun, ohne die Operation von Hand mit einer Kombination aus Bitscans und Präfixmasken zu implementieren?

Tobias Ribizel
quelle

Antworten:

123

Füllen Sie einfach die Nichtmaskenbits mit Einsen, damit sie Übertrag übertragen:

// increments x on bits belonging to mask
x = ((x | ~mask) + 1) & mask;
zch
quelle
11
Das ist ein schöner Trick ... Fast die Magie, von der ich sagte, dass es keine gibt :)
Eugene Sh.
8
@ EugeneSh. Glaube niemals, dass es nicht so ist.
Zch
23
Vermutlich nicht wichtig für OP, da sie akzeptiert haben, aber es sollte vielleicht beachtet werden, dass dies die Nichtmaskenbits auf Null setzt. Wenn sie wurden an anderer Stelle benötigt werden , müßten Sie vorsichtiger sein ersetzen x. Möglicherweise x = (x & ~mask) | (((x | ~mask) + 1) & mask);.
TripeHound
2
@TripeHound Wenn sie nicht benötigt würden, was wäre der Sinn, überhaupt eine Bitmaske zu verwenden?
Irgendwann mit dem
1
@someonewithpc Nicht sicher, was Sie sagen / fragen wollen. Ich weiß nicht, warum das OP einen nicht benachbarten Satz von Bits inkrementieren muss, daher weiß ich nicht, ob die anderen Bits im ursprünglichen Wert von Bedeutung sind oder nicht. ZB wenn der ursprüngliche Wert wäre 0101101(zB .1.1.0.in den Nicht-Masken-Bits und 0.0.1.1im "Zähler"), würden sie benötigen 0111000 (einen neuen "Zähler" 0.1.0.0während der Aufbewahrung .1.1.0.) oder ist einfach 0010000akzeptabel. Diese Antwort (und wahrscheinlich andere, obwohl ich nicht überprüft habe) geben letztere; Meine Version sollte die erstere geben, wenn dies erforderlich ist.
TripeHound
20

Dies ist zwar im Vergleich zur akzeptierten Antwort nicht intuitiv, funktioniert jedoch in nur drei Schritten:

x = -(x ^ mask) & mask;

Dies kann wie von zch vorgeschlagen überprüft werden:

  -(x ^ mask)
= ~(x ^ mask) + 1  // assuming 2's complement
= (x ^ ~mask) + 1
= (x | ~mask) + 1  // since x and ~mask have disjoint set bits

Dann entspricht es der akzeptierten Antwort.

nglee
quelle
2
Die Antwort von zch ist sehr intuitiv, ich kann sofort sehen, dass sie aufgrund seiner klaren Erklärung richtig ist. Was ist die Logik dieser Antwort? Wie funktioniert diese Formel, um den gewünschten Effekt zu erzielen? Ich bin neugierig auf den Entdeckungsprozess, die Art der Einsicht hier.
FooF
Ich denke, Ihre Überprüfung wäre viel einfacher, wenn Sie nur beweisen würden, -(x ^ mask) == (x | ~mask) + 1wann immer x eine Teilmenge der Maske ist, und dann auf meine Antwort verweisen würden.
Zch
5
-(x^mask) == ~((x ^ mask) - 1) == ~(x ^ mask) + 1 == (x ^ ~mask) + 1 == (x | ~mask) + 1. Die letzte Gleichung gilt, weil Bitsätze disjunkt sind, andere sind immer wahr (zumindest in 2-Komplement).
Zch
1
Diejenigen, die neugierig auf die Schritte sind, die ich unternommen habe, um diese Antwort abzuleiten, können auf diese Seite verweisen .
Nglee
5
Vielleicht ist es erwähnenswert, dass diese nicht das gleiche optimieren, was häufig für Leute relevant ist, die Bit-Twiddling betreiben: godbolt.org/g/7VWXas - obwohl das, was tatsächlich kürzer ist, vom Compiler abzuhängen scheint. Keine Ahnung, welches schneller wäre oder ob der Unterschied signifikant ist.
Leushenko
7

Wenn die Reihenfolge der Iteration nicht so wichtig ist und eine Dekrementierungsoperation Ihren Anforderungen entspricht, können nur zwei Operationen verwendet werden:

Lass uns beginnen mit

x = mask

und vorherigen Wert mit erhalten

x = (x - 1) & mask

x - 1part ändert das letzte Nicht-Null-Bit auf Null und setzt alle weniger signifikanten Bits auf 1. Dann lässt der & maskTeil nur Maskenbits zwischen ihnen.

Tal
quelle
2 ops, nett. Ich würde jedoch argumentieren, dass es der gleiche Ansatz ist, nur Kredite durch Nullen zu verbreiten, anstatt Einsen durchzutragen.
Zch
@zch, das stimmt, danke. Ich werde die Antwort
umformulieren
Funktioniert nur, wenn x mit allen nicht maskierten Bits beginnt.
Jasen
@Jasen, klar. Es ist jedoch nicht schwierig, diese Nichtmaskenbits zu setzen. Und andere Antworten haben das gleiche Problem.
DAle