VB.net Histogram - how to bin data - vb.net

I'm working on a histogram class and in particular a binning method.
In relation hereto I have two questions:
Is it a right/appropriate algorithm seen from a logic/statistical point of view
Is the code optimal or at least decent - please tell me how to improve it
Any help is highly appreciated - thx in advance.
Here is my code so far...
Public Class Histo
Dim data() As Double
Dim bins As Integer = 0
Dim bw As Double = 0
Dim _min As Double = 0
Dim _max As Double = 0
Dim arrMax As Double = 0
Dim cht As Chart
Public shared Decimals As Integer
Public Sub New(_arr() As Double, _cht As Chart)
'One-dimensional array as data
data = _arr
'No of bins with Sturges method
bins = NoBin_ST(data)
'calculate bin width
bw = Range(data) / bins
'bin boundries for first bin
_min = Min(data)
_max = _min + bw
'max of data
arrMax = Max(data)
'chart object
cht = _cht
'no of decimals on x-axis
Decimals = Dec
End Sub
Public Function Binning() As Integer()
'Binning "algorihtm" for continuous data
'
'RETURN: one-dimensional array with n bins
'
Array.Sort(data)
Dim j As Integer = 0
Dim mn As Double = _min
Dim mx As Double = _max
Dim counter(bins-1) As Integer
For i As Integer = 0 To data.GetLength(0)-1
'check if data point is within the boundries of the current bin
If data(i) >= mn AndAlso data(i) < mx Then
'add counter in current bin
counter(j) += 1
Else
'special case: at the end at least one data point will equal max of the last bin
' and must be counted in that bin
If data(i) = arrMax Then
counter(j) += 1
Continue For
End If
'the data point has exceeded the boundries of the previous bin
' and must be counted in the next bin
'min and max is increased with the bin width
mn += bw
mx += bw
'go to next bin
j += 1
'count data point in this bin and loop again
counter(j) += 1
End If
Next
Return counter
End Function
.....

Not sure if this is any more performant, but I think it is a bit simpler.
Function CreateBins(values As IEnumerable(Of Double), numberOfBins As Integer) As IGrouping(Of Integer, Double)()
If values Is Nothing Then Throw New Exception("Values cannot be null")
If values.Distinct.Count < 2 Then Throw New Exception("Values must contain at least two ditinct elements")
If numberOfBins < 1 Then Throw New Exception("numberOfBins must be an integer > 1")
Dim min = values.Min
Dim max = values.Max
Dim binSize = (max - min) / numberOfBins
' Checking for two distinct elements should eliminate possibility of min=max and therefore binsize=0
Dim bins = values.GroupBy(Function(x) Convert.ToInt32(Math.Floor((x - min) / binSize))).ToArray
' Group counts available using the ienumerable Count function
' Dim counts = bins.Select(Function(x) x.Count)
' Or retaining the group key
' Dim counts = bins.Select(Function(x) New With {Key x.Key, x.Count})
Return bins
End Function
Each bin is now a group. The original values are retained as part of the group, allowing potential follow up analysis. Count is available using the group function Count()

Related

Sum of Random numbers must be greater than x and less than y

