How do I manipulate arrays of bits?

Use pack() and unpack(), or else vec() and the bitwise operations.

For example, this sets $vec to have bit N set if $ints[N] was set:

    $vec = '';
    foreach(@ints) { vec($vec,$_,1) = 1 }
Here's how, given a vector in $vec, you can get those bits into your @ints array:

    sub bitvec_to_list {
	my $vec = shift;
	my @ints;
	# Find null-byte density then select best algorithm
	if ($vec =~ tr/\0// / length $vec > 0.95) {
	    use integer;
	    my $i;
	    # This method is faster with mostly null-bytes
	    while($vec =~ /[^\0]/g ) {
		$i = -9 + 8 * pos $vec;
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
		push @ints, $i if vec($vec, ++$i, 1);
	    }
	} else {
	    # This method is a fast general algorithm
	    use integer;
	    my $bits = unpack "b*", $vec;
	    push @ints, 0 if $bits =~ s/^(\d)// && $1;
	    push @ints, pos $bits while($bits =~ /1/g);
	}
	return \@ints;
    }
This method gets faster the more sparse the bit vector is. (Courtesy of Tim Bunce and Winfried Koenig.)

You can make the while loop a lot shorter with this suggestion from Benjamin Goldberg:

	while($vec =~ /[^\0]+/g ) {
	   push @ints, grep vec($vec, $_, 1), $-[0] * 8 .. $+[0] * 8;
	}
Or use the CPAN module Bit::Vector:

    $vector = Bit::Vector->new($num_of_bits);
    $vector->Index_List_Store(@ints);
    @ints = $vector->Index_List_Read();
Bit::Vector provides efficient methods for bit vector, sets of small integers and "big int" math.

Here's a more extensive illustration using vec():

    # vec demo
    $vector = "\xff\x0f\xef\xfe";
    print "Ilya's string \\xff\\x0f\\xef\\xfe represents the number ", 
	unpack("N", $vector), "\n";
    $is_set = vec($vector, 23, 1);
    print "Its 23rd bit is ", $is_set ? "set" : "clear", ".\n";
    pvec($vector);
    set_vec(1,1,1);
    set_vec(3,1,1);
    set_vec(23,1,1);
    set_vec(3,1,3);
    set_vec(3,2,3);
    set_vec(3,4,3);
    set_vec(3,4,7);
    set_vec(3,8,3);
    set_vec(3,8,7);
    set_vec(0,32,17);
    set_vec(1,32,17);
    sub set_vec { 
	my ($offset, $width, $value) = @_;
	my $vector = '';
	vec($vector, $offset, $width) = $value;
	print "offset=$offset width=$width value=$value\n";
	pvec($vector);
    }
    sub pvec {
	my $vector = shift;
	my $bits = unpack("b*", $vector);
	my $i = 0;
	my $BASE = 8;
	print "vector length in bytes: ", length($vector), "\n";
	@bytes = unpack("A8" x length($vector), $bits);
	print "bits are: @bytes\n\n";
    } 

Back to perlfaq4