Skip to content

Commit 3aac7a0

Browse files
author
Saiprashanth Pulisetti
committed
feat(bit_manipulation): add next_higher_same_ones (SNOOB) with doctests
1 parent 369788d commit 3aac7a0

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Next higher integer with the same number of set bits (SNOOB).
2+
author: @0xPrashanthSec
3+
4+
Given a non-negative integer n, return the next higher integer that has the same
5+
number of 1 bits in its binary representation. If no such number exists within
6+
Python's unbounded int range (practically always exists unless n is 0 or all
7+
ones packed at the most significant end for fixed-width), this implementation
8+
returns -1.
9+
10+
This is the classic SNOOB algorithm from "Hacker's Delight".
11+
12+
Reference: https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
13+
14+
>>> next_higher_same_ones(0b0011)
15+
5
16+
>>> bin(next_higher_same_ones(0b0011))
17+
'0b101'
18+
>>> bin(next_higher_same_ones(0b01101)) # 13 -> 14 (0b01110)
19+
'0b1110'
20+
>>> next_higher_same_ones(1)
21+
2
22+
>>> next_higher_same_ones(0) # no higher with same popcount
23+
-1
24+
>>> next_higher_same_ones(-5) # negative not allowed
25+
Traceback (most recent call last):
26+
...
27+
ValueError: n must be a non-negative integer
28+
"""
29+
from __future__ import annotations
30+
31+
32+
def next_higher_same_ones(n: int) -> int:
33+
"""Return the next higher integer with the same number of set bits as n.
34+
35+
:param n: Non-negative integer
36+
:return: Next higher integer with same popcount or -1 if none
37+
:raises ValueError: if n < 0
38+
"""
39+
if n < 0:
40+
raise ValueError("n must be a non-negative integer")
41+
if n == 0:
42+
return -1
43+
44+
# snoob algorithm
45+
# c = rightmost set bit
46+
c = n & -n
47+
# r = ripple carry: add c to n
48+
r = n + c
49+
if r == 0:
50+
return -1
51+
# ones = pattern of ones that moved from lower part
52+
ones = ((r ^ n) >> 2) // c
53+
return r | ones
54+
55+
56+
if __name__ == "__main__": # pragma: no cover
57+
import doctest
58+
59+
doctest.testmod()

0 commit comments

Comments
 (0)