I would like for my code to express the sum of random numbers generated to be in a range. Between 120 and 235.
What's the best way to do that without changing my code too much?
I'm positive it needs to create 2 Dims and an if else statement, but I can't word it properly.
I'm using Visual Studio 2017
Public Class Form1
Private Sub Button1_Click(ByVal sender As Object, e As EventArgs) Handles Button1.Click
Randomize()
TextBox1.Text = Rand(1, 100)
TextBox2.Text = Rand(Long.Parse(TextBox1.Text), 100)
TextBox3.Text = Rand(Long.Parse(TextBox2.Text), 100)
TextBox4.Text = Rand(Long.Parse(TextBox3.Text), 100)
TextBox5.Text = Rand(Long.Parse(TextBox4.Text), 100)
TextBox6.Text = Rand(Long.Parse(TextBox5.Text), 100)
End Sub
Private Function Rand(v As Long) As String
Throw New NotImplementedException()
End Function
Private Function Rand(ByVal Low As Long, ByVal High As Long) As Long
Rand = Int((High - Low + 1) * Rnd()) + Low
End Function
End Class
I'ld suggest to use the .Net Random class to generate random numbers. It's also simpler to use.
Find the first random number between 0 and the Minimum value, then the second random number will be in range: randomMin(MinValue) => (MinValue - randomMin, MaxValue - randomMin):
randomMin = rnd1.Next(Min + 1)
randomMax = rnd2.Next(Min - randomMin, Max - randomMin + 1)
result = randomMin + randomMax
To remember that, in the Random class, the upper limit is exclusive, so we need to add 1 to the Max value to include it in the range of random values.
Make a sample test:
(These code samples suppose that the VB.Net version in use is at least V.14, VS 2015+)
Private rnd1 As Random = New Random()
Private rnd2 As Random = New Random()
'(...)
Dim Min As Integer = 120
Dim Max As Integer = 235
For i = 0 To 100
Dim randomValues = GetRandomNumbersInRange(Min, Max)
Console.WriteLine($"Random Min: {randomValues.rndMin} Random Max {randomValues.rndMax}")
Console.WriteLine($"Sum: {randomValues.rndMin + randomValues.rndMax}")
Next
'(...)
Private Function GetRandomNumbersInRange(Min As Integer, Max As Integer) As
(rndMin As Integer, rndMax As Integer)
Dim randomMin As Integer = rnd1.Next(Min + 1)
Return (randomMin, rnd2.Next(Min - randomMin, Max - randomMin + 1))
End Function
If you want the method to directly return the sum, you could change the method return type like this:
Dim Min As Integer = 120
Dim Max As Integer = 235
For i = 0 To 100
Console.WriteLine(GetSumRandomNumbersInRange(Min, Max))
Next
'(...)
Private Function GetSumRandomNumbersInRange(Min As Integer, Max As Integer) As Integer
Dim randomMin As Integer = rnd1.Next(Min + 1)
Return randomMin + rnd2.Next(Min - randomMin, Max - randomMin + 1)
End Function
The random numbers could also be selected with:
randomMid(MaxValue - MinValue) => (MinValue, MaxValue - randomMid)
It this case, possibly implemented as:
Private Function GetSumRandomNumbersInRange2(Min As Integer, Max As Integer) As Integer
Dim randomFirst As Integer = rnd1.Next(Max - Min + 1)
Return randomFirst + rnd2.Next(Min, Max - randomFirst + 1)
End Function
Here is the algorithm I would use to accomplish this task:
Generate a single random number in the range 120 to 235. That will be the final sum.
Generate six random numbers in the range 0.0 to 1.0.
Normalise those six numbers, i.e. divide each by the sum of all six.
Multiply each of the six normalised numbers by the original random number.
Round each of the six results.
You now have six random numbers with a sum in the desired range. You may just need to add or subtract 1 to one of the values in case rounding has pushed the sum outside the range. You can choose one of the numbers at random to adjust.
'Create a random number generator.
Dim rng As New Random
'Create a random number in the desired range for the final sum.
Dim sum = rng.Next(120, 235 + 1)
'Generate six proportional values as fractions of 1.0.
Dim proportions = Enumerable.Range(1, 6).Select(Function(n) rng.NextDouble()).ToArray()
'Get the sum of all the proportional values.
Dim proportionsSum = proportions.Sum()
'Normalise the proportional values so that they sum to 1.0
proportions = Array.ConvertAll(proportions, Function(r) r / proportionsSum)
'Break the final sum up into the specified proportions.
Dim numbers = Array.ConvertAll(proportions, Function(r) CInt(Math.Round(r * sum)))
'Adjust as required if rounding has pushed the sum below the minimum value.
Do While numbers.Sum() < 120
'Get a random element index.
Dim index = rng.Next(0, numbers.Length)
'Increment the element at that index.
numbers(index) = numbers(index) + 1
Loop
'Adjust as required if rounding has pushed the sum above the maximum value.
Do While numbers.Sum() > 235
'Get a random element index.
Dim index = rng.Next(0, numbers.Length)
'Decrement the element at that index.
numbers(index) = numbers(index) - 1
Loop
'The numbers array now contains six random values with a sum in the range 120 to 235.

