tags:

views:

474

answers:

5

Is there a generic BitArray in .NET? I only found the non-generic one.

Can there be a generic BitArray? (i.e. would it be reasonable?)


Edit:

Maybe I should have said type-safe not generic.

Basically when you enumerate the type as object, should it not be int or bool? Or one of them provided in another member enumerator?


Example:

foreach (bool bit in myBitArray)
{

}


Edit:

I just checked the enumerator of the BitArray class, but everything returns an object except .Current property:

public virtual object Current
+5  A: 
Joel B Fant
Thanks, but that would be slower than the internal BitArray iterator (if it was type-safe), right? It uses the Get method in the MoveNext method.
Joan Venge
The first part sounds like you're asking if this would be slower than something that doesn't exist. I struck suggestions of bool[], since Array seems to only implement `IEnumerable`.
Joel B Fant
Well I am asking because I can write one that does that. It's a matter of if it would make sense. It's a game project so every ms counts.
Joan Venge
That's what benchmarking is for. Make a small project that does each of these suggestions thousands of times for a large `BitArray`. Make sure to call each one first before timing to avoid the JIT compile from screwing up your time.
Joel B Fant
Thanks, that's what I was thinking about creating a new list for iterating.
Joan Venge
Yeah this is probably what I will end up doing, like your 2nd edit mentions.
Joan Venge
Why convert to a list when you can write a `IEnumerable<bool>` iterator for `BitArray`? This spends a lot of memory (1 byte per item instead of 1 bit) and is likely to be slower.
Alexey Romanov
@alexey_r: I did benchmark that one, and it was slower than even Enumerable.Cast<T>.
Joel B Fant
Strange, I get quite different results: it's faster than ToList for one loop and slower (but still over 3 times faster than Cast<T>) for multiple loops. Please see my updated answer.
Alexey Romanov
Watch out when copying the code of BitArray...talking about software patents here. But as long this will not be a mainstream game that'll bring you tons of gold-pressed latinum this shouldn't be much of an issue.
galaktor
@alexey_r: Hmmmm. That is strange, because Cast<T> still does boxing and unboxing, as far as I could tell (looking at its workings through Reflector). Oh, well. I'm going to try running your tests later and see what I get.
Joel B Fant
+2  A: 

What would be an example of a generic type argument that you would pass to BitArray<T> if it existed?

BitArray is defined as:

Manages a compact array of bit values, which are represented as Booleans, where true indicates that the bit is on (1) and false indicates the bit is off (0).

This type is an optimized array of bits, nothing else. There is no value to making it generic as there are no members that could be factored out of the type. Any specialized collection like this one can be though of as a closed constructed type of some parent generic collection. In other words, BitArray is kind of like List<Boolean> (with many useful methods added of course).

Edit: Yes, this type implements IEnumerable and does not implement IEnumerable<T> - this is most likely due to the fact that it is an older type and was not updated. Remember that you can use Enumerable.Cast<TResult> to work around this very problem:

yourBitArray.Cast<bool>();
Andrew Hare
Exactly, interface is in terms of bool. Sorage is (irrelevant) in bits.
Henk Holterman
Thanks, so it would make sense to have a type safe BitArray if MS were to add one to the BCL?
Joan Venge
Well, `BitArray` _is_ type-safe. Its only downfall is that it does not implement any generic interfaces (all methods of BitArray: Get, Set, etc. are strongly-typed). but this can be solved with extension methods from the `Enumerable` class.
Andrew Hare
Also please see Richard's answer for another way to look at it. Even though the Enumerator will return objects that doesn't mean that you cannot use them as bools in a foreach. My suggestion of using the `Cast<T>()` method will allow you to pretend as though `BitArray` implements `IEnumerable<T>`.
Andrew Hare
Keep in mind, however, that using Cast<T>() still results in boxing and unboxing of the bool value. This may or may not be something you care about.
Randolpho
@Randolpho - Good point!
Andrew Hare
Thanks guys, yeah this is what I am am trying to avoid, boxing/unboxing.
Joan Venge
@Joan, due to the fact that the type does not implement `IEnumerable<T>` it will be impossible to iterate a `BitArray` without unboxing each value into a `Boolean`.
Andrew Hare
not necessarily... @Joel B Fant's solution doesn't box, nor would using a custom yield iterator... in fact, that gives me an idea. Preparing a new answer...
Randolpho
@Randolpho - But Joel B Fant's solution doesn't use the enumerator either, and as such does not iterate the `BitArray` :)
Andrew Hare
ooooh, distinctions! the *list* is enumerated... But good point. :)
Randolpho
+2  A: 

What possible reason would you have for a generic version? What type could a BitArray possibly use beside bits, or booleans which are turned into bits as the the case is?

Updated: It is type safe. If you are doing a foreach(var bit in bitArray) then bit will appear as an object, but you can just as easily do foreach(bool bit in bitArray), this happens for all collections that implement IEnumerable and not IEnumerable<T>.

Richard Hein
But if you do foreach(bool bit in bitArray), there is gonna be automatic casting?
Joan Venge
Yes. It's "type safe" in that the foreach does the casting for you and you already know that BitArray will always pass you a bool. It's not generic, however, so there's going to be boxing and unboxing for every foreach.
Randolpho
Thanks, that's what I was wondering.
Joan Venge
+6  A: 

BitArray is a specialized collection class from the NET 1.x era. It is quite type-safe as long as you use ba.Set(int, bool) and the indexer property.

What is 'not typesafe' is the enumeration, BitArray implements IEnumerable but not IEnumerable< bool>. So Joan is right, using foreach() involves casting from object to bool.

But is that a real problem? The elements in a BitArray are booleans, and only meaningful when combined with their position. Note that BitArray does not have an Add() method, just a Set(i, true).

