Sorting a dictionary and get top 3 - c#

I have a dictionary which stores members of a 'Skiing' tournament. It also stores there scores. What I want to do is be able to find and display the top 3 scores of the members. I was just wondering what the best way would be to approach this as I am stuck at the moment. The following is the dictionary and how a member is added:
public static Dictionary<string, Skier> Skiers = new Dictionary<string, Skier>();
static int income;
string lodgeName;
public SkiLodge(string newLodgeName)
{
newLodgeName = lodgeName;
Skiers = new Dictionary<string, Skier>();
}
static int newNumber = 1;
//ADD SKIER
public Skier AddSkier(string inName, string inAddress, int inScore)
{
string newNumberString = newNumber.ToString();
Skier result = new Skier(newNumberString, inName, inAddress, inScore);
newNumber = newNumber + 1;
Skier S = new Skier(newNumberString, inName, inAddress, inScore);
Skiers.Add(newNumberString, S);
income = income + 100;
return result;
}

I assumed that you have a property in Skier called Score, here how can you achieve your goal.
//Your dictionary must have at least 3 entries.
var orderedTopThree = Skiers.OrderByDescending(s => s.Value.Score).Take(3);

Either of these methods added to your SkiLodge class, will get you what you're looking for.
This will grab your top X KeyValuePairs with the Skier object being the Value property of the KeyValuePair.
public List<KeyValuePair<string,Skier>> GetTopSkiers(int howMany)
{
return Skiers.OrderByDescending(kvp => kvp.Value.Score).Take(howMany).ToList();
}
This will grab your top X Skiers
public List<Skier> GetTopSkiers(int howMany)
{
return Skiers.OrderByDescending(kvp => kvp.Value.Score).Take(howMany).Select(kvp => kvp.Value).ToList();
}
Both methods use the OrderByDescending Linq method, which uses a lambda expression as a selector for the sorting comparison kvp => kvp.Value.Score. Which in this case is saying, foreach kvp (KeyValuePair) in this dictionary, sort them by the Value property, which in this case is the Skier object, and use the Skier object's Score as the value to sort by.
Take will take, up to x values from an Enumerable.
Select then returns an Enumerable resulting from the lambda function passed in. In this case, kvp => kvp.Value returns an Enumerable of Skier objects, instead of a list of the KeyValuePair objects.

Either use a SortedDictionary or enumerate through the keys of dictionary or this code
public class Skier
{
public static Dictionary<string, Skier> Skiers = new Dictionary<string, Skier>();
public int inScore { get; set; }
public Skier()
{
int[] highscore = Skiers.AsEnumerable().OrderByDescending(x => ((Skier)x.Value).inScore).Take(3).Select(y => ((Skier)y).inScore).ToArray();
}
}

You may use LINQ to achieve. Here is one sample query:
Skiers.OrderByDescending(e=>e.Value.inScore).Take(3).ToList();

Using System.Linq, the following code will get you the best 3 Skiers by score:
public List<Skier> GetTop3()
{
var list = Skiers.OrderByDescending(sk=> sk.Value.Score).Take(3).ToList();
return list;
}

I have tried to replicate the behavior and fixed some of the issues in above code. In AddSkier you dont need to create 2 objects.
FetchTopThree() will give you the top 3 results. You can then display the results.
using System.Collections.Generic;
using System.Linq;
public class SkiLodge
{
public static Dictionary<string, Skier> Skiers = new Dictionary<string, Skier>();
static int income;
string lodgeName;
public SkiLodge(string newLodgeName)
{
this.lodgeName = newLodgeName;
}
static int newNumber = 1;
//ADD SKIER
public Skier AddSkier(string inName, string inAddress, int inScore)
{
string newNumberString = newNumber.ToString();
newNumber = newNumber + 1;
Skier skier= new Skier(newNumberString, inName, inAddress, inScore);
Skiers.Add(newNumberString, skier);
income = income + 100;
return skier;
}
public List<Skier> FetchTopThree()
{
return Skiers.Values.OrderByDescending(s => s.InScore).Take(3).ToList();
}
}
public class Skier
{
public string NewNumberString { get; }
public string InName { get; }
private readonly string inAddress;
public int InScore { get; }
public Skier(string newNumberString, string inName, string inAddress, int inScore)
{
this.NewNumberString = newNumberString;
this.InName = inName;
this.inAddress = inAddress;
this.InScore = inScore;
}
}