Optimization of matrix multiplication in vb.net

I'm currently trying to do some work on a neural network class in visual basic. My main drag at the moment is the multiplication of the matrices is so slow! Here's the code I use right now;
Public Function MultiplyMatricesParallel(ByVal matA As Double()(), ByVal matB As Double()()) As Double()()
Dim matACols As Integer = matA(0).GetLength(0)
Dim matBCols As Integer = matB(0).GetLength(0)
Dim matARows As Integer = matA.GetLength(0)
Dim result(matARows - 1)() As Double
For i = 0 To matARows - 1
ReDim result(i)(matBCols - 1)
Next
Dim tempMat()() As Double = MatrixTranspose(matB)
Parallel.For(0, matARows, Sub(i)
For j As Integer = 0 To matBCols - 1
Dim temp As Double = 0
Dim maA() As Double = matA(i)
Dim maB() As Double = tempMat(j)
For k As Integer = 0 To matACols - 1
temp += maA(k) * maB(k)
Next
result(i)(j) += temp
Next
End Sub)
Return result
End Function
I just jagged arrays as they are quicker in vb than rectangular arrays. Of course they really are rectangular otherwise the matrix multiplication wouldn't work (or make sense). Any advice/help would be appreciated, for reference the matrix size can change but it's currently around 7,000 x 2,000 at the maximum.

how do i change the colour of a button based on a string input

For my computing project I am creating a game of risk this requires me to randomly generate the initial countries of the players (I have started with 2 players for simplicity), for this I am going to have the buttons (which represent the countries) to change colour to denote which of the players country it is.
I have tried to assign each player a string value of their colour then set the button back colour to that value. The problem with that at the moment is with the line of "b.Tag.BackColor = Players_Colours(N)" as the value that Players_Colours(N) gives is "S"c for some reason.
Any ideas why that is the case or anything would be appreciated.
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
Public Function Random_country_picker(ByVal players)
Dim County_List = {"Oxfordshire", "Buckinhamshire", "Berkshire", "London", "Hampshire", "Surrey", "West_Sussex", "East_Sussex", "Kent", "Bedfordshire", "Cambridgeshire", "Hertfordshire", "Essex", "Cornwall", "Devon", "Gloucestershire", "Whiltshire", "Lancashire", "Cumbria", "Cheshire", "Manchester", "Merseyside", "Lincolnshire", "Staffordshire", "Herefordshire", "County_Durham", "North_Yorkshire", "South_Yorkshire", "Warwickshire", "Northumberland", "West_Yorkshire", "East_Yorkshire", "Shropshire", "Northamptonshire", "Derbyshire", "Nottinghamshire", "Worcestershire", "Somerset", "Dorset", "Isle_Of_White", "Norfolk", "Suffolk"}
Dim value As String
Dim num = 0
Dim nump = 42 / players
Dim N As Integer
Dim Players_Colours As String
Dim z As String
Players_Colours = {"Pink", "Red"}.ToString
N = 0
Randomize()
Dim rnd As New Random()
Dim index As Integer
While nump > num
If N > Players_Colours.Length Then
N = 0
End If
index = rnd.Next(0, County_List.Length)
value = County_List(index)
For Each b As Windows.Forms.Button In Me.Controls
If b.Tag = index Then
b.Tag.BackColor = Players_Colours(N)
End If
Next
Array.Clear(County_List, index, 1)
num += 1
N += 1
End While
Return 0
End Function
Random_country_picker(2)

VBA comparing multiple variables