So the simple answer is: don't use foreach(), or anything else based on IEnumerable. It only produces a stream of true/false values that can hardly be useful.

In the following snippet the BitArray is perfectly type-safe and efficient:

BitArray isEven = ...;
for(int i = 0; i < isEven.Count; i++) 
{
   isEven.Set(i, i % 2 == 0);
}
Henk Holterman
+5  A: 

You can iterate BitArray without boxing or converting it to List<bool>:

public static IEnumerable<bool> GetTypeSafeEnumerator(this BitArray ba) {
    for (int i = 0; i < ba.Length; i++)
        yield return ba[i];
}

This should be faster than converting to list and certainly take much less memory.

Of course, it is still going to be slower than a plain old for loop, and if you really need performance, you should use

for (int i = 0; i < ba.Length; i++) {
    bool b = ba[i];
    ...
}

Benchmark using MiniBench:

public static class Class1 {
    private const int N = 10000;
    private const int M = 100;

    public static void Main() {
        var bitArray = new BitArray(N);

        var results1 = new TestSuite<BitArray, int>(
            "Different looping methods")
            .Plus(PlainFor, "Plain for loop")
            .Plus(ForEachBool, "foreach(bool bit in bitArray)")
            .Plus(CastBool, "foreach(bool bit in bitArray.Cast<bool>)")
            .Plus(TypeSafeEnumerator, "foreach(bool bit in bitArray.GetTypeSafeEnumerator())")
            .Plus(UseToList, "foreach(bool bit in bitArray.ToList())")
            .RunTests(bitArray, 0);

        results1.Display(ResultColumns.All, results1.FindBest());

        var results2 = new TestSuite<BitArray, int>(
            "Avoiding repeated conversions")
            .Plus(PlainFor1, "Plain for loop")
            .Plus(CastBool1, "foreach(bool bit in bitArray.Cast<bool>)")
            .Plus(TypeSafeEnumerator1, "foreach(bool bit in bitArray.GetTypeSafeEnumerator())")
            .Plus(UseToList1, "foreach(bool bit in bitArray.ToList())")
            .RunTests(bitArray, 0);

        results2.Display(ResultColumns.All, results2.FindBest());
    }

    private static int PlainFor1(BitArray arg) {
        int j = 0;
        for (int k = 0; k < M; k++) {
            for (int i = 0; i < arg.Length; i++) {
                j += arg[i] ? 1 : 0;
            }
        }
        return j;
    }

    private static int CastBool1(BitArray arg) {
        int j = 0;
        var ba = arg.Cast<bool>();
        for (int k = 0; k < M; k++) {
            foreach (bool b in ba) {
                j += b ? 1 : 0;
            }
        }
        return j;
    }

    private static int TypeSafeEnumerator1(BitArray arg) {
        int j = 0;
        var ba = arg.GetTypeSafeEnumerator();
        for (int k = 0; k < M; k++) {
            foreach (bool b in ba) {
                j += b ? 1 : 0;
            }
        }
        return j;
    }

    private static int UseToList1(BitArray arg) {
        int j = 0;
        var ba = arg.ToList();
        for (int k = 0; k < M; k++) {
            foreach (bool b in ba) {
                j += b ? 1 : 0;
            }
        }
        return j;
    }

    private static int PlainFor(BitArray arg) {
        int j = 0;
        for (int i = 0; i < arg.Length; i++) {
            j += arg[i] ? 1 : 0;
        }
        return j;
    }

    private static int ForEachBool(BitArray arg) {
        int j = 0;
        foreach (bool b in arg) {
            j += b ? 1 : 0;                
        }
        return j;
    }

    private static int CastBool(BitArray arg) {
        int j = 0;
        foreach (bool b in arg.Cast<bool>()) {
            j += b ? 1 : 0;
        }
        return j;
    }

    private static int TypeSafeEnumerator(BitArray arg) {
        int j = 0;
        foreach (bool b in arg.GetTypeSafeEnumerator()) {
            j += b ? 1 : 0;
        }
        return j;
    }

    private static int UseToList(BitArray arg) {
        int j = 0;
        foreach (bool b in arg.ToList()) {
            j += b ? 1 : 0;
        }
        return j;
    }

    public static List<bool> ToList(this BitArray ba) {
        List<bool> l = new List<bool>(ba.Count);
        for (int i = 0; i < ba.Count; i++) {
            l.Add(ba[i]);
        }
        return l;
    }

    public static IEnumerable<bool> GetTypeSafeEnumerator(this BitArray ba) {
        for (int i = 0; i < ba.Length; i++)
            yield return ba[i];
    }
}

Results (name, number of iterations, total duration, score (high score is bad)):

============ Different looping methods ============
Plain for loop                                        456899 0:28.087 1,00
foreach(bool bit in bitArray)                         135799 0:29.188 3,50
foreach(bool bit in bitArray.Cast<bool>)               81948 0:33.183 6,59
foreach(bool bit in bitArray.GetTypeSafeEnumerator()) 179956 0:27.508 2,49
foreach(bool bit in bitArray.ToList())                161883 0:27.793 2,79

============ Avoiding repeated conversions ============
Plain for loop                                        5381 0:33.247 1,00
foreach(bool bit in bitArray.Cast<bool>)               745 0:28.273 6,14
foreach(bool bit in bitArray.GetTypeSafeEnumerator()) 2304 0:27.457 1,93
foreach(bool bit in bitArray.ToList())                4603 0:30.583 1,08
Alexey Romanov
Very good answer.
Joan Venge
Perhaps my style of benchmarking was a bit off. I get fairly similar results (though a fourth of the iterations) when using MiniBench and your tests. MiniBench is pretty nice.
Joel B Fant