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