# Slicing, Dicing and Splicing

Manipulating binary data can be a bit of a challenge in Python. One of its strengths is that you don’t have to worry about the low level data, but this can make life difficult when what you care about is precisely the thing that is safely hidden by high level abstractions.

In this section some more methods are described that treat data as a series of bits, rather than bytes.

## Slicing

Slicing takes three arguments: the first position you want, one past the last position you want and a multiplicative factor which defaults to 1.

The third argument (the ‘step’) will be described shortly, but most of the time you’ll probably just need the bit-wise slice, where for example `a[10:12]` will return a 2-bit bitstring of the 10th and 11th bits in `a`, and `a[32]` will return just the 32nd bit.

    >>> a = BitArray('0b00011110')
    >>> b = a[3:7]
    >>> print(a, b)
    0x1e 0xf

For single bit indices (as opposed to slices) a boolean is returned; that is `True` for ‘1’ bits and `False` for ‘0’ bits:

    >>> a[0]
    False
    >>> a[4]
    True

If you want a single bit as a new bitstring then use a one-bit slice instead:

    >>> a[0:1]
    BitArray('0b0')

Indexing also works for missing and negative arguments, just as it does for other containers.

    >>> a = BitArray('0b00011110')
    >>> print(a[:5])         # first 5 bits
    0b00011
    >>> print(a[3:])         # everything except first 3 bits
    0b11110
    >>> print(a[-4:])        # final 4 bits
    0xe
    >>> print(a[:-1])        # everything except last bit
    0b0001111
    >>> print(a[-6:-4])      # from 6 from the end to 4 from the end
    0b01

### Stepping in slices

The step parameter (also known as the stride) can be used in slices and has the same meaning as in the built-in containers:

    >>> s = BitArray(16)
    >>> s[::2] = [1]*8
    >>> s.bin
    '1010101010101010'
    >>> del s[8::2]
    >>> s.bin
    '101010100000'
    >>> s[::3].bin
    '1010'

Negative slices are also allowed, and should do what you’d expect. So for example `s[::-1]` returns a bit-reversed copy of `s` (which is similar to using `s.reverse()`, which does the same operation on `s` in-place).

## Joining

To join together a couple of bitstring objects use the `+` or `+=` operators, or the `append` and `prepend` methods.

    # Six ways of creating the same BitArray:
    a1 = BitArray(bin='000') + BitArray(hex='f')
    a2 = BitArray('0b000') + BitArray('0xf')
    a3 = BitArray('0b000') + '0xf'
    a4 = BitArray('0b000')
    a4.append('0xf')
    a5 = BitArray('0xf')
    a5.prepend('0b000')
    a6 = BitArray('0b000')
    a6 += '0xf'

Note that the final three methods all modify a bitstring, and so will only work with `BitArray` objects, not the immutable `Bits` objects.

If you want to join a large number of bitstrings then the method `join` can be used to improve efficiency and readability. It works like the ordinary string join function in that it uses the bitstring that it is called on as a separator when joining the list of bitstring objects it is given. If you don’t want a separator then it can be called on an empty bitstring.

    bslist = [BitArray(uint=n, length=12) for n in xrange(1000)]
    s = BitArray('0b1111').join(bslist)

## Truncating, inserting, deleting and overwriting

The functions in this section all modify the bitstring that they operate on and so are not available for `Bits` objects.

### Deleting and truncating

To delete bits just use `del` as you would with any other container:

    >>> a = BitArray('0b00011000')
    >>> del a[3:5]                # remove 2 bits at pos 3
    >>> a.bin
    ‘000000’
    >>> b = BitArray('0x112233445566')
    >>> del b[24:40]
    >>> b.hex
    '11223366'

You can of course use this to truncate the start or end bits just as easily:

    >>> a = BitArray('0x001122')
    >>> del a[-8:]   # remove last 8 bits
    >>> del a[:8]    # remove first 8 bits
    >>> a == '0x11'
    True

### `insert`

As you might expect, `insert` takes one `BitArray` and inserts it into another. A bit position must be specified for `BitArray` and `Bits`, but for BitStreams if not present then the current `pos` is used.

    >>> a = BitArray('0x00112233')
    >>> a.insert('0xffff', 16)
    >>> a.hex
    '0011ffff2233'

### `overwrite`

`overwrite` does much the same as `insert`, but predictably the `BitArray` object’s data is overwritten by the new data.

    >>> a = BitStream('0x00112233')
    >>> a.pos = 4
    >>> a.overwrite('0b1111')         # Uses current pos as default
    >>> a.hex
    '0f112233'

## The bitstring as a list

If you treat a bitstring object as a list whose elements are all either ‘1’ or ‘0’ then you won’t go far wrong. The table below gives some of the equivalent ways of using methods and the standard slice notation.

Using functions | Using slices
--- | ---
`s.insert(bs, pos)` | `s[pos:pos] = bs`
`s.overwrite(bs, pos)` | `s[pos:pos + bs.len] = bs`
`s.append(bs)` | `s[s.len:s.len] = bs`
`s.prepend(bs)` | `s[0:0] = bs`

## Splitting

### `split`

Sometimes it can be very useful to use a delimiter to split a bitstring into sections. The `split` method returns a generator for the sections.

    >>> a = BitArray('0x4700004711472222')
    >>> for s in a.split('0x47', bytealigned=True):
    ...     print(s.hex)

    470000
    4711
    472222

Note that the first item returned is always the bitstring before the first occurrence of the delimiter, even if it is empty.

### `cut`

If you just want to split into equal parts then use the `cut` method. This takes a number of bits as its first argument and returns a generator for chunks of that size.

    >>> a = BitArray('0x47001243')
    >>> for byte in a.cut(8):
    ...     print(byte.hex)
    47
    00
    12
    43