Sorting a List of Integers and Strings by integer descending order in VB - vb.net

I have to make this program that sorts the high scores of a game and then displays them biggest to smallest with the username USING LISTS. So far i have written:
Public highscore As New List(Of HighScores)
highscore.Add(New HighScores("Jeremias", 6))
highscore.Add(New HighScores("Tom", 1))
highscore.Add(New HighScores("sdf", 5))
highscore.Add(New HighScores("asfd", 1))
highscore.Sort()
highscore.Reverse()
Console.WriteLine("------High Scores-----")
For Each scores In highscore
Console.WriteLine(scores)
Next
Console.WriteLine("----------------------")
And the HighScores Class:
Public Class HighScores
Public name As String
Public score As Integer
Public Sub New(ByVal name As String, ByVal score As Integer)
Me.name = name
Me.score = score
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0}, {1}", Me.name, Me.score)
End Function
End Class
Usually i would just use .Sort() and .Reverse() to sort the list, but in this case i don't think i can do this. Any ideas how i can rewrite this/just sort the list easily?

You can specify how to sort a List(Of T) in various ways. The simplest would be like so:
highscore.Sort(Function(x, y) y.score.CompareTo(x.score))
That uses the overload of Sort that takes a Comparison(Of T) delegate and uses a Lambda expression for that delegate. Note that the Lambda parameters are x and y and the body calls CompareTo on the score of y. That is critical because that's what makes the sort happen in descending order and negates the need to call Reverse.
Note that you could use a named method instead of a Lambda. Such a method would look like this:
Private Function CompareHighScoresByScoreDescending(x As HighScores, y As HighScores) As Integer
Return y.score.CompareTo(x.score)
End Function
The code to sort would then look like this:
highscore.Sort(AddressOf CompareHighScoresByScoreDescending)
When comparing objects for sorting purposes, the convention is to use -1, 0 and 1 to represent relative positions. That's what CompareTo does and thus that's what our comparison method does here. If the object you call CompareTo on is conceptually less the object you pass in then the result is -1. 1 means the first object is greater than the second and 0 means they are equal. That method could be rewritten like so:
Private Function CompareHighScoresByScoreDescending(x As HighScores, y As HighScores) As Integer
If y.score < x.score Then
Return -1
ElseIf y.score > x.score Then
Return 1
Else
Return 0
End If
End Function
It's obviously more succinct to use the existing IComparable implementation of the Integer type though, i.e. that CompareTo method.
By the way, your code could use some improvements in other areas. Firstly, HighScores is not an appropriate name for that class. It represent a single thing so the name should not be plural and it doesn't actually represent a high score in and of itself. A more appropriate name would be PlayerScore as that more accurately describes what it represents.
Secondly, your List variable actually does represent more than one object, i.e. a list that contains multiple items, so it's name should be plural. It also does actually represent high scores so it should be named highScores.
Finally, it is almost universally bad practice to expose member variables publicly. You should absolutely be using properties in that class:
As a bonus, if you're using VS 2015 or later then you can also replace String.Format with string interpolation.
Public Class PlayerScore
Public Property Name As String
Public Property Score As Integer
Public Sub New(name As String, score As Integer)
Me.Name = name
Me.Score = score
End Sub
Public Overrides Function ToString() As String
Return $"{Name}, {Score}"
End Function
End Class

Related

In a generic VB.NET structure, how do I access its explicitly provided constructor?