There any way to compare multiple variables in VBA? For example:
Dim x As Integer
Dim y As Integer
Dim z As Integer
x = 99
y = 2
z = 3
I would like to return the smallest of the values.
I understand I could use select case x > y for all possible permutations but that seems unwieldy for more than 3 variables.
I have tried the worksheet function
solution = Application.WorksheetFunction.Min(x, y, z)
but that returns 2 and I would like it to return the variable name to be passed to another function.
many thanks,
Edit: My apologies if this was confusing, I am still a VBA novice. Here's my problem a little more generally:
I have a list of codes that correspond to names, many names per code. I want to loop through every name per code and count the number of instances that name appears on a list and choose the name with the LEAST occurrences. (could be 0 or could be the same as another name). obviously if there were 2 names it would be easy to do a if x>y then but I'm stumped as for comparing more than 3. Thanks for reading.
Use a public array rather than multiple variables. This will make it easy to iterate through them all and get the highest value, as well as reference the variable with the highest value later on:
Public myArray(0 To 2) As Integer
Public index As Integer
Public Sub calcMin()
Dim i As Integer
Dim maxValue As Integer
myArray(0) = 99
myArray(1) = 2
myArray(2) = 3
For i = 0 To UBound(myArray)
If myArray(i) < maxValue Then
maxValue = myArray(i)
index = i
End If
Next i
End Sub
Function yourFunction(valueToPass As Integer)
'your function's code here
End Function
Then pass the variable to yourFunction like so: yourFunction(myArray(index))
Same idea as Mike's but with an example to call a sub with the min value found:
Sub main()
Dim arrComp(2) As Integer
arrComp(0) = 99
arrComp(1) = 2
arrComp(2) = 3
'It is important to initialize the tmpVal to a value from the array
'to consider the chance where negative and positive values are used
Dim tmpVal As Integer: tmpVal = arrComp(LBound(arrComp))
Dim i As Integer, minIndex As Integer
For i = LBound(arrComp) To UBound(arrComp)
If arrComp(i) < tmpVal Then
tmpVal = arrComp(i)
minIndex = i
End If
Next i
showMinVal arrComp(minIndex)
End Sub
Sub showMinVal(MinVal As Integer)
MsgBox "The min value is " & MinVal
End Sub
Or, a workaround if you want the name associated to the value, you could define a new Type:
'Types must be declared at the top of the module
Type tVarName
varName As String
varVal As Integer
End Type
Sub main()
Dim arrComp(2) As tVarName
arrComp(0).varName = "x"
arrComp(0).varVal = 99
arrComp(1).varName = "y"
arrComp(1).varVal = 2
arrComp(2).varName = "z"
arrComp(2).varVal = 3
Dim tmpVal As Integer: tmpVal = arrComp(LBound(arrComp)).varVal
Dim i As Integer, minIndex As Integer
For i = LBound(arrComp) To UBound(arrComp)
If arrComp(i).varVal < tmpVal Then
tmpVal = arrComp(i).varVal
minIndex = i
End If
Next i
showMinVal arrComp(minIndex)
End Sub
'Sub showing min value along with the name associated to it
Sub showMinVal(MinVal As tVarName)
MsgBox "The min value is " & MinVal.varName & " = " & MinVal.varVal
End Sub

How to get each value in an array and show it in a listbox

Hi I am new to VB and I have problems with using array. My code is like that.
This is class FindFactorsObject.vb
Public Sub FindFactors()
count = 0
temp = Convert.ToInt32(Math.Sqrt(_x))
For i As Integer = 1 To temp
If _x Mod i = 0 Then
ReDim array(count)
array(count) = i
count += 1
End If
Next
So I created an array and stored the results. Now I want to display each value in my array in Form.vb and if it is possible can somebody teach me how to make delay for each of the displayed value. Thanks very much
Always declare your variables, if possible to their exact types, you think they will take care of. When you say 'Now I want to display each value in my array in Form.vb' I understood literally: in the Form so, we will print them on your form
Public Sub FindFactors(_x As Integer)
Dim temp As Integer = Convert.ToInt32(Math.Sqrt(_x))
Dim l As New List(Of Integer)
For i As Integer = 1 To temp
If _x Mod i = 0 Then
l.add(i)
End If
Next
Dim pf As New PointF(20, 20)
For Each i As Integer In l
creategraphics.drawstring(i.ToString, New font(font.fontFamily, 24), brushes.cadetblue, pf)
pf = New PointF(pf.X, pf.Y + 30)
Next
End Sub