Related

C# / Sorting a text file / IComparer / Custom sort

I have a text file that I want to be sorted.
Each line has a package name, a pipe and a version number.
Examples:
AutoFixture|4.15.0
Castle.Windsor.Lifestyles|0.3.0
I tried to use the default list.Sort() method but I obtained:
AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor.Lifestyles|0.3.0
Castle.Windsor|3.3.0
FluentAssertions|5.10.3
Instead of
AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor|3.3.0
Castle.Windsor.Lifestyles|0.3.0
FluentAssertions|5.10.3
As shown, I would like "Castle.Windsor" to appear before "Castle.Windsor.Lifestyles".
I'm pretty sure I have to use the IComparer but I can't find a way to get the shorter name first.
So far, I created a custom sort like this which is not working..
public class PackageComparer : IComparer<string>
{
// Assume that each line has the format: name|number
private readonly Regex packageRegEx = new Regex(#"[\w.]+\|[\d.]+", RegexOptions.Compiled);
public int Compare(string x, string y)
{
var firstPackage = this.packageRegEx.Match(x);
var firstLeft = firstPackage.Groups[1].Value;
var firstRight = firstPackage.Groups[2].Value;
var secondPackage = this.packageRegEx.Match(y);
var secondLeft = secondPackage.Groups[1].Value;
var secondRight = secondPackage.Groups[2].Value;
if (firstLeft < secondLeft)
{
return -1;
}
if (firstRight > secondLeft)
{
return 1;
}
return string.CompareOrdinal(firstSceneAlpha, secondSceneAlpha);
}
}
Well, you can use Linq, split by the pipe and order by the package name then by the versioning:
var input = #"AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor.Lifestyles|0.3.0
Castle.Windsor|3.3.0
FluentAssertions|5.10.3
Castle.Core|3.1.0";
var list = input.Split(new string[]{"\r\n","\n"},StringSplitOptions.None).ToList();
list = list
.OrderBy(x => x.Split('|')[0])
.ThenBy(x => new Version(x.Split('|')[1]))
.ToList();
Outputs:
AutoFixture|4.15.0
Castle.Core|3.1.0
Castle.Core|3.3.0
Castle.Windsor|3.3.0
Castle.Windsor.Lifestyles|0.3.0
FluentAssertions|5.10.3
You can do something like this:
public class YourClassName
{
public string PackageName { get; set; }
public string Pipe { get; set; }
public string Version { get; set; }
}
Load your data into list to sort
List<YourClassName> list = souce of data;
list = SortList<YourClassName>(list, "PackageName");
SortList Method:
public List<YourClassName> SortList<TKey>(List<YourClassName> list, string sortBy)
{
PropertyInfo property = list.GetType().GetGenericArguments()[0].GetProperty(sortBy);
return list.OrderBy(e => property.GetValue(e, null)).ToList<YourClassName>();
}

Class to return different types - best option?

I need a class that can work with string properties to store data on a structured (class) list but at the same type to have a way to return an index that acts as the order I need to set up my data.
the class can be something like:
public class MyClass
{
public int int1 = 0;
public int int2 = 0;
public int int3 = 0;
public int int4 = 0;
public string string1 { get; set; }
public string string2 { get; set; }
public string string3 { get; set; }
public string string4 { get; set; }
}
then I can use my class to store the data in the class structured format:
string fileName = #"C:\Mylocation\MyTextFileToRead.txt"; //tab delimeted file
Encoding fileEncoding = Encoding.ASCII;
List<MyClass> myDataList = new List<MyClass>();
List<string> simpleData = File.ReadAllLines(fileName, fileEncoding).ToList();
MyClass index = new MyClass();
foreach (var line in simpleData)
{
var lineSplit = line.Split('\t');
MyClass myClassElement = new MyClass
{
string1 = lineSplit[index.int1],
string2 = lineSplit[index.int2],
string3 = lineSplit[index.int3],
string4 = lineSplit[index.int4],
};
myDataList.Add(myClassElement);
}
I use the index to map each property from the text file and then to store each filed values in the corresponding string property.
I do not wish to use object types and have the properties to return a string value and cast it as (int) for the index. I was advised not to use objects as much as possible.
The use of Const fields did not work for the index unless I use the type name of the class (instead an instance of the class), but I don't know if it helps or if this is a better programming option.
I'm not sure if there is a better way of doing this, but would be very welcome with relevant feedback.
To return two diffrent types from one function you can use the way that was introduce in C# 7.3
It works like this:
public (int, string) MyFunc()
{
//do something
return (VarThatInt, VarThatString);
}
//and in main class u call it like this:
(varInt, varString) = MyFunc();
using KeyValuePair to store one property and index, use list of KeyValuePair to store one line , and use dic Dictionary to store file .
List<int> orderIndexList = new List<int> { 2, 1, 4, 0 };
Dictionary<int, List<KeyValuePair<int, string>>> data = new Dictionary<int, List<KeyValuePair<int, string>>>();
int lineNumber = 0;
foreach (var line in simpleData)
{
var lineSplit = line.Split('\t');
List<KeyValuePair<int, string>> listLine = new List<KeyValuePair<int, string>>();
orderIndexList.ForEach(index => listLine.Add(new KeyValuePair<int, string>(index, lineSplit[index])));
data.Add(++lineNumber, listLine);
}
return data;
Return a class that has the two desired outputs. You can even return a third variable to tell the receiving code, which output to use.
public myClass{
String TextValue;
int NumberValue;
bool IsReturnString;
}
You would return an object of myClass.

how to sort and display an arraylist based on points in c#

I have an array list named memberData that stores the memberID, memberName, memberPoint and some other member data. I want to sort all the members based on the memberPoint field.
Here is my code:
public void displayAllMembers()
{
int index = 1;
Console.WriteLine("ALL MEMBERS");
Console.WriteLine("No\t Member Name\t\t Member ID\t Member Point");
memberData.Sort();
foreach (object data in memberData)
{
tempMember = (Member)data;
Console.WriteLine("{0}\t\t {1} {2}\t\t {3}\t\t {4}", index, tempMember.givenName, tempMember.surName, tempMember.memberID, tempMember.memberPoint);
index++;
}
}
You have to use LINQ functions for sorting depending on sorting directions like this:
To sort in ascending order:
memberData = memberData.OrderBy(m=>m.memberPoint);
To sort in descending order :
memberData = memberData.OrderByDescending(m=>m.memberPoint);
Using LINQ
Instead of Sort() which only uses the internal Equals to compare objects in the list you simply can use LINQ for this:
var sortedByMemberPoint = memberData.OrderBy(m=>m.memberPoint);
This will sort your member data by the property provided in the OrderBy method. To sort descending use OrderByDescending() instead.
Using IComparer
Alternatively you can implement your own comparer class to compare the member data (which is quite an overhead for your simple use case). That is recommended if you want to do more complex comparison. You can have a look on MSDN for a simple sample.
Try following :
public class MemberData : IComparable<MemberData>
{
public static List<MemberData> memberData = new List<MemberData>();
public string givenName {get;set;}
public string surName {get;set;}
public string memberID { get;set;}
public string memberPoint { get; set; }
public void displayAllMembers()
{
int index = 1;
Console.WriteLine("ALL MEMBERS");
Console.WriteLine("No\t Member Name\t\t Member ID\t Member Point");
memberData.Sort();
foreach (MemberData data in memberData)
{
Console.WriteLine("{0}\t\t {1} {2}\t\t {3}\t\t {4}", index, data.givenName, data.surName, data.memberID, data.memberPoint);
index++;
}
}
public int CompareTo(MemberData other)
{
if (this.givenName != other.givenName) return this.givenName.CompareTo(other.givenName);
if (this.surName != other.surName) return this.surName.CompareTo(other.surName);
if (this.memberID != other.memberID) return this.memberID.CompareTo(other.memberID);
return this.memberPoint.CompareTo(other.memberPoint);
}
}
As #derape said, you can use also an IComparer, here you can have an example using that approach also
void Main()
{
A[] a = new A[]
{
new A
{
Point = 10,
Name="A1"
},
new A
{
Point=6,
Name="A2"
},
new A
{
Point=7,
Name="A3"
}
};
Array.Sort(a, new AComparer());
//at this point a has all element sorted
}
public class A
{
public int Point { get; set; }
public string Name { get; set;}
}
public class AComparer : IComparer<A>
{
public int Compare(A x, A y)
{
if(x.Point == y.Point)
return 0;
if(x.Point < y.Point)
return -1;
else return 1;
}
}
Hope this helps

MINIJson Unity get nested json objects [duplicate]

This question already has answers here:
Serialize and Deserialize Json and Json Array in Unity
(9 answers)
Closed 5 years ago.
{
"check":"success",
"stats":{
"2":{
"rank":1,
"score":"2000",
"name":"Muhammad"
},
"3":{
"rank":1,
"score":"2000",
"name":"Ramsay"
}}}
this is my json string. I want to use "stats" as a list to check how many entries it has. Then I need to get the rank, name, score etc from each entry.
My code is,
var result = Json.Deserialize(jsontest) as Dictionary<string,object>;
object st;
var rankholders = new List<object>();
if (result.TryGetValue("stats", out st))
{
rankholders = (List<object>)(((Dictionary<string, object>)st)["stats"]);
foreach (object obj in rankholders)
{
var tempDict = ((Dictionary<string,object>)(rankholders[0]));
WeeklyStatsItem tempRow = new WeeklyStatsItem ();
tempRow.rank = (string)tempDict["rank"];
tempRow.name = (string)tempDict["name"];
tempRow.score = (string)tempDict["score"];
weeklyScoreList.Add (tempRow);
}
}
But I get keynotfound exception. Any idea how to parse such a json loop?
To me it looks like you have an object which contains a property "check" and a dictionary called "stats". If you will make your own class having this structure, you will be able to deserialize json to this type of object
Something like:
class Stats
{
public int rank;
public int score;
public string name;
}
class ResultContainer
{
public string checked;
public Dictionary<int,Stats>stats;
}
And you should deserialize as ResultContainer.
Your Json is NOT valid because it was throwing error when stats is made to be a List or array. I was able to re-create a valid Json with the code below:
I want to use "stats" as a list to check how many entries it has
WeeklyStatsItem recreation = new WeeklyStatsItem();
recreation.check = "success";
recreation.stats = new List<Stats>();
Stats st1 = new Stats();
st1.rank = 1;
st1.score = "2000";
st1.name = "Muhammad";
recreation.stats.Add(st1);
Stats st2 = new Stats();
st2.rank = 1;
st2.score = "2000";
st2.name = "Ramsay";
recreation.stats.Add(st2);
Debug.Log(JsonUtility.ToJson(recreation));
The new generated valid Json is:
{"check":"success","stats":[{"rank":1,"score":"2000","name":"Muhammad"},{"rank":1,"score":"2000","name":"Ramsay"}]}
Now to answer your question:
I want to use "stats" as a list to check how many entries it has.
string jsonTest = "{\"check\":\"success\",\"stats\":[{\"rank\":1,\"score\":\"2000\",\"name\":\"Muhammad\"},{\"rank\":1,\"score\":\"2000\",\"name\":\"Ramsay\"}]}";
WeeklyStatsItem weeklyIt = JsonUtility.FromJson<WeeklyStatsItem>(jsonTest);
Debug.Log(weeklyIt.stats.Count);
Then I need to get the rank, name, score etc from each entry
string jsonTest = "{\"check\":\"success\",\"stats\":[{\"rank\":1,\"score\":\"2000\",\"name\":\"Muhammad\"},{\"rank\":1,\"score\":\"2000\",\"name\":\"Ramsay\"}]}";
WeeklyStatsItem weeklyIt = JsonUtility.FromJson<WeeklyStatsItem>(jsonTest);
Debug.Log("CHECK: " + weeklyIt.check);
for (int i = 0; i < weeklyIt.stats.Count; i++)
{
Debug.Log("STATS INDEX: " + i);
Debug.Log("RANK: " + weeklyIt.stats[i].rank);
Debug.Log("NAME: " + weeklyIt.stats[i].name);
Debug.Log("SCORE: " + weeklyIt.stats[i].score);
}
Your Json Classes:
[Serializable]
public class Stats
{
public int rank;
public string score;
public string name;
}
[Serializable]
public class WeeklyStatsItem
{
public string check;
public List<Stats> stats;
}
Since you're keys have semantic meaning and don't map to variable names you'll have to go a bit further than the basic examples that assume your json objects map neatly to a c# object, i.e. that they have a fixed schema.
Basically you'll have to look at each entry in the stats dict and pass both the key and the underlying values to your domain object constructor.
I'm not super familiar with MiniJSON, but I've banged together a working example. I suspect there is a more idiomatic way of doing this, e.g. using generics.
Assets/Editor/ParserTest.cs
using NUnit.Framework;
public class ParserTest {
[Test]
public void TestThatTwoPlayersAreInTestResponse()
{
string testResponse = "{ \"check\":\"success\", \"stats\":{ \"2\":{ \"rank\":1, \"score\":\"2000\", \"name\":\"Muhammad\" }, \"3\":{ \"rank\":1, \"score\":\"2000\", \"name\":\"Ramsay\" } } }";
Assert.AreEqual(MiniJsonParsingExample.parseResponse(testResponse).Count, 2);
}
}
Assets/Scripts/MiniJsonParsingExample.cs
using System.Collections.Generic;
using Facebook.MiniJSON;
public class MiniJsonParsingExample
{
public static List parseResponse(string responseText)
{
var resultDict = Json.Deserialize(responseText) as Dictionary;
var players = new List();
if (resultDict.ContainsKey("stats"))
{
var playerDict = resultDict["stats"] as Dictionary;
foreach (string playerId in playerDict.Keys)
{
var psuedoPlayer = playerDict[playerId] as Dictionary;
string playerName = psuedoPlayer["name"] as string;
long playerRank = (long) psuedoPlayer["rank"];
string playerScore = psuedoPlayer["score"] as string;
players.Add(new Player(playerId, playerName, playerRank, playerScore));
}
}
return players;
}
public class Player
{
string id;
string name;
long rank;
string score;
public Player(string id, string name, long rank, string score)
{
this.id = id;
this.name = name;
this.rank = rank;
this.score = score;
}
}
}

Get top 5 scores/names from list

I am adding a function to keep track of scores for a small game I made.
I want to get the top 5 scores (including name for that score) from a file that contains the scores.
The format of the saved scores is:
[name]-[score]
The scores and names are stored in 2 lists, which I parse this way:
string scores = File.ReadAllText(Environment.GetEnvironmentVariable("TEMP") + "/scores");
string[] splitscores = scores.Split('\n');
foreach (string entry in splitscores)
{
string replace = entry.Replace("[", "").Replace("]", "");
string[] splitentry = replace.Split('-');
if (splitentry.Count() > 1)
{
scorenames.Add(splitentry[0]);
scorelist.Add(Int32.Parse(splitentry[1]));
}
}
Then I retrieve #1 player by using:
int indexMax
= !scorelist.Any() ? -1 :
scorelist
.Select((value, index) => new { Value = value, Index = index })
.Aggregate((a, b) => (a.Value > b.Value) ? a : b)
.Index;
lblhighscore1.Text = "#1: " + scorelist[indexMax] + " by " + scorenames[indexMax];
How can I set the remaining 4 players assuming this is my scorelist:
[broodplank]-[12240]
[flo]-[10944]
[bill]-[11456]
[tony]-[9900]
[benji]-[7562]
I've figured I could do a descending sort of the score list, but that wouldn't cover the changes in the indexes of the usernames list, what is the best approach for this?
Best approach? Don't use parallel collections anti-pattern.
Instead of having 2 lists, create a class that can hold both the name and the score together
class Score
{
public string Name { get; private set; }
public int Score { get; private set; }
public Score(string name, int score)
{
Name = name;
Score = score;
}
}
and have just one list
List<Score> scores = new List<Score>();
foreach (string entry in splitscores)
{
string replace = entry.Replace("[", "").Replace("]", "");
string[] splitentry = replace.Split('-');
if (splitentry.Count() > 1)
{
scores.Add(new Score(splitentry[0], Int32.Parse(splitentry[1]))
}
}
You can easily order by one property and because the whole object will be reordered you'll keep the names in the right order without any additional code:
topScores = scores.OrderByDescending(x => x.Score).Take(5);
In addition to MarcinJuraszeks useful answer, some small things that I came across using his solution which I decided to share.
First problem was with the class which threw me the following error
'Score': member names cannot be the same as their enclosing type
Changing the case of "s" fixed it
class Score
{
public string Name { get; private set; }
public int score { get; private set; }
public Score(string name, int Score)
{
Name = name;
score = Score;
}
}
And calling the individual values can be done with Linq
string numberOne = topScores.Skip(0).First().score
string numberTwo = topScores.Skip(1).First().score
and so on

Categories