First of all, what I want to achieve:
I want to extend a value datatype by providing additional properties, especially to validate ranges provided at declaration time. I want the new datatype to be a value type as well.
Compare with Ada:
subtype Day_Number is Integer range 1 .. 31;
Ideal, but obviously not implementable, would be:
Dim DayNumber As Int64 Range 1 To 31
However, I would be happy with:
Dim DayNumber As RangeInt64(1, 31)
It is of no concern, if initialization takes its time. Once ranges are provided, they are considered to be immutable. The datatype from then on is only used to set/get values like with ordinary value types, only that they are subject of being validated against the initially provided range.
My attempt:
Since I cannot inherit from structures in order to expand on them, I tried to incorporate a structure into a structure as a member.
In a module, I have this structure:
Friend Structure SRangeValueType(Of T)
Private lMinimum As T
Private lMaximum As T
Friend Property Minimum As T
Get
Return lMinimum
End Get
Set(tValue As T)
lMinimum = tValue
End Set
End Property
Friend Property Maximum As T
Get
Return lMaximum
End Get
Set(tValue As T)
lMaximum = tValue
End Set
End Property
Friend Sub New(Minimum As T, Maximum As T)
lMinimum = Minimum
lMaximum = Maximum
End Sub
End Structure
I attempt to use this generic structure as a member of another structure (of concrete type Int64):
Public Structure RangeInt64
Private Range As SRangeValueType(Of Int64)
End Structure
However, this is not using the constructor with the two arguments.
Say I want to initialize Range (the only member of the structure RangeInt64) with the values 100 and 200 for Minimum and Maximum, resp.
I am not allowed to use something like:
Private Range As SRangeValueType(Of Int64)(100,200)
What is the correct syntax to provide my values to the generic constructor?
Normally, if you add a constructor to a structure, you can call it using the New keyword:
Dim x As SRangeValueType(Of Int64) ' Calls the default, infered, parameter-less constructor
Dim y As New SRangeValueType(Of Int64)(100, 200) ' Calls the explicitly defined constructor
However, that's not really the problem. The problem is that you are trying to set the default value of a non-shared field in a structure. That is something which is never allowed. All non-shared fields in structures must default to their default value (i.e. Nothing). For instance:
Public Structure RangeInt64
Private x As Integer = 5 ' Error: Initializers on structure members are valid only for 'Shared' members and constants
Private y As New StringBuilder() ' Error: Non-shared members in a structure cannot be declared 'New'
End Structure
And, as you may already know, you cannot override the default, inferred, parameter-less constructor on a structure either:
Public Structure RangeInt64
Public Sub New() ' Error: Structures cannot declare a non-shared 'Sub New' with no parameters
x = 5
y = New StringBuilder()
End Sub
Private x As Integer
Private y As StringBuilder
End Structure
As such, you are stuck. By design, when the default constructor is used, all fields in the structure must always default to Nothing. However, if you really, really need it to be a structure, and you can't just convert it to a class, and you really need to change it's default value, you could theoretically fake it into working by using a property to wrap the field:
Public Structure RangeInt64
Private _Range As SRangeValueType(Of Int64)
Private _RangeInitialized As Boolean
Private Property Range As SRangeValueType(Of Int64)
Get
If Not _RangeInitialized Then
_Range = New SRangeValueType(Of Int64)(100, 200)
_RangeInitialized = True
End If
Return _Range
End Get
Set(value As SRangeValueType(Of Int64))
_Range = value
End Set
End Property
End Structure
It should go without saying, though, that it's pretty gross and should be avoided if possible.
Update
Now that you've provided more details about what you are trying to accomplish, I think I may have a better solution for you. You're right that structures do not support inheritance, but what they do support is interfaces. So, if all you need is a bunch of range types, one per value-type, all having the same minimum and maximum properties, but all returning different predetermined values, then you could do something like this:
Private Interface IRangeValueType(Of T)
ReadOnly Property Minimum As T
ReadOnly Property Maximum As T
End Interface
Private Structure RangeInt64
Implements IRangeValueType(Of Int64)
Public ReadOnly Property Minimum As Long Implements IRangeValueType(Of Int64).Minimum
Get
Return 100
End Get
End Property
Public ReadOnly Property Maximum As Long Implements IRangeValueType(Of Int64).Maximum
Get
Return 200
End Get
End Property
End Structure

How to find the index of an object in a list of objects? VB.Net

