Inspections#
Once you’ve created a Tibs you can treat it very like an array of bits, with standard
Python indexing and slicing, as well as finding, counting and other useful methods.
Indexing and slicing#
The default behaviour for indexing and slicing should hold no surprises.
Indexing returns a bool, slicing returns a new Tibs:
>>> t = Tibs('0x0f')
>>> t[0]
False
>>> t[-1]
True
>>> t[:4]
Tibs('0x0')
>>> t[-4:]
Tibs('0xf')
You can also use extended slices:
>>> t[::-1].bin
'11110000'
>>> t[::2].bin
'0011'
A Mutibs can also have bits and slices set:
>>> m = Mutibs('0xff01')
>>> m[0] = 0
>>> m[-4:] = '0xbeef'
>>> m
Mutibs('0x7f0beef')
Information methods#
count#
To count the number of times a bit value or sequence of bits occurs use the Tibs.count() method:
>>> t = Tibs.from_random(100_000_000)
>>> t.count(1)
49996739
>>> t.count([1, 0, 1])
12503821
>>> Tibs('0xef').count(1, 0, 4)
3
Counting should be very fast, especially when just counting the number of 1 or 0 bits.
find / rfind#
Use Tibs.find() to find the first occurrence of a bit pattern, and
Tibs.rfind() to search from the right. Both methods return the bit index of
the match, or None if no match is found:
>>> t = Tibs('0b0011010101100')
>>> t.find('0b101')
3
>>> t.rfind('0b101')
7
>>> t.find('0b111') is None
True
The pattern can be anything that can be promoted to a Tibs - a binary string,
bytes, a strict list/tuple bit pattern, or another Tibs.
The optional start and end arguments restrict the search to a slice of
the data. If you know the
pattern can only start on a byte boundary, set byte_aligned=True. This is
often faster for scanning binary files or network frames:
>>> capture = Tibs('0x00ffaa551122aa553344')
>>> capture.find('0xaa55', byte_aligned=True)
16
The in operator is a convenient shorthand when you only care whether the
pattern exists:
>>> '0xaa55' in capture
True
find_all / find_all_iter#
Use Tibs.find_all() to get every matching start position:
>>> t = Tibs('0b10100101')
>>> t.find_all('0b101')
[0, 5]
Matches may overlap. This is useful when searching for bit patterns rather than tokens:
>>> Tibs('0b1111').find_all('0b11')
[0, 1, 2]
For large inputs, Tibs.find_all_iter() avoids building the whole list up
front:
>>> t = Tibs('0b10100101')
>>> for pos in t.find_all_iter('0b101'):
... print(pos)
0
5
There is also Tibs.rfind_all_iter(), which yields matches from right to
left. Iterator forms are only available on Tibs. If you have a Mutibs,
use Mutibs.to_tibs() to make an immutable copy, or Mutibs.as_tibs()
to move the data if you no longer need the mutable object.
starts_with / ends_with#
The Tibs.starts_with() and Tibs.ends_with() methods test prefixes and
suffixes without spelling out slice boundaries:
>>> packet = Tibs('0xaa551234')
>>> packet.starts_with('0xaa55')
True
>>> packet.ends_with('0x1234')
True
any / all#
The Tibs.any() and Tibs.all() methods mirror Python’s built-in
any() and all(), but operate directly on the stored bits:
>>> Tibs('0b0001').any()
True
>>> Tibs('0b0001').all()
False
>>> Tibs.from_ones(8).all()
True
They are most useful when the bit sequence itself is the data, for example when checking whether a mask has any flags set, or whether every flag in a required set is present.
Splitting at positions#
Sometimes instead of using slices, if you want to partition a value at one or more bit
positions use Tibs.split_at():
>>> t = Tibs('0b101100')
>>> head, tail = t.split_at(3)
>>> head, tail
(Tibs('0b101'), Tibs('0b100'))
>>> flags, length, payload = t.split_at([2, 5])
>>> flags, length, payload
(Tibs('0b10'), Tibs('0b110'), Tibs('0b0'))
The positions use normal bit offsets. Negative positions count from the end, and duplicate positions create empty pieces. The positions must be in nondecreasing order after negative positions are normalized.