I'm making a player-match-up program in Visual Basic. The program is supposed to pick random registered players and and pair them. I'm currently working on the odd-number-of-players-part.
The solution I have is working but is perhaps not that effective. Is there a better way for me to write this code?
The code is supposed to pick the random players and make sure they are not picked again. As you see, for the code to work i must make it loop thousands of times. If I don't some of the players won't show up in the listbox. Is there a better solution???
In case it's confusing "spiller" is norwegian for "player"
For i As Integer = 0 To 100000
Dim spiller1 As Integer
Dim spiller2 As Integer
Do
spiller1 = CInt(Math.Floor(Rnd() * spillerListe.Count))
spiller2 = CInt(Math.Floor(Rnd() * spillerListe.Count))
Loop Until CBool(spiller1 <> spiller2)
If brukteSpillere(spiller1) = False And brukteSpillere(spiller2) = False Then
brukteSpillere(spiller1) = True
brukteSpillere(spiller2) = True
lstSpillere.Items.Add(spillerListe(spiller1).ToString + " VS. " + spillerListe(spiller2).ToString())
End If
Next i
This is a mess... Have a List(Of Integer) with all the available index.
Loop while availableIndex.Count > 1
Pick a random index from availableIndex and remove it from that list
Pick a random index from availableIndex and remove it from that list
Add these two index to the list of pairs
End Loop
that way you don't need to check if the random values are the same or if they were already picked.
Now, if you don't want to create a list. Then threat the random number not as an index, but as the number of items to check.
Delta = RandomNumber
x = 0
For i As Integer = 0 To itemList.Count-1
If Not itemList(i).IsChoosen Then
x += 1
If x = Delta Then
' i is now your item to pick
itemList(i).IsChoosen = True
Exit For
End If
End If
Next
There are two efficient ways in approaching this problem:
Sort your player list by random number, then match up 1 with 2, 3 with 4 and so on.
Dim r As New Random
Dim randomListe = spillerListe.OrderBy(Function() r.Next).ToList
Generate two random numbers from your range, match up those players into a separate List, remove players from the original list. General two more random numbers from a smaller range (original minus 2), match up, etc.
EDIT: Having looked at MSDN, List has O(n) performance for RemoveAt, so it's not quite efficient, better be using a dictionary, which is O(1) at removing items, so instead of spillerListe have some spillerDicte, where you would add entries in a form (key = index, value = item).
Instead of working with integers, what if you keep your players name in a list and, after picking a player you remove it from the list. Probably this will not be the best performant solution, but it is clear what are you trying to do
Dim lstSpillere = new List(Of String)() ' Just for the example below
Dim spillerListe = new List(Of String)() from {"Marc", "John", "Steve", "George", "David", "Jeremy", "Andrew" }
Dim rnd = new Random()
While spillerListe.Count > 1
Dim firstPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(firstPlayer)
Dim secondPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(secondPlayer)
lstSpillere.Add(firstPlayer + " VS. " + secondPlayer)
' for debug purpose....
Console.WriteLine(firstPlayer & " VS. " & secondPlayer)
End While
if spillerListe.Count > 0 Then
Console.WriteLine("Excluded from play list is:" & spillerListe(0))
End if
The important key here is the generation of Random instance that should be outside the loop to avoid to generate the same number in the short time period required by the loop to execute.
Try this:
Module Module1
Dim rnd As New Random
Sub Main()
Dim RegisteredPlayers As New List(Of Player)
' Fill List (example 100 players)
For index As Integer = 1 To 100
RegisteredPlayers.Add(New Player(String.Format("Player{0}", index)))
Next
'Sort Players using a random number
Dim SortedPlayersArray = RandomSortItems(RegisteredPlayers.ToArray())
'Pair players by selecting 2 consequative ones from randomly sorted array
Dim Matches As New List(Of Match)
For index As Integer = 1 To SortedPlayersArray.Length Step 2
Dim m As Match = New Match(SortedPlayersArray(index - 1), SortedPlayersArray(index))
Matches.Add(m)
Debug.WriteLine(m.ToString())
Next
' Match Player48 vs. Player79
' Match Player3 vs. Player53
' Match Player18 vs. Player43
' Match Player85 vs. Player1
' Match Player47 vs. Player56
' Match Player23 vs. Player66
' etc..
End Sub
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim sorted As T() = New T(items.Length-1) {}
Array.Copy(items, sorted, sorted.Length)
Dim keys As Double() = New Double(items.Length-1) {}
For i As Integer = 1 To items.Length
keys(i - 1) = rnd.NextDouble()
Next
Array.Sort(keys, sorted)
Return sorted
End Function
End Module1
Public Class Player
Dim m_name As String
Public Sub New(ByVal player_name As String)
m_name = player_name
End Sub
Public ReadOnly Property Name() As String
Get
Return m_name
End Get
End Property
Public Overrides Function ToString() As String
Return m_name
End Function
End Class
Public Class Match
Dim m_player_1 As Player, m_player_2 As Player
Public Sub New(ByVal player_1 As Player, ByVal player_2 As Player)
m_player_1 = player_1
m_player_2 = player_2
End Sub
Public ReadOnly Property Player1() As Player
Get
Return m_player_1
End Get
End Property
Public ReadOnly Property Player2() As Player
Get
Return m_player_2
End Get
End Property
Public Overrides Function ToString() As String
Return String.Format("Match {0} vs. {1}", Player1, Player2)
End Function
End Class
Edit 1:
An alternate random sorter (which should be faster) is
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim slist As New SortedList(Of Double, T)
For i As Integer = 1 to items.Length
slist.Add(rnd.NextDouble(), items(i-1) )
Next i
return slist.Values.ToArray()
End Function
Related
I'm populating a DataGridView from an Excel file, and trying to sort only ONE column of my choice, other columns should remain as-is. How can it be achieved? Should the component be changed to something else, in case it is not possible in DataGridView?
I Created a List of my custom class, and this class will handle the sorting based on my preference (Randomization in this case)
Public Class Mylist
Implements IComparable(Of Mylist)
Private p_name As String
Private r_id As Integer
Public Property Pname() As String 'This will hold the contents of DGV that I want sorted
Get
Return p_name
End Get
Set(value As String)
p_name = value
End Set
End Property
Public Property Rid() As Integer 'This will be the basis of sort
Get
Return r_id
End Get
Set(value As Integer)
r_id = value
End Set
End Property
Private Function IComparable_CompareTo(other As Mylist) As Integer Implements IComparable(Of Mylist).CompareTo
If other Is Nothing Then
Return 1
Else
Return Me.Rid.CompareTo(other.Rid)
End If
End Function
End Class
Then a Button which will sort the contents:
Dim selcol = xlView.CurrentCell.ColumnIndex
Dim rand = New Random()
Dim x As Integer = 0
Dim plist As New List(Of Mylist)
Do While x < xlView.Rows.Count
plist.Add(New Mylist() With {
.Pname = xlView.Rows(x).Cells(selcol).Value,
.Rid = rand.Next()
})
x += 1
Loop
plist.Sort()
x = 0
Do While x < xlView.Rows.Count
xlView.Rows(x).Cells(selcol).Value = plist.ElementAt(x).Pname
x += 1
Loop
xlView.Update()
plist.Clear()
I'm open to any changes to code, as long as it achieves the same result.
Here is the simpler version. Pass the column index will do like Call SortSingleColum(0)
Private Sub SortSingleColumn(x As Integer)
Dim DataCollection As New List(Of String)
For i = 0 To dgvImport.RowCount - 2
DataCollection.Add(dgvImport.Item(x, i).Value)
Next
Dim t As Integer = 0
For Each item As String In DataCollection.OrderBy(Function(z) z.ToString)
dgvImport.Item(x, t).Value = item
t = t + 1
Next
End Sub
Trying to loop through the results of a stored procedure using entity framework. I need to compare the list of communities to the value in a textbox so the user does not enter duplicate communities in the database, using a duplicate flag. I can retrieve my list of communities but I'm having difficulty looping through that list.
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For Each n As String In list.CommunityName
If n = txtCommunities.Text Then
duplicate = True
End If
Next n
While debugging I can hover over Accommodations.GetCommunities and see all of the values I need to loop through under the field "CommunityName" but when I step through the loop, the value for n shows up as a single character. Is there a way to turn this result set into a list so that I can loop through each value under "CommunityName"
I've also tried the below code and it sets com equal to the name of the complex type for some reason, but it properly loops through the correct number of items in the list. How can I extract that field to compare it to the textbox?
Dim duplicate = False
Dim com As String = String.Empty
Dim list = Accommodations.GetCommunities
For i As Integer = 0 To list.count - 1
com = list(i).ToString
If com = txtCommunities.Text Then
duplicate = True
End If
Next
Return duplicate
FINAL EDIT:
This is the function I used after. Used the String.Compare() method to compare the strings to ignore the case as well.
Private Function checkDuplicates()
Dim duplicate = False
Dim list = Accommodations.GetCommunities 'Put items in a list
For i As Integer = 0 To list.count - 1 'loop through the list
If String.Compare(list(i).CommunityName, txtCommunities.Text, True) = 0 Then 'Compare the two strings, comparrison is not case sensitive
duplicate = True 'set dup flag to true
Exit For 'exit loop
End If
Next
Return duplicate
End Function
In the first case you are comparing n to each character in CommunityName. You should do something like:
For Each n As String In list
If n.CommunityName = txtCommunities.Text Then
In the second case list(i) is a community in the list. Thus list(i).ToString() shows the name of the variable.
I think it should look something more like
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For Each community As [something] In list
If community.CommunityName = txtCommunities.Text Then
duplicate = True
End If
Next n
or
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For i As Integer = 0 To list.count - 1
If list(i).CommunityName = txtCommunities.Text Then
duplicate = True
End If
Next
Return duplicate
Side note: Try to use good variable names instead of just n. Also, when you find a duplicate, you can exit for loop or just return True right away.
Sample 1
Ther is also option using dictionary, If u have your list as a dictionary u don't need looping after if u wana check add or remove by unique key value
Public Class TestClass
Property Name As String
End Class
Private Function TestFun() As Boolean
'Sample List to convert use Accommodations.GetCommunities
Dim List As New List(Of TestClass)
List.Add(New TestClass With {.Name = "a"})
List.Add(New TestClass With {.Name = "b"})
'If at Begining all elements by key are unique u can convert to dictionary
'
'From This Point u can map your list in to dictionary Use Accommodations.GetCommunities instant List. and type of what use in this collection replece as TestClass
Dim CheckInDictionary As Dictionary(Of String, TestClass) = List.ToDictionary(Function(p) p.Name, Function(p) p)
' After you can us if some key or Id exist
Return CheckInDictionary.ContainsKey("a")
End Function 'Return True
Sample 2
Ther is Also Another Possibility to Check if List have duplicate, but this is abit more complex.
List have method Contains that check if element exist in said list, so before add new element you can check if list have it. To Compare is used Method Equal so if you overide it in your base class you can do special rules for equal.
Like
Public Class TestClass
Property Name As String
Public Overrides Function Equals(obj As Object) As Boolean
'IMPORTEND Input is as object if anny case will be somthing diffrent then this type it can meak exception
If DirectCast(obj, TestClass).Name = Me.Name Then Return True
Return MyBase.Equals(obj)
End Function
End Class
and after when u atempt to add new element u dolike that
Dim NewEle = New TestClass With {.Name = "a"}
If Not List.Contains(NewEle) Then
List.Add(NewEle)
End If
I would like to find the largest integer(s) between 3 integers.
I could do this by nesting If statements. Since I have further code to write however this would be long and untidy.
I was wondering if there was an easier way to find the largest integer(s) (including if let's say A and B are equal but both higher than C).
P.S Can you do this with 2-D arrays?
Use LINQ to do this:
Dim numbers() As Integer = {1, 3, 5}
Dim max As Integer = numbers.Max()
Debug.Write("Max number in numbers() is " & max.ToString())
Output:
Edited as per conversation with OP on wanting to know which genre was ranked the best.
When asked How do you get the data? OP responds with:
I have a text file containing movie|genre on every line. I read this and count which genre (out of 3) is the highest.
I have drafted up some code which reads from a text file and populates a class.
First let me show you the code:
Dim myFilms As New Films
Using sr As New IO.StreamReader("C:\films.txt")
Do Until sr.Peek = -1
Dim columns As String() = sr.ReadLine().Split(New Char() {"|"c}, StringSplitOptions.RemoveEmptyEntries)
'columns(0) = film name
'columns(1) = genre
myFilms.Add(New Film(columns(0), columns(1)))
Loop
End Using
If myFilms.Count > 0 Then
Dim bestGenre = myFilms.GetBestGenre()
'Go off and read the genre file based on bestGenre
End If
From the above code you can see the class Films being populated with a new Film. I then call a method from the Films class, but only if there are films to choose from. Let me show you the class structure for both these:
Film:
Public Class Film
Public Key As String
Public Sub New(ByVal filmName As String,
ByVal genre As String)
_filmName = filmName
_genre = genre
End Sub
Private _filmName As String
Public ReadOnly Property FilmName As String
Get
Return _filmName
End Get
End Property
Private _genre As String
Public ReadOnly Property Genre As String
Get
Return _genre
End Get
End Property
End Class
Films:
Public Class Films
Inherits KeyedCollection(Of String, Film)
Protected Overrides Function GetKeyForItem(ByVal item As Film) As String
Return item.Key
End Function
Public Function GetBestGenre() As String
Return Me.GroupBy(Function(r) r.Genre).OrderByDescending(Function(g) g.Count()).First().Key
End Function
End Class
I must note that although this code does work it may come unstuck if you have 2 or more genres which are joint top. The code still works however it only returns one of the genres. You may want to expand on the code to suit your needs based on that scenario.
Try something like this:
Dim max As Integer
max = integer1
If integer2 > max Then
max = integer2
End If
If integer3 > max Then
max = integer3
End If
Not many more ways that I can think of off the top of my head to do this.
Something along these lines will work for any number of integers.
Put the numbers into an array then use a For[...]Next statement to loop through the array comparing the current member with max. If max is lower, set it to the current member. When the loop terminates, max will contain the highest number:
Dim nums() As Integer = {1, 2, 3}
Dim max As Integer
For i = 0 To nums.Length - 1
If max < nums(i) Then
max = nums(i)
End If
Next
I have some two column data that I read from a file and put into a list then sort alphabetically.
//The file
Hummus,0.75
Chili,0.50
Tabouli,1.25
Tzatziki,0.50
//Declaring the variables and public properties
Dim extraList As List(Of extra)
Public Class extra
Implements IComparable(Of extra)
Public Property Name As String
Public Property Price As Decimal
'Public Property extraList As List(Of extra)
Public Function CompareTo(other As extra) As Integer Implements IComparable(Of extra).CompareTo
Return Me.Name.CompareTo(other.Name)
End Function
End Class
//Puts the data into a list and sorts it
Sub Get_Extras_List()
'Reads the extras file and puts the information into a list, splitting the name of the extra and the price into separate columns
Dim allExtras = From line In System.IO.File.ReadLines("C:\Users\ExtrasList.txt")
Let Columns = line.Split(","c)
Where Columns.Length = 2
Let Price = Decimal.Parse(Columns(1).Trim())
Let Name = Columns(0).Trim()
Select New extra With {.Name = Name, .Price = Price}
extraList = allExtras.ToList()
'Sort the list alphabetically
extraList.Sort()
End Sub
Now I need to code a method that allows the user to type in an extra and search for it using a binary search to see if it exists. So far I have tried this but it just doesn't work and even if it did how do I get it to return a true or false value? (If it exists or not?)
Sub Search_Extras_List()
Dim strSearchExtra As String = Console.ReadLine()
Console.WriteLine(vbLf & "BinarySearch for '{0}':", strSearchExtra)
Dim index As Integer =
List(Of extra).BinarySearch(extraList.Name, strSearchExtra)
End Sub
Finally I have to get the user to choose one of the extras and then add the price of it to the total price. How do I refer to the price? extraList.Price? extra.Price? etc.
If you want to do a binary search like that, you will need to write a comparer.
Referring to List(Of T).BinarySearch Method (T, IComparer(Of T)), it could be
Public Class ExtraComparer
Implements IComparer(Of extra)
Public Function Compare(ByVal x As extra, ByVal y As extra) As Integer Implements IComparer(Of extra).Compare
If x Is Nothing Then
If y Is Nothing Then
' If x is Nothing and y is Nothing, they're equal.
Return 0
Else
' If x is Nothing and y is not Nothing, y is greater.
Return -1
End If
Else
' If x is not Nothing...
If y Is Nothing Then
' ...and y is Nothing, x is greater.
Return 1
Else
' ...and y is not Nothing, compare the names of the two extras.
'
Return x.Name.CompareTo(y.Name)
End If
End If
End Function
End Class
Which you can test with
' Get some item from the data so we know it is present...
Dim a = extraList(2).Name
Dim lookFor As New extra With {.Name = a}
Dim idx = extraList.BinarySearch(lookFor, New ExtraComparer)
Console.WriteLine($"Index of {a} is {idx}.")
(although you would probably want to do a case-insensitive string comparison).
If the index returned is negative then the item was not found. (See the documentation linked to above for more details.)
However, you might find it easier to use LINQ:
Dim a = extraList(2).Name
Dim chosenExtra = extraList.FirstOrDefault(Function(x) x.Name.Equals(a, StringComparison.CurrentCultureIgnoreCase))
If chosenExtra IsNot Nothing Then
Console.WriteLine($"User chose {chosenExtra.Name} at a price of {chosenExtra.Price}")
Else
Console.WriteLine($"User's choice of ""{a}"" was not found.")
End If
I used a case-insensitive comparison in there.
Or Just present the user with a dropdown of the available extras so that they don't have to type it.
I'm trying to find a way of loading a single record with 25 columns into a datatable.
I could list all 25 variables called SPOT1 to SPOT25 (columns in the datatable) but I'm looking for more concise method like using a loop or dictionary.
The code below shows two methods, a 'long' method which is cumbersome, and a 'concise' method which I'm trying to get help with.
Public dctMC As Dictionary(Of String, VariantType)
Dim newMC As New MONTE_CARLO()
'long method: this will work but is cumbersome
newMC.SPOT1=999
newMC.SPOT2=887
...
newMC.SPOT25=5
'concise method: can it be done more concisely, like in a loop for example?
Dim k As String
For x = 1 To 25
k = "SPOT" & CStr(x)
newMC.K = dctMC(k) 'convert newMC.k to newMC.SPOT1 etc
Next
'load record
DATA.MONTE_CARLOs.InsertOnSubmit(newMC)
Per the others, I think there are better solutions, but it is possible...
Public Class MONTE_CARLO
Private mintSpot(-1) As Integer
Property Spot(index As Integer) As Integer
Get
If index > mintSpot.GetUpperBound(0) Then
ReDim Preserve mintSpot(index)
End If
Return mintSpot(index)
End Get
Set(value As Integer)
If index > mintSpot.GetUpperBound(0) Then
ReDim Preserve mintSpot(index)
End If
mintSpot(index) = value
End Set
End Property
End Class
Usage...
Dim newMC As New MONTE_CARLO
For i As Integer = 0 To 100
newMC.Spot(i) = i
Next i
MsgBox(newMC.Spot(20))