Can this code be optimized?
I'm interested in speed, not good code, so I'm using an array, not a list (integer).
I have an array similar to: 0,1,0,1,1,0,1,0,1,1,1,0,0,1
I am interested in the position of each number, so I can later pick one at random.
so what I do is loop through the array to take the position number of each 1 and then create a new array like this: 2,4,5,7,9,10,11,14
bitwise can be used here? I have no idea
the code looks like this:
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-15% faster) 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 more optimized than that unless someone finds a way to do exactly what the solution does, but faster.
Before this question, my whole business was able to do about 25,500 for every 10 seconds.
Now it can do over 32250 all the time, 21% more, thanks!
source to share
A few tips for the original algorithm:
- Try storing the arIn.GetUpperBound (0) results in a variable. I don't know how VB makes it loops, but chances are the function is being called once on each iteration. You should check it out.
- That which
If arCount > -1
will always be true. Delete it.
If you want to keep the same I / O, then I don't think there is much that can be improved.
Now, if you want a function that makes random selection, then this might be a little better. I will write in C # since I know it better. You must 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 better. Not unless you can change the inputs / outputs or requirements in any way.
source to share
Instead of storing an array of integers, why not put them all in one whole?
oldArray = [0, 1, 1, 0, 1]
newValue = 22 (binary 10110)
If you want to check if a particular bit position is set, perform a bitwise comparison with two on the strength 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 bitwise comparison search and you should find a lot of help.
Here are some good questions that might help:
What are bitwise operators?
How do you set, clear, and switch one bit?
source to share
I find it hard to believe that the backup will be slower than your loop if it is not inside a loop.
In this case, for raw speed, don't count the number 1 in arIn to set the size of ar. Since ar can never be larger than arIn, just set it to the same size and store it at the end at the end (won't be slower as it is outside the loop and will always be trimming, not expanding - Hopefully VB can do it in place rather than allocating more memory). Also, the size of the arIn cache in case VB calculates it every time through the loop (probably if ReDim is enabled).
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 can completely remove the redim command if you slightly change what is returned. Make the returned array larger than the input array, and use the first element to manipulate whatever portions of the array you choose at will.
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.
This code will:
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 that selects a random value from ar instead of:
rndval = rnd(ar.GetUpperBound)
using:
rndval = rnd(ar(0) + 1)
source to share
Disable overflow checking.
Project Properties -> Compilation -> Advanced Compilation Options -> Remove Integer Integrity Checks.
If you need overflow checking for the rest of the project, you can move the code to a new project (like a DLL) and disable overflow checking for that new project.
Also make sure you are running a release build (optimization enabled) and you are not debugging it.
EDIT: I get 8.5s (12s if I declare an array inside the For parameter that I use for testing) to call the function 50 million times. If you only get 32000, either you are using very large inputs or something is slowing down your code. For example, if you count the time inside a program and run it in a profiler, you will get incorrect results because profiling can slow down the program dramatically. Also glitches like these Slow method calls can impact performance.
source to share
I think when Recursive quoted 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++;
}
}
}
source to share
Speed ββfor the number of items?
Changing items?
Compile time or run time?
What are the spatial constraints?
A well-known strategy is to take a few of them and write down 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 pick a random bit? This is unlikely to help in 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 there are. make a random selection, then choose the selected one.
source to share
You may find that using the For Each parameter and a list initialized with at least the length of the input array is faster than an index:
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 will have to test it.
source to share
Perhaps you can compress the best performance using a lookup table. Then apply the following algorithm:
Sorry I'm not talking about VB anymore, so I'll have to write C #. Here is a snippet of all the code, since the entire lookupTable will contain 256 elements.
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;
}
}
}
source to share
If it is easy to create a prime number slightly larger than the length of your array (depending on its size, it may or may not be easy), and you don't care about a completely uniform random, then you can generate a random number in that range and generate this cycle. It should find the answer within a few iterations (depending on the density 1s). Source code in C # since I don't remember the 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 is not 100% uniformly random, but it should be pretty close.
source to share
I've tried a different approach. The idea is to save a random entry and exit when an entry is found that matches 1. This solution is not ideal because some random are not used, which may or may not break randomness. In addition, the speed is highly dependent on the density "1". The code looks like this:
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;
}
On my PC, if the Random object instantiation is moved to global, it can run 50,000,000 probes in about 11 seconds if their value is 5 "1" out of 30. If there is up to 15 "1" the time it takes is reduced to about 5 from.
Compared to the code suggested by Vilx, its code can run 50,000,000 probes on my PC in about 13 seconds
source to share