1

I'm interested in speed, not good looking code, that is why I'm using array and not list(of integer).

I have an array looking like: 0,1,0,1,1,0,1,0,1,1,1,0,0,1

I'm interesting in the position of each number so I can later pick one randomly.

so what I do is looping through the array to take position number of each 1 then creating a new array looking like this: 2,4,5,7,9,10,11,14

is bitwise could be used here? I have no idea

code look like:

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar() As Integer = Nothing
    Dim arCount As Integer = -1

    For x = 1 To arIn.GetUpperBound(0)
        If arIn(x) = 1 Then
            arCount += 1
        End If
    Next

    If arCount > -1 Then
        'using redim preseve is slower than the loop above
        ReDim ar(arCount)

        arCount = 0
        For x = 1 To arIn.GetUpperBound(0)
            If arIn(x) = 1 Then
                ar(arCount) = x
                arCount += 1
            End If
        Next
    End If

    Return ar
End Function

* EDIT *

current solution(10% to 15% faster) is now

Private Function theThing() As Integer
    Dim ar() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim arLenght As Integer = ar.GetUpperBound(0)
    Dim arCount As Integer = 0
    Dim x As Integer

    For x = 1 To arLenght
        If ar(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    dim r As New Random()

    Return ar(r.Next(arCount))
End Function

I don't think it can be optimized more than that, unless someone find a way to do exactly what the solution does but way faster

Before this question, my whole thing was able to do about 25500 run each 10 seconds.

Now, it can do over 32250 all the time, a 21% increase, thanks!

Fredou
  • 19,848
  • 10
  • 58
  • 113
  • Note the two more optimizations that I've hinted at in my code - Promoting "r" and "ar" to a global variables. These allocations are probably the slowest things in the procedure right now (after the inevitable r.Next, of course). – Vilx- Jan 08 '09 at 01:34
  • "r" is already in fact at the class level, for the code above i moved it inside the function, for the ar, I'm not sure if moving it to the class level would help since the length always change – Fredou Jan 08 '09 at 01:43
  • Fredou, you're now returning one element of the array which means you'll have to do this function EVERY TIME you want a value, yes? That doesn't strike me as more efficient at all. Better to return the array and select element outside of this function, methinks. – paxdiablo Jan 08 '09 at 01:46
  • @Pax what I was doing with the array was simply checking if it was nothing or not then doing the random, I removed a if outside that function which is good – Fredou Jan 08 '09 at 01:49
  • If you have to pick several items off of the same list then it is definately better to cache "ar" instead of recalculating it every time. – Vilx- Jan 08 '09 at 01:53
  • As for moving it to class level - yes, it would help because you wouldn't have to recreate it (ReDim) every time. So who cares if it's a few items larger than necessary? Make it big enough that it's never too small and that's that. – Vilx- Jan 08 '09 at 01:54
  • @Vilx-, I moved ar and arin to class level, i saw a 0.5% to 1% performance increase but I had to make a loop to reset all item to 0 (array.clear is slower than the loop i created) – Fredou Jan 08 '09 at 02:14
  • Are you going to be re-using this? It seems faster to build the single list of positions, then randomly grab from that many times. If you are only changing it by removing the bit you selected, then that can be handled without regenerating the entire list again – FryGuy Jan 08 '09 at 02:17
  • and now why a vote down? – Fredou Jan 08 '09 at 02:18
  • @FryGuy, you gave me the idea to remove one array and reusing the one i already had – Fredou Jan 08 '09 at 02:22
  • Use another ' after the comments because stackoverflow doesn't support vb.net comments – ggf31416 Jan 08 '09 at 02:34
  • is ar a constant? If so, you could call "theThing" once during program startup and just store the resultant array. – Evan Teran Jan 08 '09 at 03:17
  • not a constant at all, by the way, I modified stuff outside that function, I now reach over 44000 per run for 10sec loop – Fredou Jan 08 '09 at 04:22
  • That is extremely slow. Take a good look at why you need this exact form. It can easily be imnproved a factor 10 to 100 – Stephan Eggermont Jan 08 '09 at 09:17
  • The thing we're all trying to tell you here: You are asking the wrong question. Tell us something about the larger problem you're trying to solve, and we can get you much better answers. Factor 2 is nothing – Stephan Eggermont Jan 08 '09 at 09:22
  • this part of the code work with other code, what make my process going slow is what I do with the result and how I create the arIn array, passed to the function by parameter – Fredou Jan 08 '09 at 13:00
  • Fredou - you're still missing the point about moving "ar" to class level. You don't need to reset it to zero! Everything will work just fine even if there are junk values there at the beginning. – Vilx- Jan 08 '09 at 16:44
  • Although the solution you have written now does away with this entirely. If you can reuse "arIn" to work as "ar" then there is nothing else to optimize. – Vilx- Jan 08 '09 at 16:45

11 Answers11

8

Instead of storing an array of integers, why not put them all into one integer?

oldArray = [0, 1, 1, 0, 1]
newValue =  22     (binary 10110)

If you want to check if a particular bit position is set, do a bitwise comparison with two to the power of that position:

is position 2 set?
value:    10110
4 (2^2):  00100
result:   00100 --> true

is position 0 set?
value:    10110
1 (2^0):  00001
result:   00000 --> false

Do a search for bitwise comparison and you should find a lot of help.

Here are some good Stack Overflow questions which might help:
What are bitwise operators?
How do you set, clear, and toggle a single bit?

Community
  • 1
  • 1
nickf
  • 537,072
  • 198
  • 649
  • 721
  • I want to store the location of each true bit so I can do a random on the list to pick one, I'm interested on the position. – Fredou Jan 08 '09 at 00:34
  • Breaks if there's more than 32 array indexes. – paxdiablo Jan 08 '09 at 01:03
  • your bit positions are off by one place. 8 = 1000bin, 16 = 10000bin aka 2^3 2^2 2^1 2^0 – Tanj Jan 08 '09 at 01:06
  • +1 - the problem description is begging for a bit vector implementation. Note to Pax, there are ways to implement that with arbitrary size. – Ian Varley Jan 08 '09 at 01:40
2

Few tips on the original algorithm:

  1. Try storing the results of arIn.GetUpperBound(0) in a variable. I don't know how VB makes it's loops, but there is a chance that the function gets called once every iteration. You should check that though.
  2. That If arCount > -1 is always going to be true. Remove it.

If you wish to keep the same inputs/outputs, then I don't think there is much else that can be improved.

Now if you wanted a function that does the random selection too, then it might be a bit better. I'll write in C# since I know it better. You should be able to understand:

public static int GetRandomSetBit(int[] AllBits)
{
    // Perhaps check here if AllBits is null/empty. I'll skip that for now.

    int L = AllBits.Length;
    int SetBitCount = 0;

    // No point to save a few bytes here. Also - you can make a global array
    // large enough for all occasions and skip allocating it every time.
    // In that case consider automatic resizing and watch out for
    // multithreading issues (if you use several threads).
    int[] GoodPositions = new int[L];

    for ( int i = 0; i < L; i++ )
        if ( AllBits[i] != 0 )
        {
            GoodPositions[SetBitCount] = i;
            SetBitCount++;
        }
     Random r = new Random(); // Should use one global instance
     return GoodPositions[r.Next(SetBitCount)];
}

I'm afraid it won't get any better than that. Not unless you can somehow change the inputs/outputs or requirements.

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • +1 for storing the getupperbound(0), went from 25500-26500 to, for my 5 test, over 27000-27400 run, over 10 secs in a loop – Fredou Jan 08 '09 at 01:09
  • sometime there will be not true bit, that is why i do the check for arcount > -1 (fixed my code above to make it -1) – Fredou Jan 08 '09 at 01:12
  • Bad choice. Now you've upset the counter and it will be off by 1. You should just leave it alone (set to 0 at start, not -1) and remove the IF. Then it will work fine. – Vilx- Jan 08 '09 at 01:19
  • and I wish I could give you another +1 for the idea of doing the random on the setbitcount(doing over 28000 now), I will probably mark you as the answer if there is no better way for the next 30 minutes – Fredou Jan 08 '09 at 01:23
  • Bet you can squeeze a little more performance out of this by using a System.Collections.BitArray rather than an int[]. – Juliet Jan 08 '09 at 03:28
  • 1
    Bet you can't because then accessing each value will require several bitwise operations. Now it's just an array lookup. – Vilx- Jan 08 '09 at 16:40
  • result of my question can be found here http://www.codeproject.com/KB/vb/CodeBehindSudoku.aspx , thanks! – Fredou Apr 22 '09 at 13:28
1

I find it hard to believe that a redim preserve would be slower than your loop unless it were itself inside the loop.

In which case, for raw speed, don't count the number of 1's in arIn just to set the size of ar. Since ar can never be bigger than arIn, just set it to the same size and redim-preserve at the end (won't be slower since it's outside the loop and will always be trimming, not expanding - VB hopefully can do this in-place rather than allocating more memory). In addition, cache size of arIn in case VB calculates it each time through the loop (likely if ReDim's are allowed).

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar(arIn.GetUpperBound(0)) As Integer
    Dim arCount As Integer
    Dim arInCount As Integer

    arCount = 0
    arInCount = arIn.GetUpperBound(0)
    For x = 1 To arInCount
        If arIn(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    ReDim Preserve ar(arCount)
    Return ar
End Function

Alternatively, you could remove the redim altogether if you tweak slightly what's returned. Make the return array one bigger than the input array and use the first element to control which parts of the array you'll select randomly.

For your sample, the returned array would be:

{8,2,4,5,7,9,10,11,14,?,?,?,?,?,?} (? values are irrelevant).
 ^ <-------+--------> <----+---->
 |         |               |
 |         |               +-- These are unused.
 |         |
 |         +-- These are used.
 |
 +-- This is the count of elements to use.

That code would be:

Private Function theThing() As Integer()
    Dim x As Integer
    'arIn() would be a parameter
    Dim arIn() As Integer = {0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1}
    Dim ar(arIn.GetUpperBound(0)+1) As Integer
    Dim arCount As Integer
    Dim arInCount As Integer

    arCount = 0
    arInCount = arIn.GetUpperBound(0)
    For x = 1 To arInCount
        If arIn(x) = 1 Then
            ar(arCount) = x
            arCount += 1
        End If
    Next

    ar(0) = arCount
    Return ar
End Function

Then, in your code which selects a random value from ar, instead of:

rndval = rnd(ar.GetUpperBound)

use:

rndval = rnd(ar(0) + 1)
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • yes you are correct, Vilx- made me realize that earlier when I did my test with the preserve, it was inside the loop which was really bad – Fredou Jan 08 '09 at 01:31
  • Then my first solution is probably better, especially if you wish to minimize changes to the calling code. – paxdiablo Jan 08 '09 at 01:35
  • result of my question can be found here http://www.codeproject.com/KB/vb/CodeBehindSudoku.aspx , thanks! – Fredou Apr 22 '09 at 13:27
1

Disable Overflow Checking.
Project Properties -> Compile -> Advanced Compile Options -> Remove Integer Overflow Checks.
If you need overflow checking for the rest of the project you may move the code to a new project (for example a DLL) and disable overflow checking for that new project.

Also make sure that you are running a release build(optimizations enabled) and you are not debugging it.

EDIT: I get 8.5s (12s if I declare the array inside the For I'm using for testing) for calling the function 50 millons times. If you are getting only 32000 either you are using very large inputs or something is slowing down your code. For example if you are counting the time inside the program and you are running it in a profiler you will get wrong results as profiling can slowdown the program dramatically. Also glitches like this Slow methods calls may affect performance.

Community
  • 1
  • 1
ggf31416
  • 3,582
  • 1
  • 25
  • 26
  • result of my question can be found here http://www.codeproject.com/KB/vb/CodeBehindSudoku.aspx , thanks! – Fredou Apr 22 '09 at 13:26
1

I think that when Recursive cited BitArray, he meant something like this:

using System.Collections.Generic;
using System;
using System.Collections;

class Program
{
    static void Main(string[] args)
    {
        var input = new BitArray(new byte[] { 0x5A /*01011010b*/
                                        , 0xE4 /*11100100b*/ });
        var output = new List<int>();
        var offset = 1;
        foreach (var bit in input)
        {
            if (bit)
            {
                output.Add(offset);
            }
            offset++;
        }
    }
}
Jader Dias
  • 88,211
  • 155
  • 421
  • 625
0

Speed for how many items?

Changing items?

Compile time or execution time?

With what space constraints?

A known strategy is to take a few ones together and write out all combinations and their results: 0000 -> 0001 -> 4 0010 -> 3 0011 -> 3,4 0100 -> 2 0101 -> 2,4 0110 -> 2,3 ...

Why do you want to convert from this binary representation to select a random one bit? That is unlikely to help with performance. Better group them by 8 bits, use a table telling you how many 1s are in the group, and repeat 5 times. then you know how many ones there are. do a random over that and then look up the selected one.

Stephan Eggermont
  • 15,847
  • 1
  • 38
  • 65
  • item in the array? between 0 to 36-40, what do you mean by changing items?, execution time because it can be used in a HUGE loop, no real space constraints – Fredou Jan 08 '09 at 00:31
  • Like I said, I'm interested in the position. – Fredou Jan 08 '09 at 01:04
0

You might find that using For Each and a list initialized to at least the length of the input array is faster than an indexer:

If arIn.Length > 0 Then
  Dim lstOut As New List(Of Integer)(arIn.Length)
  Dim ix As Integer = 0
  For Each val As Integer In arIn
    If val = 1 Then
      lstOut.Add(ix)
    End If
    ix = ix + 1
  End If
  Return lstOut.ToArray()
Else
  Return Nothing
End If

But you'd have to test it.

Andrew Kennan
  • 13,947
  • 3
  • 24
  • 33
  • @Mitch Wheat, over a 10sec(i do way more than using my code above) loop, with array I do 26295 run, for the list(of T), 25390 run. Ok, you can remove the "way" word, but it's still slower – Fredou Jan 08 '09 at 00:54
0

This is what BitArray was made for.

recursive
  • 83,943
  • 34
  • 151
  • 241
  • interesting, I will look into it. I might have to do a major recoding of my current work if it does work faster – Fredou Jan 08 '09 at 00:57
  • I don't see an easy way to do what I want, storing position of true value so I can do a random on that list – Fredou Jan 08 '09 at 01:00
0

Maybe you can squeeze the maximum performance by using a conversion table. And then apply the algorithm below:

Sorry I'm not fluent on VB anymore, so I'll have to write C#. Here's a piece of the whole code, as the entire lookupTable would have 256 items.

using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        var input = new byte[] { 0x5A /*01011010b*/, 0xE4 /*11100100b*/ };
        var output = new List<int>();
        var lookupTable = new int[][] { new int[0] /*00000000b*/
                                           , new int[] { 8 }    /*00000001b*/
                                           , new int[] { 7 }    /*00000010b*/
                                           , new int[] { 7,8 }  /*00000011b*/
                                           , new int[] { 6 }    /*00000100b*/
                                           , new int[] { 6,8 }  /*00000101b*/
                                           , new int[] { 6,7,8 }  /*00000111b*/
                                          };
        int offset = 0;
        foreach (var value in input)
        {
            foreach (var position in lookupTable[(int)value])
            {
                output.Add(position + offset);
            }
            offset += 8;
        }
    }
}
Jader Dias
  • 88,211
  • 155
  • 421
  • 625
0

If it's easy to generate a prime number slightly larger than the length of your array (depending on the size of it, this may or may not be easy), and you don't care about completely uniformly random, then you can generate a random number in that range and generate that cycle. It should find an answer within a few iterations (based on the density of 1s). Source code in c# since I don't remember vb syntax:

int RandomInArray(int[] ar)
{
    int p = GenerateSlightlyLargerPrime(ar.Length);

    int x = random.nextInt(p);
    int i = x;
    do
    {
       if (i < ar.Length && ar[i] == 1)
           return i;
       i = (i + x) % p;
    } while (i != x);
    return -1;
}

Note that this isn't 100% uniformly random, but it should be fairly close.

FryGuy
  • 8,614
  • 3
  • 33
  • 47
0

I have tried a different approach. The idea is to keep getting a random entry and exit when an entry corresponding to 1 is found. This solution is not perfect because some randoms are not used which may or may not break the randomness. In addition, the speed greatly depends on the density of "1"s. Code is as follow:

public static int GetRandomSetBit(int[] AllBits)
{
    int L = AllBits.Length;
    Random r = new Random();    // Better use a global instance as this hurt performance a lot
    do
    {
         selected = r.Next(L);

    } while (AllBits[selected] == 0);
    return selected;
}

In my PC, if the creation of the Random object is moved into global, it can run 50000000 trials in around 11s if there are 5 "1"s out of 30. If there are up to 15 "1"s, the time it takes is shortened to around 5s.

Comparing with the code as suggested by Vilx, his code can run 50000000 trials in my PC around 13s

Conrad
  • 733
  • 4
  • 7
  • sometime, it can be all false value – Fredou Jan 08 '09 at 04:18
  • Yes, as I said it is not a perfect solution. In case the input is all zero, it will never return. Actually if there are too few "1"s, this algorithm will perform very badly. – Conrad Jan 08 '09 at 06:33