I am making a program that outputs random numbers and then organizes them.
I am organizing the numbers so later I can add code to tell the user how many matching numbers he or she has received.
The program compiles fine, but then when I run the exe, after the first line of random numbers is outputted it crashes. The error I receive is:
the index is outside the boundaries of the array.
Any help at all would be gratefully appreciated.
Option Explicit On
Option Strict On
Imports System
Module Yahtzed
Sub Main()
Randomize()
Dim Index, Values, NumberOfPlayers,Temp as Integer
Dim order(index) as integer
Dim Last As Integer = 0 'to Order.Length-2
Console.Write("How many people will be playing Yahtzed?: ")
NumberOfPlayers = convert.toint32(Console.Readline)
Do while NumberOfPlayers > 0
Index = 0
Do until index = 5
Values = CInt(Int((6 * Rnd()) + 1))
Console.Write(" "&values)
Index += 1
Loop
Do Until Index = 0
If Order(Index + 1) < Order(index)
Temp = Order(Index + 1)
Order(Index + 1) = order(index)
Order(index) = Temp
Console.WriteLine(Order(Index))
End if
index -= 1
loop
Console.Writeline
NumberOfPlayers -= 1
Console.Writeline()
Loop
End Sub
End Module
The code doesn't really make sense as it is now. You create some random numbers, then you throw them away, and sort an array that never has been assigned anything. Also, the array only has one item, so it would not be able to hold the random values.
I think that you want to declare the array for five items, not one (as index is zero by the time you create the array):
Dim order(4) As Integer
Then put the random numbers in the array instead of putting them in a variable where each random number will replace the previous one:
Index = 0
Do until index = 5
order(index) = CInt(Int((6 * Rnd()) + 1))
Console.Write(" " & order(index))
Loop
When you sort the array, you start looking at index 6 (as the variable index is 5), which is outside the array. You would want to start at one item from the last in the array (i.e. at index 3). Then you loop until index is -1, otherwise you won't be comparing the two first items in the array.
Also, you have to continue sorting until there are no more swaps, just going through the array once doesn't make it sorted:
Dim swapped as Boolean = True
Do While swapped
index = 3
swapped = False
Do Until index = -1
If order(index + 1) < order(index)
temp = order(index + 1)
order(index + 1) = order(index)
order(index) = temp
swapped = True
End if
index -= 1
Loop
Loop
This sorting algorithm is called Bubble Sort.
There is also sorting built into the framework, if you want to use that instead:
Array.Sort(order)
If you would write out the values while sorting, you would get them several times over, so you do that after they are sorted:
index = 0
Do until index = 5
Console.Write(" " & order(index))
Loop
Related
I have a list
Dim list As New List(Of Double)
I want to remove the last entries, if the differences are > 20.
My idea to check the last 30 entries:
Do While index >= list.Count - 30
If Math.Sqrt((list(index) - list(index + 1)) ^ 2) > 20 Then
list.RemoveAt(index)
Exit Do
End If
Loop
It does not lead to my solution. Can somebody help? Thank you very much.
Instead of using a List of doubles I would use a LinkedList of doubles to make use of the class methods and properties like AddFirst, Last, Last.Previous
So let's assume that you have a LinkedList declared like
Dim list As New LinkedList(Of Double)
And you have added elements to this list using
list.AddFirst(134.5678)
Now, you could remove from the end of the list with something like this
' You want to have a list of at least 30 elements
Do While list.Count > 30
' Last node and the previous one
Dim dLast = list.Last
Dim dLastPrev = list.Last.Previous
' Evaluate the two elements
If Math.Sqrt(dLastPrev.Value - dLast.Value) ^ 2 > 20 Then
' Remove the last and continue to evaluate the next pair
list.RemoveLast()
Else
' Stop if the condition is not met.
Exit Do
End If
Loop
Of course this could also be done using your current list type.
Also notice how I have swapped the two elements to verify. This is done to avoid any possibility of an IndexOutOfRangeException
Dim index as Integer = list.Count - 1
Do While index >= list.Count - 30
Dim prev = index - 1
If Math.Sqrt((list(prev) - list(index)) ^ 2) > 20 Then
list.RemoveAt(index)
else
Exit Do
End If
index = index - 1
Loop
I have six variables, symbolizing three pairs (key/value). It will always be three pairs.
cb1 = 1: cb1value = 10
cb2 = 1: cb2value = 20
cb3 = 8: cb3value = 10
What I'm failing at is aggregating the values in a dictionary according to the key.
So in the above case the result would be:
1, (10, 20)
8, (10)
The end goal here is to use Sum(key) to get the total per key.
EDIT: Thanks for the replies so far. Maybe I'm just thinking too complicated. First I've put all the values in an array and then loop through it.
MyArray = Array(cb1, cb1value, cb2, cb2value, cb3, cb3value)
Now the keys are every 2 steps, so in my loop:
For i = 0 To 5 Step 2
If Not (keywords.Exists(MyArray(i))) Then
keywords.Add MyArray(i), Collection(MyArray(i + 1))
Else
'If the key exists, the value should be added to the existing key's collection. **But how?**
End If
Next i
For i = lbound(MyArray) To UBound(MyArray)-1 Step 2
If Not (keywords.Exists(MyArray(i))) Then keywords.Add MyArray(i), New Collection
keywords(MyArray(i)).Add MyArray(i + 1)
Next i
To get the sum of all entries in a collection:
Function SumCollection(col as Collection)
Dim rv As Double, i
For Each i in col
rv = rv + i
Next i
SumCollection = rv
End Function
If all you need is the sum though, you don't need the collection: just sum directly in the dictionary as you add each item.
I got some help from one, and the code works perfectly fine.
What im looking for, is an explanation of the code, since my basic VBA-knowledge does not provide me with it.
Can someone explain what happens from "Function" and down?
Sub Opgave8()
For i = 2 To 18288
If Left(Worksheets("arab").Cells(i, 12), 6) = "262015" Then
Worksheets("arab").Cells(i, 3) = "18" & UniqueRandDigits(5)
End If
Next i
End Sub
Function UniqueRandDigits(x As Long) As String
Dim i As Long
Dim n As Integer
Dim s As String
Do
n = Int(Rnd() * 10)
If InStr(s, n) = 0 Then
s = s & n
i = i + 1
End If
Loop Until i = x + 1
UniqueRandDigits = s
End Function
n = Int(Rnd()*10) returns a value between 0 and 9, since Rnd returns a value between 0 and 1 and Int converts it to an integer, aka, a natural number.
Then If InStr(s, n) = 0 checks if the random number is already in your result: if not, it adds it using the string concatenation operator &.
This process loops (do) until i = x + 1 where x is your input argument, so you get a string of length x. Then the first part just fills rows with these random strings.
N.B. : I explained using the logical order of the code. Your friend function UniqRandDigits is defined after the "business logic", but it's the root of the code.
The code loops from row 2 to 18288 in Worksheet "arab". If first 6 characters in 12th column are "262015", then in 3rd column macro will fill cell with value "18" followed by result of function UniqueRandDigits(5) which generates 5 unique digits (0-9).
About the UniqueRandDigits function, the most important is that Rnd() returns a value lesser than 1 but greater than or equal to zero.
Int returns integer value, so Int(Rnd() * 10) will generate a random integer number from 0 to 9.
If InStr(s, n) = 0 Then makes sure than generated integer value doesn't exist in already generated digits of this number, because as the function name says, they must be unique.
Sub FWP()
Dim i As Integer
Dim j As Integer
Dim n As Integer
n = Range("A1").Value
For i = 1 To n
For j = 1 To n
If Cells(i + 1, j) = 0 Then
Cells(i + 1, j).Value = Int(((n ^ 2) - 1 + 1) * Rnd + 1)
ElseIf Cells(i + 1, j) <> 0 Then
Cells(i + 1, j).Value = Cells(i + 1, j).Value
End If
Next j
Next i
I am trying to do a part of a homework question that asks to fill in missing spaces in a magic square in VBA. It is set up as a (n x n) matrix with n^2 numbers in; the spaces I need to fill are represented by zeros in the matrix. So far I have some code that goes through checking each individual cell value, and will leave the values alone if not 0, and if the value is 0, it replaces them with a random number between 1 and n^2. The issue is that obviously I'm getting some duplicate values, which isn't allowed, there must be only 1 of each number.
How do I code it so that there will be no duplicate numbers appearing in the grid?
I am attempting to put in a check function to see if they are already in the grid but am not sure how to do it
Thanks
There are a lot of approaches you can take, but #CMArg is right in saying that an array or dictionary is a good way of ensuring that you don't have duplicates.
What you want to avoid is a scenario where each cell takes progressively longer to populate. It isn't a problem for a very small square (e.g. 10x10), but very large squares can get ugly. (If your range is 1-100, and all numbers except 31 are already in the table, it's going to take a long time--100 guesses on average, right?--to pull the one unused number. If the range is 1-40000 (200x200), it will take 40000 guesses to fill the last cell.)
So instead of keeping a list of numbers that have already been used, think about how you can effectively go through and "cross-off" the already used numbers, so that each new cell takes exactly 1 "guess" to populate.
Here's one way you might implement it:
Class: SingleRandoms
Option Explicit
Private mUnusedValues As Scripting.Dictionary
Private mUsedValues As Scripting.Dictionary
Private Sub Class_Initialize()
Set mUnusedValues = New Scripting.Dictionary
Set mUsedValues = New Scripting.Dictionary
End Sub
Public Sub GenerateRange(minimumNumber As Long, maximumNumber As Long)
Dim i As Long
With mUnusedValues
.RemoveAll
For i = minimumNumber To maximumNumber
.Add i, i
Next
End With
End Sub
Public Function GetRandom() As Long
Dim i As Long, keyID As Long
Randomize timer
With mUnusedValues
i = .Count
keyID = Int(Rnd * i)
GetRandom = .Keys(keyID)
.Remove GetRandom
End With
mUsedValues.Add GetRandom, GetRandom
End Function
Public Property Get AvailableValues() As Scripting.Dictionary
Set AvailableValues = mUnusedValues
End Property
Public Property Get UsedValues() As Scripting.Dictionary
Set UsedValues = mUsedValues
End Property
Example of the class in action:
Public Sub getRandoms()
Dim r As SingleRandoms
Set r = New SingleRandoms
With r
.GenerateRange 1, 100
Do Until .AvailableValues.Count = 0
Debug.Print .GetRandom()
Loop
End With
End Sub
Using a collection would actually be more memory efficient and faster than using a dictionary, but the dictionary makes it easier to validate that it's doing what it's supposed to do (since you can use .Exists, etc.).
Nobody is going to do your homework for you. You would only be cheating yourself. Shame on them if they do.
I'm not sure how picky your teacher is, but there are many ways to solve this.
You can put the values of the matrix into an array.
Check if a zero value element exists, if not, break.
Then obtain your potential random number for insertion.
Iterate through the array with a for loop checking each element for this value. If it is not present, replace the zero element.
I'm trying to randomize an array from numbers 0 to 51 using loops but I just cannot seem to pull it off. My idea was that
Generate a Random Number
Check if this random number has been used by storing the previous in an array
If this random number has been used, generate new random number until it is not a duplicate
If it's not a duplicate, store it
My attempt:
Dim list(51) As Integer
Dim templist(51) As Integer
For i As Integer = 0 To 51 Step 1
list(i) = i
Next i
Do While counter <= 51
p = rand.Next(0, 52)
templist(counter) = p
For n As Integer = 0 To 51 Step 1
p = rand.Next(0, 52)
If templist(n) = p Then
Do While templist(n) = p
p = rand.Next(0, 52)
Loop
templist(n) = p
Else
templist(n) = p
End If
Next
counter += 1
Loop
For n As Integer = 0 To 51 Step 1
ListBox1.Items.Add(templist(n))
Next
It will be a lot easier if you just have a list of all of the possible numbers (0 to 51 in your case), then remove the number from the list so it can't be picked again. Try something like this:
Dim allNumbers As New List (Of Integer)
Dim randomNumbers As New List (Of Integer)
Dim rand as New Random
' Fill the list of all numbers
For i As Integer = 0 To 51 Step 1
allNumbers.Add(i)
Next i
' Grab a random entry from the list of all numbers
For i As Integer = 0 To 51 Step 1
Dim selectedIndex as Integer = rand.Next(0, (allNumbers.Count - 1) )
Dim selectedNumber as Integer = allNumbers(selectedIndex)
randomNumbers.Add(selectedNumber)
allNumbers.Remove(selectedNumber)
' Might as well just add the number to ListBox1 here, too
ListBox1.Items.Add(selectedNumber)
Next i
If your goal is to get the numbers into ListBox1, then you don't even need the "randomNumbers" list.
EDIT:
If you must have an array, try something like this:
Function RandomArray(min As Integer, max As Integer) As Integer()
If min >= max Then
Throw New Exception("Min. must be less than Max.)")
End If
Dim count As Integer = (max - min)
Dim randomNumbers(count) As Integer
Dim rand As New Random()
' Since an array of integers sets every number to zero, and zero is possibly within our min/max range (0-51 here),
' we have to initialize every number in the array to something that is outside our min/max range.
If min <= 0 AndAlso max >= 0 Then
For i As Integer = 0 To count
randomNumbers(i) = (min - 1) ' Could also be max + 1
Next i
End If
Dim counter As Integer = 0
' Loop until the array has count # of elements (so counter will be equal to count + 1, since it is incremented AFTER we place a number in the array)
Do Until counter = count + 1
Dim someNumber As Integer = rand.Next(min, max + 1)
' Only add the number if it is not already in the array
If Not randomNumbers.Contains(someNumber) Then
randomNumbers(counter) = someNumber
counter += 1
End If
Loop
Return randomNumbers
End Function
This is good enough for your assignment, but the computer scientist in my hates this algorithm.
Here's why this algorithm is much less desirable. If zero is in your range of numbers, you will have to loop through the array at least 2N times (so 104+ times if you are going from 0 to 51). This is a best case scenario; the time complexity of this algorithm actually gets worse as the range of numbers scales higher. If you try running it from 0 to 100,000 for example, it will fill the first few thousand numbers very quickly, but as it goes on, it will take longer and longer to find a number that isn't already in the list. By the time you get to the last few numbers, you could potentially have randomly generated a few trillion different numbers before you find those last few numbers. If you assume an average complexity of 100000! (100,000 factorial), then the loop is going to execute almost ten to the half-a-millionth power times.
An array is more difficult to "shuffle" because it is a fixed size, so you can't really add and remove items like you can with a list or collection. What you CAN do, though, is fill the array with your numbers in order, then go through a random number of iterations where you randomly swap the positions of two numbers.
Do While counter <= 51
p = rand.Next(0, 52)
While Array.IndexOf(list, p) = -1
p = rand.Next(0, 52)
End While
counter += 1
Loop
Haven't written VB in about 5 years, but try this out:
Function GetRandomUniqueNumbersList(ByVal fromNumber As Integer, ByVal toNumber As Integer) As List(Of Integer)
If (toNumber <= fromNumber) Then
Throw New ArgumentException("toNumber must be greater than fromNumber", toNumber)
End If
Dim random As New Random
Dim randomNumbers As New HashSet(Of Integer)()
Do
randomNumbers.Add(random.Next(fromNumber, toNumber))
Loop While (randomNumbers.Count < toNumber - fromNumber)
Return randomNumbers.ToList()
End Function
Ok, that was painful. Please someone correct it if I made any mistakes. Should be very quick because it's using a HashSet.
First response to forum on stackoverflow - be gentle.
I was looking for a way to do this but couldn't find a suitable example online.
I've had a go myself and eventually got this to work:
Sub addUnique(ByRef tempList, ByVal n, ByRef s)
Dim rand = CInt(Rnd() * 15) + 1
For j = 0 To n
If tempList(j) = rand Then
s = True
End If
Next
If s = False Then
tempList(n) = rand
Else
s = False
addUnique(tempList, n, s)
End If
End Sub
Then call the sub using:
Dim values(15) As Byte
Dim valueSeen As Boolean = False
For i = 0 To 15
addUnique(values, i, valueSeen)
Next
This will randomly add the numbers 1 to 16 into an array. Each time a value is added, the previous values in the array are checked and if any of them are the same as the randomly generated value, s is set to true. If a value is not found (s=false), then the randomly generated value is added. The sub is recursively called again if s is still true at the end of the 'For' loop. Probably need 'Randomize()' in there somewhere.
Apologies if layout is a bit wobbly.