bitarray 3.12.2__cp313-cp313-pyemscripten_2025_0_wasm32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitarray/__init__.py +66 -0
- bitarray/__init__.pyi +176 -0
- bitarray/_bitarray.cpython-313-wasm32-emscripten.so +0 -0
- bitarray/_util.cpython-313-wasm32-emscripten.so +0 -0
- bitarray/bitarray.h +357 -0
- bitarray/bitfields.py +447 -0
- bitarray/py.typed +0 -0
- bitarray/pythoncapi_compat.h +3138 -0
- bitarray/test_281.pickle +0 -0
- bitarray/test_bitarray.py +5788 -0
- bitarray/test_bitfields.py +721 -0
- bitarray/test_free_threading.py +840 -0
- bitarray/test_util.py +3166 -0
- bitarray/util.py +694 -0
- bitarray/util.pyi +86 -0
- bitarray-3.12.2.dist-info/METADATA +1134 -0
- bitarray-3.12.2.dist-info/RECORD +20 -0
- bitarray-3.12.2.dist-info/WHEEL +5 -0
- bitarray-3.12.2.dist-info/licenses/LICENSE +46 -0
- bitarray-3.12.2.dist-info/top_level.txt +1 -0
bitarray/util.py
ADDED
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
# Copyright (c) 2019 - 2026, Ilan Schnell; All Rights Reserved
|
|
2
|
+
# bitarray is published under the PSF license.
|
|
3
|
+
#
|
|
4
|
+
# Author: Ilan Schnell
|
|
5
|
+
"""
|
|
6
|
+
Useful utilities for working with bitarrays.
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import math
|
|
11
|
+
import random
|
|
12
|
+
import operator
|
|
13
|
+
import itertools
|
|
14
|
+
|
|
15
|
+
from bitarray import bitarray, frozenbitarray, bits2bytes
|
|
16
|
+
|
|
17
|
+
from bitarray._util import (
|
|
18
|
+
zeros, ones, count_n, parity, xor_indices,
|
|
19
|
+
count_and, count_or, count_xor, any_and, subset,
|
|
20
|
+
correspond_all, byteswap,
|
|
21
|
+
serialize, deserialize,
|
|
22
|
+
ba2hex, hex2ba,
|
|
23
|
+
ba2base, base2ba,
|
|
24
|
+
sc_encode, sc_decode,
|
|
25
|
+
rl_encode, rl_decode,
|
|
26
|
+
vl_encode, vl_decode,
|
|
27
|
+
canonical_decode,
|
|
28
|
+
)
|
|
29
|
+
import bitarray._util as _util
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
'zeros', 'ones', 'urandom', 'random_k', 'random_p', 'gen_primes',
|
|
33
|
+
'pprint', 'strip', 'count_n',
|
|
34
|
+
'parity', 'sum_indices', 'xor_indices',
|
|
35
|
+
'count_and', 'count_or', 'count_xor', 'any_and', 'subset',
|
|
36
|
+
'correspond_all', 'byteswap', 'intervals',
|
|
37
|
+
'ba2hex', 'hex2ba',
|
|
38
|
+
'ba2base', 'base2ba',
|
|
39
|
+
'ba2int', 'int2ba',
|
|
40
|
+
'serialize', 'deserialize',
|
|
41
|
+
'sc_encode', 'sc_decode', 'sc_stat',
|
|
42
|
+
'rl_encode', 'rl_decode',
|
|
43
|
+
'vl_encode', 'vl_decode',
|
|
44
|
+
'huffman_code', 'canonical_huffman', 'canonical_decode',
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def urandom(__n, endian=None):
|
|
49
|
+
"""urandom(n, /, endian=None) -> bitarray
|
|
50
|
+
|
|
51
|
+
Return random bitarray of length `n` (uses `os.urandom()`).
|
|
52
|
+
"""
|
|
53
|
+
a = bitarray(os.urandom(bits2bytes(__n)), endian)
|
|
54
|
+
del a[__n:]
|
|
55
|
+
return a
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def random_k(__n, k, endian=None):
|
|
59
|
+
"""random_k(n, /, k, endian=None) -> bitarray
|
|
60
|
+
|
|
61
|
+
Return (pseudo-) random bitarray of length `n` with `k` elements
|
|
62
|
+
set to one. Mathematically equivalent to setting (in a bitarray of
|
|
63
|
+
length `n`) all bits at indices `random.sample(range(n), k)` to one.
|
|
64
|
+
The random bitarrays are reproducible when calling Python's `random.seed()`
|
|
65
|
+
with a specific seed value.
|
|
66
|
+
"""
|
|
67
|
+
r = _Random(__n, endian)
|
|
68
|
+
k = operator.index(k)
|
|
69
|
+
return r.random_k(k)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def random_p(__n, p=0.5, endian=None):
|
|
73
|
+
"""random_p(n, /, p=0.5, endian=None) -> bitarray
|
|
74
|
+
|
|
75
|
+
Return (pseudo-) random bitarray of length `n`, where each bit has
|
|
76
|
+
probability `p` of being one (independent of any other bits). Mathematically
|
|
77
|
+
equivalent to `bitarray((random() < p for _ in range(n)), endian)`, but much
|
|
78
|
+
faster for large `n`. The random bitarrays are reproducible when calling
|
|
79
|
+
Python's `random.seed()` with a specific seed value.
|
|
80
|
+
|
|
81
|
+
This function requires Python 3.12 or higher, as it depends on the standard
|
|
82
|
+
library function `random.binomialvariate()`. Raises `NotImplementedError`
|
|
83
|
+
when Python version is too low.
|
|
84
|
+
"""
|
|
85
|
+
if sys.version_info[:2] < (3, 12):
|
|
86
|
+
raise NotImplementedError("bitarray.util.random_p() requires "
|
|
87
|
+
"Python 3.12 or higher")
|
|
88
|
+
r = _Random(__n, endian)
|
|
89
|
+
return r.random_p(p)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _Random:
|
|
93
|
+
|
|
94
|
+
# To better understand how the algorithm works, see ./doc/random_p.rst
|
|
95
|
+
# See also, Internal_Tests and VerificationTests in devel/test_random.py
|
|
96
|
+
|
|
97
|
+
# maximal number of calls to .random_half() in .combine_half()
|
|
98
|
+
M = 8
|
|
99
|
+
|
|
100
|
+
# number of resulting probability intervals
|
|
101
|
+
K = 1 << M
|
|
102
|
+
|
|
103
|
+
# limit for setting individual bits randomly
|
|
104
|
+
SMALL_P = 0.01
|
|
105
|
+
|
|
106
|
+
def __init__(self, n=0, endian=None):
|
|
107
|
+
self.n = n
|
|
108
|
+
self.nbytes = bits2bytes(n)
|
|
109
|
+
self.endian = endian
|
|
110
|
+
|
|
111
|
+
def random_half(self):
|
|
112
|
+
"""
|
|
113
|
+
Return bitarray with each bit having probability p = 1/2 of being 1.
|
|
114
|
+
"""
|
|
115
|
+
nbytes = self.nbytes
|
|
116
|
+
# Use random module function for reproducibility (not urandom()).
|
|
117
|
+
# random.randbytes() can be used once Python 3.9 is the minimum
|
|
118
|
+
# supported version.
|
|
119
|
+
b = random.getrandbits(8 * nbytes).to_bytes(nbytes, 'little')
|
|
120
|
+
a = bitarray(b, self.endian)
|
|
121
|
+
del a[self.n:]
|
|
122
|
+
return a
|
|
123
|
+
|
|
124
|
+
def op_seq(self, i):
|
|
125
|
+
"""
|
|
126
|
+
Return bitarray containing operator sequence.
|
|
127
|
+
Each item represents a bitwise operation: 0: AND 1: OR
|
|
128
|
+
After applying the sequence (see .combine_half()), we
|
|
129
|
+
obtain a bitarray with probability q = i / K
|
|
130
|
+
"""
|
|
131
|
+
if not 0 < i < self.K:
|
|
132
|
+
raise ValueError("0 < i < %d, got i = %d" % (self.K, i))
|
|
133
|
+
|
|
134
|
+
# sequence of &, | operations - least significant operations first
|
|
135
|
+
a = bitarray(i.to_bytes(2, byteorder="little"), "little")
|
|
136
|
+
return a[a.index(1) + 1 : self.M]
|
|
137
|
+
|
|
138
|
+
def combine_half(self, seq):
|
|
139
|
+
"""
|
|
140
|
+
Combine random bitarrays with probability 1/2
|
|
141
|
+
according to given operator sequence.
|
|
142
|
+
"""
|
|
143
|
+
a = self.random_half()
|
|
144
|
+
for k in seq:
|
|
145
|
+
if k:
|
|
146
|
+
a |= self.random_half()
|
|
147
|
+
else:
|
|
148
|
+
a &= self.random_half()
|
|
149
|
+
return a
|
|
150
|
+
|
|
151
|
+
def adjust_count(self, a, increase):
|
|
152
|
+
"""
|
|
153
|
+
Adjust the number of set bits in bitarray `a` by `increase`.
|
|
154
|
+
The length of `a` must equal `self.n`, and `increase` must be in the
|
|
155
|
+
range `-a.count() <= increase <= self.n - a.count()`. The bitarray
|
|
156
|
+
is modified in-place at positions selected uniformly at random.
|
|
157
|
+
"""
|
|
158
|
+
randrange = random.randrange
|
|
159
|
+
n = self.n
|
|
160
|
+
|
|
161
|
+
if increase > 0: # not enough 1 bits - increase count
|
|
162
|
+
for _ in range(increase):
|
|
163
|
+
i = randrange(n)
|
|
164
|
+
while a[i]:
|
|
165
|
+
i = randrange(n)
|
|
166
|
+
a[i] = 1
|
|
167
|
+
elif increase < 0: # too many 1 bits - decrease count
|
|
168
|
+
for _ in range(-increase):
|
|
169
|
+
i = randrange(n)
|
|
170
|
+
while not a[i]:
|
|
171
|
+
i = randrange(n)
|
|
172
|
+
a[i] = 0
|
|
173
|
+
|
|
174
|
+
def random_k(self, k):
|
|
175
|
+
n = self.n
|
|
176
|
+
# error check inputs and handle edge cases
|
|
177
|
+
if k <= 0 or k >= n:
|
|
178
|
+
if k == 0:
|
|
179
|
+
return zeros(n, self.endian)
|
|
180
|
+
if k == n:
|
|
181
|
+
return ones(n, self.endian)
|
|
182
|
+
raise ValueError("k must be in range 0 <= k <= n, got %s" % k)
|
|
183
|
+
|
|
184
|
+
# exploit symmetry to establish: k <= n // 2
|
|
185
|
+
if k > n // 2:
|
|
186
|
+
a = self.random_k(n - k)
|
|
187
|
+
a.invert() # use in-place to avoid copying
|
|
188
|
+
return a
|
|
189
|
+
|
|
190
|
+
# decide on sequence, see VerificationTests devel/test_random.py
|
|
191
|
+
if k < 16 or k * self.K < 3 * n:
|
|
192
|
+
i = 0
|
|
193
|
+
else:
|
|
194
|
+
p = k / n # p <= 0.5
|
|
195
|
+
p -= (0.2 - 0.4 * p) / math.sqrt(n)
|
|
196
|
+
i = int(p * (self.K + 1))
|
|
197
|
+
|
|
198
|
+
# combine random bitarrays using bitwise AND and OR operations
|
|
199
|
+
if i < 3:
|
|
200
|
+
a = zeros(n, self.endian)
|
|
201
|
+
self.adjust_count(a, k)
|
|
202
|
+
else:
|
|
203
|
+
a = self.combine_half(self.op_seq(i))
|
|
204
|
+
self.adjust_count(a, k - a.count())
|
|
205
|
+
|
|
206
|
+
return a
|
|
207
|
+
|
|
208
|
+
def random_p(self, p):
|
|
209
|
+
n = self.n
|
|
210
|
+
endian = self.endian
|
|
211
|
+
# error check inputs and handle edge cases
|
|
212
|
+
if p <= 0.0 or p == 0.5 or p >= 1.0 or not math.isfinite(p):
|
|
213
|
+
if p == 0.0:
|
|
214
|
+
return zeros(n, endian)
|
|
215
|
+
if p == 0.5:
|
|
216
|
+
return self.random_half()
|
|
217
|
+
if p == 1.0:
|
|
218
|
+
return ones(n, endian)
|
|
219
|
+
raise ValueError("p must be in range 0.0 <= p <= 1.0, got %s" % p)
|
|
220
|
+
|
|
221
|
+
# for small n, use literal definition
|
|
222
|
+
if n < 16:
|
|
223
|
+
return bitarray((random.random() < p for _ in range(n)), endian)
|
|
224
|
+
|
|
225
|
+
# exploit symmetry to establish: p < 0.5
|
|
226
|
+
if p > 0.5:
|
|
227
|
+
a = self.random_p(1.0 - p)
|
|
228
|
+
a.invert() # use in-place to avoid copying
|
|
229
|
+
return a
|
|
230
|
+
|
|
231
|
+
# for small p, set randomly individual bits
|
|
232
|
+
if p < self.SMALL_P:
|
|
233
|
+
a = zeros(n, endian)
|
|
234
|
+
self.adjust_count(a, random.binomialvariate(n, p))
|
|
235
|
+
return a
|
|
236
|
+
|
|
237
|
+
# calculate operator sequence
|
|
238
|
+
i = int(p * self.K)
|
|
239
|
+
if p * (self.K + 1) > i + 1: # see devel/test_random.py
|
|
240
|
+
i += 1
|
|
241
|
+
seq = self.op_seq(i)
|
|
242
|
+
q = i / self.K
|
|
243
|
+
|
|
244
|
+
# when n is small compared to number of operations, also use literal
|
|
245
|
+
if n < 100 and self.nbytes <= len(seq) + 3 * (q != p):
|
|
246
|
+
return bitarray((random.random() < p for _ in range(n)), endian)
|
|
247
|
+
|
|
248
|
+
# combine random bitarrays using bitwise AND and OR operations
|
|
249
|
+
a = self.combine_half(seq)
|
|
250
|
+
if q < p:
|
|
251
|
+
x = (p - q) / (1.0 - q)
|
|
252
|
+
a |= self.random_p(x)
|
|
253
|
+
elif q > p:
|
|
254
|
+
x = p / q
|
|
255
|
+
a &= self.random_p(x)
|
|
256
|
+
|
|
257
|
+
return a
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def gen_primes(__n, endian=None, odd=False):
|
|
261
|
+
"""gen_primes(n, /, endian=None, odd=False) -> bitarray
|
|
262
|
+
|
|
263
|
+
Generate a bitarray of length `n` in which active indices are prime numbers.
|
|
264
|
+
By default (`odd=False`), active indices correspond to prime numbers directly.
|
|
265
|
+
When `odd=True`, only odd prime numbers are represented in the resulting
|
|
266
|
+
bitarray `a`, and `a[i]` corresponds to `2*i+1` being prime or not.
|
|
267
|
+
"""
|
|
268
|
+
n = operator.index(__n)
|
|
269
|
+
if n < 0:
|
|
270
|
+
raise ValueError("bitarray length must be >= 0")
|
|
271
|
+
|
|
272
|
+
if odd:
|
|
273
|
+
a = ones(105, endian) # 105 = 3 * 5 * 7
|
|
274
|
+
a[1::3] = 0
|
|
275
|
+
a[2::5] = 0
|
|
276
|
+
a[3::7] = 0
|
|
277
|
+
f = "01110110"
|
|
278
|
+
else:
|
|
279
|
+
a = ones(210, endian) # 210 = 2 * 3 * 5 * 7
|
|
280
|
+
for i in 2, 3, 5, 7:
|
|
281
|
+
a[::i] = 0
|
|
282
|
+
f = "00110101"
|
|
283
|
+
|
|
284
|
+
# repeating the array many times is faster than setting the multiples
|
|
285
|
+
# of the low primes to 0
|
|
286
|
+
a *= (n + len(a) - 1) // len(a)
|
|
287
|
+
a[:8] = bitarray(f, endian)
|
|
288
|
+
del a[n:]
|
|
289
|
+
# perform sieve starting at 11
|
|
290
|
+
if odd:
|
|
291
|
+
for i in a.search(1, 5, int(math.sqrt(n // 2) + 1.0)): # 11//2 = 5
|
|
292
|
+
j = 2 * i + 1
|
|
293
|
+
a[(j * j) // 2 :: j] = 0
|
|
294
|
+
else:
|
|
295
|
+
# i*i is always odd, and even bits are already set to 0: use step 2*i
|
|
296
|
+
for i in a.search(1, 11, int(math.sqrt(n) + 1.0)):
|
|
297
|
+
a[i * i :: 2 * i] = 0
|
|
298
|
+
return a
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def sum_indices(__a, mode=1):
|
|
302
|
+
"""sum_indices(a, /, mode=1) -> int
|
|
303
|
+
|
|
304
|
+
Return sum of indices of all active bits in bitarray `a`.
|
|
305
|
+
Equivalent to `sum(i for i, v in enumerate(a) if v)`.
|
|
306
|
+
`mode=2` sums square of indices.
|
|
307
|
+
"""
|
|
308
|
+
# For details see: devel/test_sum_indices.py
|
|
309
|
+
n = 1 << 19 # block size 512 Kbits
|
|
310
|
+
if len(__a) <= n: # shortcut for single block
|
|
311
|
+
return _util.ssqi(__a, mode)
|
|
312
|
+
|
|
313
|
+
if not isinstance(__a, bitarray):
|
|
314
|
+
raise TypeError("bitarray expected, got '%s'" % type(__a).__name__)
|
|
315
|
+
mode = operator.index(mode)
|
|
316
|
+
if mode not in (1, 2):
|
|
317
|
+
raise ValueError("unexpected mode %r" % mode)
|
|
318
|
+
|
|
319
|
+
# Constants
|
|
320
|
+
m = n // 8 # block size in bytes
|
|
321
|
+
o1 = n * (n - 1) // 2
|
|
322
|
+
o2 = o1 * (2 * n - 1) // 3
|
|
323
|
+
|
|
324
|
+
nblocks = (len(__a) + n - 1) // n
|
|
325
|
+
padbits = __a.padbits
|
|
326
|
+
sm = 0
|
|
327
|
+
for i in range(nblocks):
|
|
328
|
+
# use memoryview to avoid copying memory
|
|
329
|
+
v = memoryview(__a)[i * m : (i + 1) * m]
|
|
330
|
+
block = bitarray(None, __a.endian, buffer=v)
|
|
331
|
+
if padbits and i == nblocks - 1:
|
|
332
|
+
if block.readonly:
|
|
333
|
+
block = bitarray(block)
|
|
334
|
+
block[-padbits:] = 0
|
|
335
|
+
|
|
336
|
+
k = block.count()
|
|
337
|
+
if k:
|
|
338
|
+
y = n * i
|
|
339
|
+
z1 = o1 if k == n else _util.ssqi(block)
|
|
340
|
+
if mode == 1:
|
|
341
|
+
sm += k * y + z1
|
|
342
|
+
else:
|
|
343
|
+
z2 = o2 if k == n else _util.ssqi(block, 2)
|
|
344
|
+
sm += (k * y + 2 * z1) * y + z2
|
|
345
|
+
|
|
346
|
+
return sm
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def pprint(__a, stream=None, group=8, indent=4, width=80):
|
|
350
|
+
"""pprint(bitarray, /, stream=None, group=8, indent=4, width=80)
|
|
351
|
+
|
|
352
|
+
Pretty-print bitarray object to `stream`, defaults to `sys.stdout`.
|
|
353
|
+
By default, bits are grouped in bytes (8 bits), and 64 bits per line.
|
|
354
|
+
Non-bitarray objects are printed using `pprint.pprint()`.
|
|
355
|
+
"""
|
|
356
|
+
if stream is None:
|
|
357
|
+
stream = sys.stdout
|
|
358
|
+
|
|
359
|
+
if not isinstance(__a, bitarray):
|
|
360
|
+
import pprint as _pprint
|
|
361
|
+
_pprint.pprint(__a, stream=stream, indent=indent, width=width)
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
group = operator.index(group)
|
|
365
|
+
if group < 1:
|
|
366
|
+
raise ValueError('group must be >= 1')
|
|
367
|
+
indent = operator.index(indent)
|
|
368
|
+
if indent < 0:
|
|
369
|
+
raise ValueError('indent must be >= 0')
|
|
370
|
+
width = operator.index(width)
|
|
371
|
+
if width <= indent:
|
|
372
|
+
raise ValueError('width must be > %d (indent)' % indent)
|
|
373
|
+
|
|
374
|
+
gpl = (width - indent) // (group + 1) # groups per line
|
|
375
|
+
epl = group * gpl # elements per line
|
|
376
|
+
if epl == 0:
|
|
377
|
+
epl = max(1, width - indent - 2)
|
|
378
|
+
type_name = type(__a).__name__
|
|
379
|
+
n = len(__a)
|
|
380
|
+
multiline = n > 0 and ( # here 4 is len("('')")
|
|
381
|
+
len(type_name) + 4 + n + (n - 1) // group >= width
|
|
382
|
+
)
|
|
383
|
+
quotes = "'''" if multiline else ("'" if __a else "")
|
|
384
|
+
|
|
385
|
+
stream.write("%s(%s" % (type_name, quotes))
|
|
386
|
+
for i, b in enumerate(__a):
|
|
387
|
+
if multiline and i % epl == 0:
|
|
388
|
+
stream.write('\n%s' % (indent * ' '))
|
|
389
|
+
if i % group == 0 and i % epl != 0:
|
|
390
|
+
stream.write(' ')
|
|
391
|
+
stream.write(str(b))
|
|
392
|
+
|
|
393
|
+
if multiline:
|
|
394
|
+
stream.write('\n')
|
|
395
|
+
|
|
396
|
+
stream.write("%s)\n" % quotes)
|
|
397
|
+
stream.flush()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def strip(__a, mode='right'):
|
|
401
|
+
"""strip(bitarray, /, mode='right') -> bitarray
|
|
402
|
+
|
|
403
|
+
Return a new bitarray with zeros stripped from left, right or both ends.
|
|
404
|
+
Allowed values for mode are the strings: `left`, `right`, `both`
|
|
405
|
+
"""
|
|
406
|
+
if not isinstance(mode, str):
|
|
407
|
+
raise TypeError("str expected for mode, got '%s'" %
|
|
408
|
+
type(mode).__name__)
|
|
409
|
+
if mode not in ('left', 'right', 'both'):
|
|
410
|
+
raise ValueError("mode must be 'left', 'right' or 'both', got %r" %
|
|
411
|
+
mode)
|
|
412
|
+
|
|
413
|
+
start = None if mode == 'right' else __a.find(1)
|
|
414
|
+
if start == -1:
|
|
415
|
+
return __a[:0]
|
|
416
|
+
stop = None if mode == 'left' else __a.find(1, right=1) + 1
|
|
417
|
+
return __a[start:stop]
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def intervals(__a):
|
|
421
|
+
"""intervals(bitarray, /) -> iterator
|
|
422
|
+
|
|
423
|
+
Compute all uninterrupted intervals of 1s and 0s, and return an
|
|
424
|
+
iterator over tuples `(value, start, stop)`. The intervals are guaranteed
|
|
425
|
+
to be in order, and their size is always non-zero (`stop - start > 0`).
|
|
426
|
+
"""
|
|
427
|
+
n = len(__a)
|
|
428
|
+
if n == 0:
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
value = __a[0] # value of current interval
|
|
432
|
+
start = 0
|
|
433
|
+
while start < n:
|
|
434
|
+
stop = __a.find(not value, start)
|
|
435
|
+
if stop < 0:
|
|
436
|
+
stop = n
|
|
437
|
+
yield int(value), start, stop
|
|
438
|
+
start = stop
|
|
439
|
+
value = not value # next interval has opposite value
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def ba2int(__a, signed=False):
|
|
443
|
+
"""ba2int(bitarray, /, signed=False) -> int
|
|
444
|
+
|
|
445
|
+
Convert the given bitarray to an integer.
|
|
446
|
+
The bit-endianness of the bitarray is respected.
|
|
447
|
+
`signed` indicates whether two's complement is used to represent the integer.
|
|
448
|
+
"""
|
|
449
|
+
if not isinstance(__a, bitarray):
|
|
450
|
+
raise TypeError("bitarray expected, got '%s'" % type(__a).__name__)
|
|
451
|
+
|
|
452
|
+
length = len(__a)
|
|
453
|
+
if length == 0:
|
|
454
|
+
raise ValueError("non-empty bitarray expected")
|
|
455
|
+
|
|
456
|
+
res = int.from_bytes(__a.tobytes(), byteorder=__a.endian)
|
|
457
|
+
|
|
458
|
+
# Big-endian pad bits are on the right and must be shifted away.
|
|
459
|
+
if __a.endian == 'big':
|
|
460
|
+
res >>= __a.padbits
|
|
461
|
+
|
|
462
|
+
if signed and res & (1 << (length - 1)):
|
|
463
|
+
res -= 1 << length
|
|
464
|
+
|
|
465
|
+
return res
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def int2ba(__i, length=None, endian=None, signed=False):
|
|
469
|
+
"""int2ba(int, /, length=None, endian=None, signed=False) -> bitarray
|
|
470
|
+
|
|
471
|
+
Convert the given integer to a bitarray (with given bit-endianness,
|
|
472
|
+
and no leading (big-endian) / trailing (little-endian) zeros), unless
|
|
473
|
+
the `length` of the bitarray is provided. An `OverflowError` is raised
|
|
474
|
+
if the integer is not representable with the given number of bits.
|
|
475
|
+
`signed` determines whether two's complement is used to represent the integer,
|
|
476
|
+
and requires `length` to be provided.
|
|
477
|
+
"""
|
|
478
|
+
i = operator.index(__i)
|
|
479
|
+
if length is not None:
|
|
480
|
+
length = operator.index(length)
|
|
481
|
+
if length <= 0:
|
|
482
|
+
raise ValueError("length must be > 0")
|
|
483
|
+
|
|
484
|
+
if signed:
|
|
485
|
+
if length is None:
|
|
486
|
+
raise TypeError("signed requires argument 'length'")
|
|
487
|
+
m = 1 << (length - 1)
|
|
488
|
+
if not (-m <= i < m):
|
|
489
|
+
raise OverflowError("signed integer not in range(%d, %d), "
|
|
490
|
+
"got %d" % (-m, m, i))
|
|
491
|
+
if i < 0:
|
|
492
|
+
i += 1 << length
|
|
493
|
+
elif length and i >> length:
|
|
494
|
+
raise OverflowError("unsigned integer not in range(0, %d), "
|
|
495
|
+
"got %d" % (1 << length, i))
|
|
496
|
+
|
|
497
|
+
if length is None:
|
|
498
|
+
length = max(1, i.bit_length())
|
|
499
|
+
|
|
500
|
+
a = bitarray(0, endian)
|
|
501
|
+
a.frombytes(i.to_bytes(bits2bytes(length), byteorder=a.endian))
|
|
502
|
+
|
|
503
|
+
if a.endian == 'little':
|
|
504
|
+
del a[length:]
|
|
505
|
+
else:
|
|
506
|
+
del a[:-length]
|
|
507
|
+
return a
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def sc_stat(stream):
|
|
511
|
+
"""sc_stat(stream) -> dict
|
|
512
|
+
|
|
513
|
+
Scan a stream produced by `sc_encode()` and return a dictionary containing
|
|
514
|
+
its bit-endianness (`endian`), length (`nbits`), and the number of blocks
|
|
515
|
+
of each type (`blocks`). `blocks` is a list such that `blocks[i]` is the
|
|
516
|
+
number of blocks of type `i`.
|
|
517
|
+
|
|
518
|
+
Like `sc_decode()`, it consumes one encoded bitarray and leaves remaining
|
|
519
|
+
input untouched.
|
|
520
|
+
"""
|
|
521
|
+
def decode_header(stream):
|
|
522
|
+
head = next(stream)
|
|
523
|
+
if head & 0xe0:
|
|
524
|
+
raise ValueError("invalid header: 0x%02x" % head)
|
|
525
|
+
endian = 'big' if head & 0x10 else 'little'
|
|
526
|
+
length = head & 0x0f # number of bytes representing nbits
|
|
527
|
+
nbits = 0
|
|
528
|
+
for j in range(length):
|
|
529
|
+
nbits |= next(stream) << 8 * j
|
|
530
|
+
return dict(endian=endian, nbits=nbits)
|
|
531
|
+
|
|
532
|
+
def scan_block(stream, blocks):
|
|
533
|
+
head = next(stream)
|
|
534
|
+
|
|
535
|
+
if head < 0xa0: # type 0 - 0x00 -- 0x9f
|
|
536
|
+
if head == 0: # stop byte
|
|
537
|
+
return False
|
|
538
|
+
n = 0
|
|
539
|
+
k = head if head <= 32 else 32 * (head - 31)
|
|
540
|
+
elif head < 0xc0: # type 1 - 0xa0 .. 0xbf
|
|
541
|
+
n = 1
|
|
542
|
+
k = head - 0xa0
|
|
543
|
+
elif 0xc2 <= head <= 0xc4: # type 2 .. 4 - 0xc2 .. 0xc4
|
|
544
|
+
n = head - 0xc0
|
|
545
|
+
k = next(stream) # index count byte
|
|
546
|
+
else:
|
|
547
|
+
raise ValueError("invalid block head: 0x%02x" % head)
|
|
548
|
+
|
|
549
|
+
blocks[n] += 1
|
|
550
|
+
|
|
551
|
+
nc = max(1, n) * k # size of block data to consume
|
|
552
|
+
next(itertools.islice(stream, nc, nc), None)
|
|
553
|
+
return True
|
|
554
|
+
|
|
555
|
+
stream = iter(stream)
|
|
556
|
+
stats = decode_header(stream)
|
|
557
|
+
|
|
558
|
+
blocks = 5 * [0]
|
|
559
|
+
while scan_block(stream, blocks):
|
|
560
|
+
pass
|
|
561
|
+
|
|
562
|
+
stats['blocks'] = blocks
|
|
563
|
+
return stats
|
|
564
|
+
|
|
565
|
+
# ------------------------------ Huffman coding -----------------------------
|
|
566
|
+
|
|
567
|
+
def _huffman_tree(__freq_map):
|
|
568
|
+
"""_huffman_tree(dict, /) -> Node
|
|
569
|
+
|
|
570
|
+
Given a dict mapping symbols to their frequency, construct a Huffman tree
|
|
571
|
+
and return its root node.
|
|
572
|
+
"""
|
|
573
|
+
from heapq import heappush, heappop
|
|
574
|
+
|
|
575
|
+
class Node:
|
|
576
|
+
"""
|
|
577
|
+
There are two types of Node instances (both have 'freq' attribute):
|
|
578
|
+
* leaf node: has 'symbol' attribute
|
|
579
|
+
* parent node: has 'child' attribute (tuple with both children)
|
|
580
|
+
"""
|
|
581
|
+
def __lt__(self, other):
|
|
582
|
+
# heapq needs to be able to compare the nodes
|
|
583
|
+
return self.freq < other.freq
|
|
584
|
+
|
|
585
|
+
minheap = []
|
|
586
|
+
# create all leaf nodes and push them onto the queue
|
|
587
|
+
for sym, f in __freq_map.items():
|
|
588
|
+
leaf = Node()
|
|
589
|
+
leaf.symbol = sym
|
|
590
|
+
leaf.freq = f
|
|
591
|
+
heappush(minheap, leaf)
|
|
592
|
+
|
|
593
|
+
# repeat the process until only one node remains
|
|
594
|
+
while len(minheap) > 1:
|
|
595
|
+
# take the two nodes with lowest frequencies from the queue
|
|
596
|
+
# to construct a new parent node and push it onto the queue
|
|
597
|
+
parent = Node()
|
|
598
|
+
parent.child = heappop(minheap), heappop(minheap)
|
|
599
|
+
parent.freq = parent.child[0].freq + parent.child[1].freq
|
|
600
|
+
heappush(minheap, parent)
|
|
601
|
+
|
|
602
|
+
# the single remaining node is the root of the Huffman tree
|
|
603
|
+
return minheap[0]
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _check_anydict(d):
|
|
607
|
+
types = dict if sys.version_info[:2] < (3, 15) else (dict, frozendict)
|
|
608
|
+
if not isinstance(d, types):
|
|
609
|
+
raise TypeError("dict expected, got '%s'" % type(d).__name__)
|
|
610
|
+
if not d:
|
|
611
|
+
raise ValueError("cannot create Huffman code with no symbols")
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def huffman_code(__freq_map, endian=None):
|
|
615
|
+
"""huffman_code(dict, /, endian=None) -> dict
|
|
616
|
+
|
|
617
|
+
Given a frequency map, a dictionary mapping symbols to their frequency,
|
|
618
|
+
calculate the Huffman code, i.e. a dict mapping those symbols to
|
|
619
|
+
frozenbitarrays (with given bit-endianness). Note that the symbols are not
|
|
620
|
+
limited to being strings. Symbols may be any hashable object.
|
|
621
|
+
"""
|
|
622
|
+
_check_anydict(__freq_map)
|
|
623
|
+
|
|
624
|
+
if len(__freq_map) == 1:
|
|
625
|
+
# With one symbol, Huffman coding would normally assign it an empty
|
|
626
|
+
# code. Here the code must be nonempty, so that .encode() represents
|
|
627
|
+
# each occurrence and .decode() can recover their number. We use 0,
|
|
628
|
+
# leaving 1 unassigned; therefore the prefix code is incomplete.
|
|
629
|
+
sym = next(iter(__freq_map))
|
|
630
|
+
return {sym: frozenbitarray('0', endian)}
|
|
631
|
+
|
|
632
|
+
result = {}
|
|
633
|
+
|
|
634
|
+
def traverse(nd, prefix):
|
|
635
|
+
try: # leaf
|
|
636
|
+
result[nd.symbol] = prefix
|
|
637
|
+
except AttributeError: # parent, so traverse each child
|
|
638
|
+
traverse(nd.child[0], prefix + '0')
|
|
639
|
+
traverse(nd.child[1], prefix + '1')
|
|
640
|
+
|
|
641
|
+
traverse(_huffman_tree(__freq_map), frozenbitarray(0, endian))
|
|
642
|
+
return result
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def canonical_huffman(__freq_map):
|
|
646
|
+
"""canonical_huffman(dict, /) -> tuple
|
|
647
|
+
|
|
648
|
+
Given a frequency map, a dictionary mapping symbols to their frequency,
|
|
649
|
+
calculate the canonical Huffman code. Returns a tuple containing:
|
|
650
|
+
|
|
651
|
+
0. the canonical Huffman code as a dict mapping symbols to frozenbitarrays
|
|
652
|
+
1. a list containing the number of symbols of each code length
|
|
653
|
+
2. a list of symbols in canonical order
|
|
654
|
+
|
|
655
|
+
Note: the two lists may be used as input for `canonical_decode()`.
|
|
656
|
+
"""
|
|
657
|
+
_check_anydict(__freq_map)
|
|
658
|
+
|
|
659
|
+
if len(__freq_map) == 1:
|
|
660
|
+
# Only one symbol: see note above in huffman_code()
|
|
661
|
+
sym = next(iter(__freq_map))
|
|
662
|
+
return {sym: frozenbitarray('0', 'big')}, [0, 1], [sym]
|
|
663
|
+
|
|
664
|
+
code_length = {} # map symbols to their code length
|
|
665
|
+
|
|
666
|
+
def traverse(nd, length):
|
|
667
|
+
# traverse the Huffman tree, but (unlike in huffman_code() above) we
|
|
668
|
+
# simply record the length for reaching each symbol
|
|
669
|
+
try: # leaf
|
|
670
|
+
code_length[nd.symbol] = length
|
|
671
|
+
except AttributeError: # parent, so traverse each child
|
|
672
|
+
traverse(nd.child[0], length + 1)
|
|
673
|
+
traverse(nd.child[1], length + 1)
|
|
674
|
+
|
|
675
|
+
traverse(_huffman_tree(__freq_map), 0)
|
|
676
|
+
|
|
677
|
+
# We now have a mapping of symbols to their code length, which is all we
|
|
678
|
+
# need to construct a list of tuples (symbol, code length) sorted by
|
|
679
|
+
# code length:
|
|
680
|
+
table = sorted(code_length.items(), key=lambda item: item[1])
|
|
681
|
+
|
|
682
|
+
maxbits = table[-1][1]
|
|
683
|
+
codedict = {}
|
|
684
|
+
count = (maxbits + 1) * [0]
|
|
685
|
+
|
|
686
|
+
code = 0
|
|
687
|
+
for i, (sym, length) in enumerate(table):
|
|
688
|
+
codedict[sym] = frozenbitarray(int2ba(code, length, 'big'))
|
|
689
|
+
count[length] += 1
|
|
690
|
+
if i + 1 < len(table):
|
|
691
|
+
code += 1
|
|
692
|
+
code <<= table[i + 1][1] - length
|
|
693
|
+
|
|
694
|
+
return codedict, count, [item[0] for item in table]
|