I am creating a method FindPerson which searches for a given name in a list of objects and returns the index in the list of the object with this name if found, otherwise it returns -1.
Public Class TPerson
Private Name As String
Private Address As String
Private Age As Integer
Public Sub New()
Name = "x"
Address = "x"
Age = 0
End Sub
……
End Class
Public Class TGroup
Private Group As List(Of TPerson)
Private GroupSize As Integer
Public Sub New(size As Integer)
GroupSize = size
Group = New List(Of TPerson)
End Sub
Public Sub FindPerson(findname As String)
Dim index As Integer
index = Group.FindIndex(findname) 'error
End Sub
End Class
The output should be an index in the list, however when I run the program I get the error: BC30311 Value of type 'String' cannot be converted to 'Predicate(Of TPerson)'
I am not quite sure how to fix this any help will be appreciated
How exactly do you expect the FindIndex method to know what to do with that String that you are passing in? You seem to assume that it will know that it represents a name and that it needs to match an item by Name property but how do you think it's going to do that? Why do you think it would match on Name rather than Address?
As the error message says, you need to provide a Predicate which is a delegate that takes an object of type T and returns a Boolean. In your case, T is TPerson and the Boolean needs to indicate whether findname matches its Name property. The simplest way to do that is with a Lambda expression:
Dim index = Group.FindIndex(Function(person) person.Name = findname)
You could do it with a named method and a delegate if you wanted to but it would be more long-winded and it would mean getting the findname value in by some convoluted means. If you read the documentation for the FindIndex method (which you should have done before posting here) you can find an example of that sort of thing.

How do I sort objects in VB.net with custom comparer elegantly?

I want to sort a bunch of domainsdata object. I want to sort first based on countryCode then I want to sort based on revenue.
First I created a private comparer.
Private Function CompareDomainCountry(ByVal x As domainsData, ByVal y As domainsData) As Integer
If x.countryCode < y.countryCode Then
Return -1
ElseIf y.countryCode < x.countryCode Then
Return 1
ElseIf x.revenue < y.revenue Then
Return 1
ElseIf y.revenue < x.revenue Then
Return -1
Else
Return 0
End If
End Function
This has several problem.
The comparer returns 1,-1,0. I think there should be a normal enum for that.
Also I think my comparer should simply call standard vb.net comparer.
And after that, how do I sort list (of domainsdata)?
comparer?
You can use the CompareTo method to compare the values. If the first comparison is zero, then do the other comparison:
Private Function CompareDomainCountry(ByVal x As domainsData, ByVal y As domainsData) As Integer
Dim result As Integer = x.countryCode.CompareTo(y.countryCode)
If result = 0 Then
result = x.revenue.CompareTo(y.revenue)
End If
Return result
End Function
To sort the list using the comparison you use it in the Sort method call:
myList.Sort(AddressOf CompareDomainCountry)
First, note that your Revenue compare code is inconsistent: a smaller X should return -1. You also don't absolutely need that method. This gives the same result:
Dim sorted = DomainList.OrderBy(Function(x) x.CountryCode).
ThenBy(Function(y) y.Revenue).
ToList()
If you want to rely on a standard NET method, your method can be a class member:
Public Class DomainComparer
Implements IComparer(Of Domain)
Public Function Compare(x As Domain, y As Domain) As Integer _
Implements IComparer(Of Domain).Compare
' all your code
End Function
End Class
Then to use it:
Dim dSorter = New DomainComparer
DomainList.Sort(dSorter)
' or simply:
DomainList.Sort(New DomainComparer)
Mr Guffa's AddressOf method is simpler and more concise; I like the class method when there are other variables/properties such as a SortOrder.
The results are the same either way (when the revenue result is changed) unless a sort member is mixed alpha-numeric string (which seemed not to be the case based on the names and comparison).
If you were hoping to use your method with OrderBy(), I dont think you can - the signature doesn't match Func(Of T)(TKey). The return however is uniform with most all Compare() methods to indicate the larger value (DateTime indicates the lesser/earlier date; there may be others).

finding multiple alike values in items in a list

