I am creating a poker game - Texas Holdem (5 cards on the table and 2 cards for myself).
I've already created flush, straight and straight flush functions and I got stuck on evaluating if the hand has a:
1. Four of a kind
2. Three of a kind
3. Full house
4. Two pairs
5. One pair
I believe I can write one function for all of the above, that will return a corresponding string.
I have created a list that holds list of Cards (7 cards)
Class Card has a property cardNumber of Integer type (Ace = 1, Two = 2, Three = 3 etc)
Here is my function:
Public Shared Function ofAKind(hand As List(Of Card)) As String
Dim result As String = ""
Dim counter As Integer
Dim IntegerList As New List(Of Integer)
'creating a list of integers that are representing faces of cards
Do
IntegerList.Add(hand.Item(counter).cardNumber)
counter += 1
Loop Until counter = hand.Count
Dim groupedIntegers = From Int In IntegerList
Group By Int
Into grouping = Group, Count()
'and here is my problem: how can I make such a grouping? below is just pseudocode.
'When using a debugger, I see that it groups them well. It is just that I do not know
'how to use LINQ to extract that grouping into the below if statement and get a corresponding string.
'if grouping = 4 Then
'result = "Four of a kind"
'if grouping = 3 andAlso grouping = 2 Then
'result = "Full House"
'if grouping = 2 andAlso grouping = 2 Then
'result = "Two Pairs"
'if grouping = 2 Then
'result = "Pair"
Return result
End Function
For the lack of being able to comment.
Possibly String.Concat all of the card Values together (with whitespace in-between each) and use a Regex.Matches(...) with match code "\d" to match the Numbers
Then Array.ForEach(...) for the Groups() with an in-line If[...] to count the occurrences in each group and test if it has particular combinations of matches.
It may be a little tedious, and a long in-line Linq, but just a thought :p
I figured it out. I am sure it can be done in a cleaner way, but it worked for me. At this phase of my programming discovery - this is a next milestone achieved. Thanks to Plutonix. Appreciate it.
Public Function ofAKind(IntegerList As List(Of Integer)) As String
Dim result As String = "YES"
Dim groupedIntegerList As New List(Of Integer)
Dim groupedIntegers = From Int In IntegerList
Group By Int
Into LOL = Group, Count()
'creating another list (I am sure there is a cleaner way, but I don't know it yet)
For Each e In groupedIntegers
groupedIntegerList.Add(e.Count)
Next
If groupedIntegerList.Contains(3) And groupedIntegerList.Contains(2) Then
result = "Fullhouse!"
ElseIf groupedIntegerList.Contains(4) Then
result = "Four of a kind!"
ElseIf groupedIntegerList.Contains(3) Then
result = "Three of a kind"
ElseIf groupedIntegerList.Contains(2) Then
result = "Pair!"
End If
'ugly way to search for two pairs (but it works)
If result = "Pair!" Then
Dim searchingForTwoPairs = From int In groupedIntegerList
Where int > 1
Group By int
Into LOL2 = Group, Count()
Dim twoPairsList As New List(Of Integer)
For Each e In searchingForTwoPairs
twoPairsList.Add(e.Count)
Next
If twoPairsList.Contains(2) Or twoPairsList.Contains(3) Then
result = "Two pairs!"
End If
End If
Return result
End Function
Related
I have got a list of Strings looking like this:
The items of the list of Strings are formated like this "#,#" where # stands for an integer number and # stands for a string or number.
I need to find the index of the first occurrence where the integer number is lower than the integer number of the previous item.
I am able to find it using a loop over all entries like this:
For X = 0 To UBound(TempList.ToArray) - 1
If Val(Left(TempList(X), InStr(TempList(X), ",") - 1)) > Val(Left(TempList(X + 1), InStr(TempList(X + 1), ",") - 1)) Then
Dim Result As String = TempList(X) & " -- " & TempList(X + 1)
End If
Next
I am sure that this can be done much smarter using linq - but my very poor knowledge regarding linq is not enough ...
Can someone please give me a hint?
Linq is cool but it is not necessarily faster. It is looping internally.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim TempList As New List(Of String) From {"450,245.5", "510,1", "520,1", "703,1", "704,0", "705,0", "706,0", "901,1.244", "921,3", "1,1", "2,1", "3,0"}
Dim Result As String = ""
For i As Integer = 1 To TempList.Count - 1
If CInt(TempList(i).Split(","c)(0)) < CInt(TempList(i - 1).Split(","c)(0)) Then
Result = $"{TempList(i)} At index {i} -- {TempList(i - 1)}"
Exit For 'Stop looping when found
End If
Next
MessageBox.Show(Result)
End Sub
You can use LINQ for the loop, but I do think a For / For Each is sufficient - see Mary's answer.
But at least, you could use LINQ to parse your list into something other than strings to make it more manageable when it's used. This will decouple your code a bit and will make it clearer. I'll hold the pair of data in a Tuple(Of Double, String). If I knew the first item was distinct, I would use a Dictionary(Of Double, String).
So for fun, you can use TakeWhile instead of a For / For Each.
Dim TempList = { "450,245.5", "510,1", "520,1", "701,0", "702,0", "703,1", "704,0", "705,0", "706,0", "720,0", "901.2,455", "921,3", "1,1", "2,1", "3,0"}
Dim parsedList = TempList.
Select(Function(s) s.Split(","c)).
Select(Function(ss) (CDbl(ss(0)), ss(1)))
Dim lastValue As Double = Double.NaN
Dim index = parsedList.TakeWhile(
Function(t)
Dim result = Double.IsNaN(lastValue) OrElse t.Item1 > lastValue
lastValue = t.Item1
Return result
End Function).Count()
Dim item = parsedList(index)
Console.WriteLine($"Index: {index}, number: {item.Item1}, string: {item.Item2}")
Index: 12, number: 1, string: 1
This is basically just making a For Each with LINQ. It's probably not necessary, and a simple loop is arguably more readable, and you won't gain any benefit in performance either.
i am trying to sum values of same id in datagridview and trying to update sql database table. how to do it?
For i As Integer = 0 To DataGridView2.Rows.Count - 1
If Convert.ToInt32(DataGridView2.Rows(i).Cells(0).Value) = Convert.ToInt32(DataGridView2.Rows(i).Cells(0).Value) Then
y += Convert.ToInt32(DataGridView2.Rows(i).Cells(3).Value)
End If
Next
As you have been already told, the if statement you are coding is comparing a cell with itself, which doesn't make sense, because it Will always return true.
I'm guessing that you need to compare all cells that share the same id and sum the result, but you would need to elaborate the question better, so we can understand what your actuall purpose is.
you can use Dictionary to check and group id with sum values
Dim totals As Dictionary(Of String, Integer) = New Dictionary(Of String, Integer)()
For i As Integer = 0 To DataGridView2.Rows.Count - 1
Dim group As String = Convert.ToInt32(DataGridView2.Rows(i).Cells("id").Value)
Dim qty As Integer = Convert.ToInt32(DataGridView2.Rows(i).Cells("value").Value)
If totals.ContainsKey(group) = False Then
totals.Add(group, qty)
Else
totals(group) += qty
End If
Next
or use group with LINQ:
Dim totals = DataGridView2.Rows.Cast(Of DataGridViewRow)().GroupBy(Function(row) row.Cells("id").Value.ToString()).[Select](Function(g) New With {Key
.id= g.Key, Key
.SumValues = g.Sum(Function(row) Convert.ToInt32(row.Cells("value").Value))
})
I have this textfile:
Paul George|2.87|29
Stephen Curry|2.85|28
Jamal Murray|2.72|21
PJ Tucker|2.72|11
Kyle Lowry|2.61|15
Game
Paul George|g2d|g2p
Stephen Curry|g2d|g2p
Jamal Murray|g2d|g2p
PJ Tucker|g2d|g2p
Kyle Lowry|g2d|g2p
Game
Paul George|g3d|g3p
Stephen Curry|g3d|g3p
Jamal Murray|g3d|g3p
PJ Tucker|g3d|g3p
Kyle Lowry|g3d|g3p
Game
Paul George|g4d|g4p
Stephen Curry|g4d|g4p
Jamal Murray|g4d|g4p
PJ Tucker|g4d|g4p
Kyle Lowry|g4d|g4p
I want to add the items to the arrays
Names(name, gamenumber)
Distance(distance, gamenumber)
Points(Points, gamenumber)
with the first index being the data for the player, and the second being the game that data is from
For example,
distance(1, 0) = 2.87
distance(5, 0) = 2.61
distance(1, 1) = g2d
So that the indexes match up with the player for the given game number.
So far I have:
Private Sub openFile_Click(sender As Object, e As EventArgs) Handles openFile.Click
OpenFileDialog.ShowDialog()
Dim strFileName = OpenFileDialog.FileName
Dim objReader As New System.IO.StreamReader(strFileName)
Dim textline As String
Dim Names(100, 3) As String
Dim Distance(100, 3) As String
Dim Points(100, 3) As String
Dim Count As Integer = 0
Dim GameNumber As Integer = 0
Do While objReader.Peek() <> -1
textline = objReader.ReadLine() & vbNewLine
If textline = "Game" Then
GameNumber = GameNumber + 1
Else
Dim parts() As String = textline.Split("|")
Names(Count, GameNumber) = parts(0)
Distance(Count, GameNumber) = parts(1)
Points(Count, GameNumber) = parts(2)
Count = Count + 1
End If
Loop
End Sub
The parts of each line are split up by |, putting them into "parts", it then assigns the three parts it gets from the line (the player name, distance, and points) into there separate arrays as
Names(<Name>, 0)
Distance(<Distance>, 0)
Points(<Points>, 0)
It continues down the textfile but IF the line = "Game" it should, increment GameNumber, and then move to the next line, continuing to add the data, instead as
Names(<Name>, 1)
Distance(<Distance>, 1)
Points(<Points>, 1)
and so on, but it my code isn't working. After getting this working, I wont it to print the desired Game statistics for the players on the page in a listbox with something like:
For n = 0 To Count - 1
lstNames.Items.Add(Names(n, GameWanted))
lstNames.Items.Add(" ")
lstDistance.Items.Add(Distance(n, GameWanted) + " Miles")
lstDistance.Items.Add(" ")
lstPoints.Items.Add(Points(n, GameWanted))
lstPoints.Items.Add(" ")
Next
This would become a lot easier if you create a class representing your player and index them with a dictionary
Class Player
Public Property Distances as List(Of Decimal)
Public Property Points as List(Of Integer)
Public Property Name as String
Public Sub New(n as String)
Name = n
Distances = New List(of Decimal)
Points = New List(of Integer)
End sub
End class
And then in your method that reads your file:
Dim d as new Dictionary(of String, Person)
ForEach line as String in File.ReadAllLines(...)
Dim bits = line.Split("|"c)
If bits.Length < 3 Then Continue For
If Not d.ContainsKey Then d.Add(bits(0), New Person(bits(0))
Dim p = d(bits(0)) 'retrieve the old or just added person
p.Distances.Add(decimal.parse(bits(1)))
p.Points.Add(integer.parse(bits(2)))
Next line
Note; I'm a c# programmer and seldom do vb any more. I code with array indexes starting at 0, if you're on 1 base indexing, add one to the indexes above. This code should probably be treated as pseudocode; it was written on a cellphone from a 5 year old memory of what vb looks like and might have a raft of vb syntax errors(sorry) or take the long way round to do things that we have shorter sybtaxes for these days (list initializers etc)
At the end of this loop through all the file you will have a dictionary of your people, one per name encountered. Each person will have a list of scores and distances. If you want to add them up or average them add a Function to person class that iterates the list and returns the result, and to print them all out do a
foreach s as string in d.Keys
Console.Write(d(s).Name & " got avg distance " & d(s).GetAverageDist())
Or similar
To print out all their distances:
foreach s as string in d.Keys
foreach dis as decimal in d(s).Distances
Console.Write(d(s).Name & " got distance " & dis)
This is object oriented programming; we model the world using classes to represent things and do useful stuff. We don't try to collect data together in 20 different arrays and tie it all together with indexes etc - that's a very procedural code mindset and the opposite of what vb.net was invented for
It's likely actually that this falls short of a proper solution and is also somewhat hypocritical because I use two lists to track distance and points and assert that the list indexes are equal - the distance at index 3 and the points at index 3 are from game 4 (zero based indexing note)
What we really should do is also define a GameResult class and it have properties of distance, points and gamenumber, then each person class has a single List(of GameResult) - person could have a function that returns a nicely formatted score card for that person - that's proper OO :)
I have several 3D items in listOri. For this example:
listOri has A,B,C,D,E.
A overlaps with C.
B overlaps with D.
D overlaps with E.
I have a recursive function which accepts listOri, check if each item overlaps with each other, and generates a final listNew which has AC, BDE.
Iteration 1:
Loop through each item in listOri, generates listNew containing AC,B,D,E
Iteration 2:
Loop through AC,B,D,E in listNew, generates (new) listNew containing, AC,BD,E
Iteration 3: and so on.
Here is the snippet code which check if each 3D object in a list overlaps, and produces a new list recursively.
Private Function SimplifyModel2(ByVal listOri As List(Of Mesh3D)) As List(Of Mesh3D)
Dim listNew As New List(Of Mesh3D)(listOri)
Dim indexOut, indexIn, indexInner, PercentProgressCurrent As Integer
Dim currentMeshOutter, currentMeshInner As Mesh3D
Dim isExitForCalled As Boolean = False
totInnerLoops = totInnerLoops + 1 ' increment total number of inner loops
For indexOut = 0 To (listOri.Count - 1)
currentMeshOutter = listOri(indexOut)
indexInner = indexOut + 1
For indexIn = indexInner To (listOri.Count - indexInner)
currentMeshInner = listOri(indexIn)
If Is3DOverlap(currentMeshInner, currentMeshOutter) = True Then
currentMeshOutter.CombineMerge(currentMeshInner)
listNew.Remove(currentMeshInner)
listNew.Remove(currentMeshOutter)
listNew.Insert(0, currentMeshOutter)
listNew = SimplifyModel2(listNew) ' recursively call the function
isExitForCalled = True
Exit For
End If
Next
If isExitForCalled = True Then
Exit For
End If
Next
indLoopExit = indLoopExit + 1
Return listNew
End Function
The function works well with listOri with very few items.
However, when there are thousands of 3D items in listOri, the functions takes very long time to produce the listNew.
How do I increase the speed of the recursive function?
Is there another way to write an algorithm which performs the same task above?
Let me know if you need any information.
Thank you.
I have found the solutions from the Code Review StackExchange.
Please refer to the link below:
Recursive function to merge 3D objects
I have a Part class with the fields list in the code below. I have a DataGridView control, which I am filtering with the Advanced DGV (ADGV) DLL from NUGET. I must include the ADGV in my winform. I currently have a DataGridView, a search box on the form, and a button to run the following function. I need to go through all of the visible rows, collect a unique list of part numbers with their most recent revisions, and then color the rows in DataGridView which are out of date by checking the part number and rev on each row against the mostuptodate list. For 45,000 entries displayed in DataGridView, this take ~17 secs. For ~50 entries, it take ~1.2 seconds. This is extremely inefficient, but I can't see a way to cut the time down.
Sub highlightOutdatedParts()
'Purpose: use the results in the datagridview control, find the most recent revision of each part, and
' highlight all outdated parts relative to their respective most recent revisions
'SORT BY PART NUMBER AND THEN BY REV
If resultsGrid.ColumnCount = 0 Or resultsGrid.RowCount = 0 Then Exit Sub
Dim stopwatch As New Stopwatch
stopwatch.Start()
resultsGrid.Sort(resultsGrid.Columns("PartNumber"), ListSortDirection.Ascending)
Dim iBag As New ConcurrentBag(Of Part)
Dim sortedList As Generic.List(Of Part)
For Each row As DataGridViewRow In resultsGrid.Rows
If row.Visible = True Then
Dim iPart As New Part()
Try
iPart.Row = row.Cells(0).Value
iPart.Workbook = CStr(row.Cells(1).Value)
iPart.Worksheet = CStr(row.Cells(2).Value)
iPart.Product = CStr(row.Cells(3).Value)
iPart.PartNumber = CStr(row.Cells(4).Value)
iPart.ItemNo = CStr(row.Cells(5).Value)
iPart.Rev = CStr(row.Cells(6).Value)
iPart.Description = CStr(row.Cells(7).Value)
iPart.Units = CStr(row.Cells(8).Value)
iPart.Type = CStr(row.Cells(9).Value)
iPart.PurchCtgy = CStr(row.Cells(10).Value)
iPart.Qty = CDbl(row.Cells(11).Value)
iPart.TtlPerProd = CDbl(row.Cells(12).Value)
iPart.Hierarchy = CStr(row.Cells(13).Value)
iBag.Add(iPart)
Catch ice As InvalidCastException
Catch nre As NullReferenceException
End Try
End If
Next
sortedList = (From c In iBag Order By c.PartNumber, c.Rev).ToList() ' sort and convert to list
Dim mostUTDRevList As New Generic.List(Of Part) ' list of most up to date parts, by Rev letter
For sl As Integer = sortedList.Count - 1 To 0 Step -1 'start at end of list and work to beginning
Dim query = From entry In mostUTDRevList ' check if part number already exists in most up to date list
Where entry.PartNumber = sortedList(sl).PartNumber
Select entry
If query.Count = 0 Then ' if this part does not already exist in the list, add.
mostUTDRevList.Add(sortedList(sl))
End If
Next
'HIGHLIGHT DATAGRIDVIEW ROWS WHERE PART NUMBERS ARE OUT OF DATE
For Each row As DataGridViewRow In resultsGrid.Rows
' if that part with that Rev does not exist in the list, it must be out of date
Try
Dim rowPN As String = CStr(row.Cells(4).Value).ToUpper ' get part number
Dim rowR As String = CStr(row.Cells(6).Value).ToUpper ' get Rev
Dim query = From entry In mostUTDRevList ' check if that part number with that Rev is in the list.
Where entry.PartNumber.ToUpper.Equals(rowPN) AndAlso
entry.Rev.ToUpper.Equals(rowR)
Select entry
If query.Count = 0 Then ' if the part is out of date highlight its' row
row.DefaultCellStyle.BackColor = Color.Chocolate
End If
Catch ex As NullReferenceException
Catch ice As InvalidCastException
End Try
Next
resultsGrid.Select()
stopwatch.Stop()
If Not BackgroundWorker1.IsBusy() Then timertextbox.Text = stopwatch.Elapsed.TotalSeconds.ToString & " secs"
MessageBox.Show("Highlighting completed successfully.")
End Sub
It is almost always faster to work with the data than the control. The control is simply the means to present a view of the data (in a grid) to the users. Working with the data from there requires too much converting to be effieicent. Then, use the DGV events to highlight the rows
Its hard to tell all the details of what you are doing, but it looks like you are comparing the data to itself (as opposed to some concrete table where the lastest revision codes are defined). Nor is it clear why the datasources are collections, ConcurrentBags etc. The key would be to use collections optimized for the job.
To demonstrate, I have a table with 75,000 rows; the product codes are randomly selected from a pool of 25,000 and a revision code is a random integer (1-9). After the DGV datasource is built (a DataTable) a LookUp is created from the ProductCode-Revision pair. This is done once and once only:
' form level declaration
Private PRCodes As ILookup(Of String, Int32)
' go thru table
' group by the product code
' create an anon Name-Value object for each,
' storing the code and highest rev number
' convert result to a LookUp
PRCodes = dtSample.AsEnumerable.
GroupBy(Function(g) g.Item("ProductCode"),
Function(key, values) New With {.Name = key.ToString(), .Value = values.
Max(Of Int32)(Function(j) j.Field(Of Int32)("RevCode"))
}).
ToLookup(Of String, Int32)(Function(k) k.Name, Function(v) v.Value)
Elapsed time via stopwatch: 81 milliseconds to create the collection of 23731 items. The code uses an anonymous type to store a Max Revision code for each product code. A concrete class could also be used. If you're worried about mixed casing, use .ToLowerInvariant() when creating the LookUp (not ToUpper -- see What's Wrong With Turkey?) and again later when looking up the max rev.
Then rather than looping thru the DGV rows use the RowPrePaint event:
If e.RowIndex = -1 Then Return
If dgv1.Rows(e.RowIndex).IsNewRow Then Return
' .ToLowerInvariant() if the casing can vary row to row
Dim pc = dgv1.Rows(e.RowIndex).Cells("ProductCode").Value.ToString()
Dim rv = Convert.ToInt32(dgv1.Rows(e.RowIndex).Cells("RevCode").Value)
Dim item = PRCodes(pc)(0)
If item > rv Then
dgv1.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.MistyRose
End If
Notes
It takes some time to create the DataSource, but 75,000 rows is a lot to throw at a user
The time to create the LookUp is minimal - barely measurable
There is no noticeable wait in displaying them because a) the LookUp is made for this sort of thing, b) rows are done as needed when they are displayed. Row # 19,999 may never be processed if the user never scrolls that far.
This is all geared to just color a row. If you needed to save the Current/NotCurrent state for each row, add a Boolean column to the DataTable and loop on that. The column can be invisible if to hide it from the user.
The random data results in 47,000 out of date RevCodes. Processing 75k rows in the DataTable to set the flag takes 591 milliseconds. You would want to do this before you set the DataTable as the DataSource to prevent changes to the data resulting in various events in the control.
In general, the time to harvest the max RevCode flag and even tag the out of date rows is a trivial increment to creating the datasource.
The Result:
The data view is sorted by ProductCode so that the coloring of lower RevCodes is apparent.
We surely cant grok all the details and constraints of the system from a small snippet - even the data types and original datasource are a guess for us. However, this should provide some help with better look-up methods, and the concept of working with the data rather than the user's view.
One thing is the revision code - yours is treating them as a string. If this is alphanumeric, it may well not compare correctly - "9" sorts/compares higher than "834" or "1JW".
See also:
Lookup(Of TKey, TElement) Class
Anonymous Types
The solution was spurred in part by #Plutonix.
Sub highlightOutdatedParts()
If resultsGrid.ColumnCount = 0 Or resultsGrid.RowCount = 0 Then Exit Sub
Dim stopwatch As New Stopwatch
stopwatch.Start()
resultsGrid.DataSource.DefaultView.Sort = "PartNumber ASC, Rev DESC"
resultsGrid.Update()
'HIGHLIGHT DATAGRIDVIEW ROWS WHERE PART NUMBERS ARE OUT OF DATE
Dim irow As Long = 0
Do While irow <= resultsGrid.RowCount - 2
' if that part with that Rev does not exist in the list, it must be out of date
Dim utdPN As String = resultsGrid.Rows(irow).Cells(4).Value.ToString().ToUpper()
Dim utdRev As String = resultsGrid.Rows(irow).Cells(6).Value.ToString().ToUpper()
Dim iirow As Long = irow + 1
'If iirow > resultsGrid.RowCount - 1 Then Exit Do
Dim activePN As String = Nothing
Dim activeRev As String = Nothing
Try
activePN = resultsGrid.Rows(iirow).Cells(4).Value.ToString().ToUpper()
activeRev = resultsGrid.Rows(iirow).Cells(6).Value.ToString().ToUpper()
Catch ex As NullReferenceException
End Try
Do While activePN = utdPN
If iirow > resultsGrid.RowCount - 1 Then Exit Do
If activeRev <> utdRev Then
resultsGrid.Rows(iirow).DefaultCellStyle.BackColor = Color.Chocolate
End If
iirow += 1
Try
activePN = resultsGrid.Rows(iirow).Cells(4).Value.ToString().ToUpper()
activeRev = resultsGrid.Rows(iirow).Cells(6).Value.ToString().ToUpper()
Catch nre As NullReferenceException
Catch aoore As ArgumentOutOfRangeException
End Try
Loop
irow = iirow
Loop
resultsGrid.Select()
stopwatch.Stop()
If Not BackgroundWorker1.IsBusy() Then
timertextbox.Text = stopwatch.Elapsed.TotalSeconds.ToString & " secs"
resultcounttextbox.Text = resultsGrid.RowCount - 1 & " results"
End If
MessageBox.Show("Highlighting completed successfully.")
End Sub