2

How do I convert a bool array into a byte array?

I would like the resulting vector to have 1/8th the length of the input vector.

Like this

let a = [false; 160];
let b: [u8; 20] = ???

I guess I can make a for-loop and do some arithmetic, but I wonder if there is a simpler way of doing it.

Thorkil Værge
  • 2,727
  • 5
  • 32
  • 48

4 Answers4

2

As an alternative to the crate-based solutions, the "hand-rolled" version isn't too difficult to implement, and avoids the allocations:

    let mut b = [0u8;20];
    for (idx, bit) in a.into_iter().enumerate() {
        let byte = idx / 8;
        let shift = 7 - idx % 8;
        b[byte] |= (bit as u8) << shift;
    }

The issue making this generic is that the current const generics don't support arithmetics on constants.

In nightly with generic_const_expr, it's possible for the thing to work on arbitrary input sizes (this incomplete implementation will panic on incomplete trailing bytes):

#![feature(generic_const_exprs)]

fn to_bits<const N: usize>(bools: [bool;N]) -> [u8;N/8] {
    let mut out = [0;N/8];
    for (idx, bit) in bools.into_iter().enumerate() {
        let byte = idx / 8;
        let shift = 7 - idx % 8;
        out[byte] |= (bit as u8) << shift;
    }
    out
}

fn main() {
    let mut a = [false; 160];
    a[42] = true;
    let b = to_bits(a);
    println!("{:?}", b);
}

though I think bitvec also has a BitArray type which might allow doing this without allocations as well.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • You went with the opposite bit-endianness that I did. In your first piece of code, `let shift = 7 - idx % 8;` can be replaced with `let shift = idx % 8;` to flip the bit-endianness. – Thorkil Værge Jun 10 '22 at 11:11
  • 1
    Might break if `N % 8 != 0`, e.g. `[bool;15]` becomes `[u8; 1]` which is definitely wrong. maybe `[0; (N+7)/8]`? – Finomnis Jun 10 '22 at 12:14
  • @Finomnis it doesn't become anything, it currently panics, as noted in the answer. Feel free to fix it (and remove the note) if you feel that's important. – Masklinn Jun 10 '22 at 13:11
  • Not really. One can see the concept. – Finomnis Jun 10 '22 at 14:30
2

This works and doesn't require a new dependency.

    let mut b = [0u8;20];
    for i in 0..160 {
        b[i / 8] |= u8::from(a[i]) << (i % 8);
    }
Thorkil Værge
  • 2,727
  • 5
  • 32
  • 48
1

I don't think there is a way to do that with just the built-in std library.

With the excellent bit-vec crate, though:

use bit_vec::BitVec;

fn main() {
    let mut a = [false; 160];
    a[42] = true;

    let bitvec: BitVec = a.into_iter().collect();

    let b: [u8; 20] = bitvec.to_bytes().try_into().unwrap();

    println!("{:?}", b);
}
[0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

You can put it inside a generic function, but that requires nightly and unstable features:

#![feature(generic_const_exprs)]

use bit_vec::BitVec;

fn convert_vector<const N: usize>(bits: [bool; N]) -> [u8; N / 8] {
    let bitvec: BitVec = bits.into_iter().collect();
    bitvec.to_bytes().try_into().unwrap()
}

fn main() {
    let mut a = [false; 160];
    a[42] = true;

    println!("{:?}", convert_vector(a));
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27
1

Using the bitvec crate, something like this should come close:

use bitvec::vec::BitVec;

let a = [false; 160];
let b = [0; 20]
b[..].copy_from_slice (a.iter().collect::<BitVec>().as_raw_slice());
Jmb
  • 18,893
  • 2
  • 28
  • 55