i am writing a poker game, it will deal you different cards and then adds the dealt cards to a list. its a list of cards, each card has a value, cardno, which stores the number, what i need to do is check the list for say a straight (4,5,6,7,8 of clubs) or a full house (5 club, 5 diamond, 7heart ,7 spade, 7 diamond) and so on.
heres my cards code:
Public Class card
Public suite As String
Public cardlet As String
Public cardno As Integer
End Class
and then i added it to a list
dim dealtcards as new list(Of card)
so i need to check if dealtcards contains a pair, two pair, three of a kind, etc.
What you should do is create different 'functions' that look if a hand contains a certain combination. You can search for values in Arrays with this function:
Public Shared Function FindValueFromArray(ByVal Values As Object(), ByVal valueToSearch As Object) As Boolean
Dim retVal As Boolean = False
Dim myArray As Array = DirectCast(Values, Array)
If Array.IndexOf(myArray, valueToSearch) <> -1 Then
retVal = True
End If
Return retVal
End Function
From here.
It sounds like you need another class... Public Class Hand.
It could internally contain a list of cards, but the main point is the public interface it presents. Methods to manipulate the list of cards (add to hand, remove from hand) and a method to check for a "status" of the hand (which could return a strongly-typed enumeration of known statuses). Something not unlike this perhaps (my VB is very rusty, so this is mostly pseudo-code):
Public Class Hand
Private cards As IList(Of card)
' A method to add a card to the hand, which should check if the hand can hold another card or not
' A method to remove a card from the hand, perhaps?
' Other methods representing actions that can be performed, such as folding the hand
Public Function GetHandStatus As HandStatus
If HandIsFlush() Then
Return HandStatus.Flush
Else If HandIsStraight() Then
Return HandStatus.Straight
End IF
End Function
Private Function HandIsFlush As Boolean
' loop through cards, return true if they are all the same suit
End Function
Private Function HandIsStraight As Boolean
' iterate through sorted-by-value cards, return false if more than 1 value separates any two
End Function
End Class
What this is doing is reducing each element of business logic for this application into a single function to handle that logic. If the class starts to get large, you can further refactor it, perhaps changing HandStatus from an enumeration into an abstract class with subclasses representing the statuses, and moving logic onto there.
What I would do is create and dictionary of lambdas where the key is the type of hand you have and the lambda is the actual used to determine what time of hand.
Dim fullHouse = Function(cards as List(of Card)
//logic to check
return True or False
End Function
Dim rules as New Dictionary(of String, func(of List(Of Card,Boolean))
rules.Add("FullHouse",fullHouse)
Dim hand as List(of Card)
DIm typeOfHand _
rules.
Keys.
Select(Function(k)
If rules.Item(k)(hand) = True then Return k
Return string.Empty).
Where(Function(r) Not String.IsEmptyOrNull(r)).
FirstOrDefault()
Dim score = hand.Select(h) h.CardNo).Sum()
Of course you can expand upon this example with more OOP but I think the use to lambdas and Linq with a functional approach will make it easier to understand and maintain your code.

Split 'RockPaperScissors.Paper' string in vb.net to just retrieve 'Rock'

The following code
player2.getWeapon.ToString()
returns
'RockPaperScissors.Rock'
OR
'RockPaperScissors.Paper'
OR
'RockPaperScissors.Scissors'
I am just interested in the Class Name, i.e. Rock, Paper or Scissors. How can I achieve this?
Split was what came to my mind but not sure if its the best.
Yes, string.Split seems to be the appropriate way:
Dim tokens As String() = player2.getWeapon.ToString().Split("."c)
Dim className As String = tokens.Last() ' or simply tokens(1) without Linq
But if you can extend your Weapon class with a property that returns it directly i would do that.
ToString is just a regular overridable method defined by the base Object class. Therefore, you can easily override it to make it return whatever you want. For instance:
Public Class Rock
Public Overrides Function ToString() As String
Return "Rock"
End Function
End Class
Or, if you want to force your classes to implement it, you could do it in your base Weapon class, like this:
Public Class Weapon
Public Sub New(weaponName As String)
_weaponName = weaponName
End Sub
Private _weaponName As String
Public Overrides Function ToString() As String
Return _weaponName
End Function
End Class
Public Class Rock
Inherits Weapon
Public Sub New()
MyBase.New("Rock")
End Sub
End Class
Alternatively, if you don't want to require it in the constructor, you could add a protected MustOverride Function GetWeaponName() As String in your base class and then return that from ToString. Either way, the derived classes will be forced to provide the name so that it can be returned in ToString.
The reason I recommend doing it this way is because I suspect you are using this string as a description which is displayed to the user. If that's the case, it's not really the class name that you want, but a description of the weapon. So far, they just happen to be the same, but logically speaking, that's not alway going to be the case. For instance, if you had a class called SuperDuperRock, you aren't going to want to display it like that to the user. Rather, you'd want it to be properly formatted with spaces between the words.
If you really do not wish to use Split() you could use IndexOf (more performant)
Dim className as String
Dim weapon = player2.getWeapon.ToString()
Dim pos as Integer = weapon.IndexOf("."c)
if pos >= 0 Then
className = weapon.Substring(pos + 1)
End if