I'm trying to sort a list in descending order of the 'score' variable. How would I do this?
This is the code I used to set up the list:
private void LeaderboardScreen_Load(object sender, EventArgs e)
{
//GETTING VARIABLES FOR THE CLASS OBJECT
using (var fileStream = File.OpenRead(".\\InputInfo.bin"))
using (var streamReader = new StreamReader(fileStream, true))
{
string line;
while ((line = streamReader.ReadLine()) != null) ;
}
var lines = File.ReadLines(".\\InputInfo.bin");
foreach (var line in lines)
{
string[] words = line.Split(); //Splits the line into seperate words, and puts them into an array called 'words'
string name = words[0]; // Makes the first word the name
string age = words[1]; //Makes the second word the age
string gender = words[2];//Makes the third word the gender
int score = Convert.ToInt32(words[3]);//Makes the forth word the score
//SETTING UP THE LIST
List<Player> players = new List<Player>();
players.Add(new Player(name, age, gender, score));
}
}
THANKS!
using System.Linq;
players = players.OrderByDescending(i => i.Score).ToList();
Since you seems a little newbie at linq, here's an "optimized version"
lines.Select(line =>
{
string[] words = line.Split(); //Splits the line into seperate words, and puts them into an array called 'words'
string name = words[0]; // Makes the first word the name
string age = words[1]; //Makes the second word the age
string gender = words[2];//Makes the third word the gender
int score = Convert.ToInt32(words[3]);//Makes the forth word the score
return new Player(name, age, gender, score);
}).OrderByDescending(i => i.Score).ToList();
It avoid two list instantiations, and two loops over the whole set.
You can simply use the OrderBy statement:
players = players.OrderBy(x => -x.Score).ToList();
By using a minus (-) - I assume score is a numerical value - you reverse the order.
You however made an error by constructing a new List<Player>(); each time in your foreach loop so the list won't store the previous items. You should construct the List<Player> before entering the foreach loop and ordering it after the foreach loop.
While Linq is syntactically shiny, it is a bit wasteful. The final .ToList() is creating a copy the list.
One of many non-Linq solutions is to pass a custom comparison function into Sort(...)
public void DoStuff()
{
List<Player> players = new List<Player>();
foreach (var line in lines)
{
// Fill your players list
}
players.Sort(ComparePlayersDescending);
}
public int ComparePlayersDescending(Player p1, Player p2)
{
int scoreDiff = p2.Score - p1.Score;
if (scoreDiff != 0)
return scoreDiff;
else
return p2.Name.CompareTo(p1.Name);
}
Just for my own curiosity I ran the Linq method and this older one and measured the memory allocated on a list of 50,000 simple player objects. You can either have efficiency or small code but not both :)
players.Sort() allocated 8,192 bytes.
players.OrderByDescending allocated 1,857,296 bytes.
GC.Collect();
long memCur = GC.GetTotalMemory(false);
//players = players.OrderByDescending(i => i.Score).ToList();
players.Sort(ComparePlayersDescending);
long memNow = GC.GetTotalMemory(false);
MessageBox.Show(string.Format("Total Memory: {0} {1}, diff {2}", memCur, memNow, memNow - memCur));
Related
Good day I have some problem regarding selecting a random string from my string array I am currently developing a guessing word game.
this is my string array:
string[] movie = {"deadpool", "batmanvssuperman", "findingdory", "titanic", "suicidesquad", "lordoftherings", "harrypotter", "jurassicpark", "hungergames", "despicableme" };
while this is the process in selecting a random string to my array, what should i do next, because I want to select the string not repeated.
e.g
when the program starts it will select a string then when i select random string again i want to not select the previous word that i've already selected previously.
string word = movie[r.Next(0, movie.Length)].ToUpper();
Thank you for response! Have a nice day.
Well, simply convert your array to list and shuffle it in random order :
var rand = new Random();
string[] movies = { "deadpool", "batmanvssuperman", "findingdory", "titanic", "suicidesquad", "lordoftherings", "harrypotter", "jurassicpark", "hungergames", "despicableme" };
List<string> randomMovies = movies.ToList();
for (int i = 0; i < movies.Length / 2; i++)
{
var randNum = rand.Next(i, randomMovies.Count);
var temp = randomMovies[randNum];
randomMovies[randNum] = randomMovies[i];
randomMovies[i] = temp;
}
Then you can just take random elements by :
var randomMovie = randomMovies.First();
randomMovies.Remove(randomMovie); // either remove it or use loop to iterate through the list
I sort of like to use Queue collection here :
var moviesQueue = new Queue<string>(randomMovies);
while (moviewQueue.Count > 0)
{
Console.WriteLine(moviewQueue.Dequeue());
}
P.S.
As suggested you don't really need to delete elements from randomMovie, you can save last used index in some field and use it later;
var lastIndex = 0;
var randomMovie = randomMovies[lastIndex++];
Just loop if it's been selected. This is untested code:
private string _last;
private string GetNonRepeatedMovie()
{
string selected = "";
do
{
selected = movie[r.Next(0, movie.Length)].ToUpper();
}
while (selected == this._last);
this._last = selected;
return selected;
}
This should work to select the initial string as well when the application starts.
If you need to keep a memory, convert your list to be a class that contains the name and a field of whether it has been chosen or not.
If you go through all of them, turn this semiphor off and begin again.
class GuessingName
{
public GuessingName(string name){Name = name;}
public string Name;
public bool chosen;
}
class RandomNamePicker{
private List<GuessingName> names;
public RandomNamePicker(){
names = new List<GuessingName>();
names.Add(new GuessingName("movie"));
}
string RandomPicker(){
if(names.All(c=>c.chosen))
names.ForEach(c=>c.chosen=false);
int r1 = r.Next(0, names.Length);
while(names[r1].chosen){
r1= r.Next(0,names.Length);
}
return names[r1].Name;
}
}
I'm working on a leaderboard for a game I am making.
In this code I create a list called 'players', it is a list of
objects from a custom class called 'Player'.
The code then reads all of the highscores from a file (which has
already been created), line by line. (Each line takes the format
of "Name Age Gender Score").
For each line it splits up the text into 4 parts and assigns each
of those parts to a variable.
Then it adds these variables to the list 'players'.
The problem I am having is that all of the items in the list end up with the same values.(The values on the last line of my highscores file). Any ideas how to fix this?
Here is the code:
private void LeaderboardScreen_Load(object sender, EventArgs e)
{
string name = "error";
string age = "error";
string gender = "error";
int score = 0;
List<Player> players = new List<Player>();
using (var fileStream = File.OpenRead(".\\InputInfo.bin"))
using (var streamReader = new StreamReader(fileStream, true))
{
string line;
line = streamReader.ReadLine();
}
foreach (var line in File.ReadLines(".\\InputInfo.bin")) // loop for every line in the inputinfo file
{
string[] words = line.Split(); //Splits the line into seperate words, and puts them into an array called 'words'
name = words[0]; // Makes the first word the name
age = words[1]; //Makes the second word the age
gender = words[2];//Makes the third word the gender
score = Convert.ToInt32(words[3]);//Makes the forth word the score
players.Add(new Player(name,age,gender,score)); **//This is where the problem is I think**
}
players = players.OrderByDescending(i => score).ToList(); //sorts list of players into descending order
}
Also if it helps at all here is the class:
public class Player
{
public static int score = 0;
public static string name;
public static string age;
public static string gender;
public Player(string aName, string aAge, string aGender, int aScore)
{
name = aName;
age = aAge;
gender = aGender;
score = aScore;
}
}
Remove static. It signifies that your member is shared by all instances of the class, but you actually want them per-instance.
It would help if you had included the file "InputInfo.bin". Could you please post that?
also you need to change this line:
players.Add(new Player(name,age,gender,score)); //This is where the problem is I think
to this:
players.Add(new Player(name,age,gender,score)); //** This is where the problem is I think**
It will not compile until the // are in front of the **
So for my homework assignment I need to write a type of program that has the user type in the persons name and then their score. It then needs to find the highest, lowest, and the average score and list the player that achieved each one.
static void Main()
{
string userInput;
Console.Write("Please enter bowler's first name and then a score then use a comma to seperate plays\nExample: Elliott 200,John 180,Jane 193\nPlease enter values here: ");
userInput = Console.ReadLine();
char[] delimiters = new char[] { ' ', ','};
string[] parts = userInput.Split(delimiters);
for (int i = 0; i < parts.Length; i++)
{
Console.WriteLine(parts[i]);
}
Console.ReadLine();
}//End Main()
As you can see I've figured out how to split the input, but I don't know how to organize them and pair them together. I've been looking online for an answer and have heard of lists but I've never used them. It this even possible on an array? Do I need to do this on two different arrays?
I have also tried splitting into two arrays but it has a convert issue
string userInput;
const int ZERO = 0;
const int ONE = 1;
const int TEN = 10;
string[] parsedInput = new string[TEN];
string[] name = new string[TEN];
int[] score = new int[TEN];
for (int i = 0; i < TEN; ++i)
{
Console.Write("Please enter bowler's first name and then a score\nExample: Name 200\nPlease enter values here: ", i);
userInput = Console.ReadLine();
parsedInput = userInput.Split();
name = parsedInput[ZERO];
score = int.Parse(parsedInput[ONE]);
}
When you say you don't know how to pair them together, this is what a class is for.
public class Person
{
public string Name { get; set; }
public int Score { get; set; }
}
Once you have that, you're on the right track with using a List instead of an array (not that you couldn't use arrays but this would be the preferred way)
List<Person> people = new List<Person>();
You can then use a loop to go through your list and access the properties.
foreach(Person person in people)
{
Console.WriteLine(person.Name + ", " + person.Score.ToString());
}
The other answer posted is fine, but you should probably focus on something like this first if you are just learning.
You definitely can do that using simple array of string. But I'm not going to give you code like that. You should definitely spend much more time to learn about classes, arrays, lists, loops, etc. You're not gonna learn anything unless you do it by yourself.
And just to show you, how it could be done when you know the language you're using well: LINQ one-liner to get a list of anonymous type objects, sorted descending by the value:
var items = input.Split(',')
.Select(x => x.Split(' '))
.Select(x => new { name = x[0], value = int.Parse(x[1]) })
.OrderByDescending(x => x.value)
.ToList();
And taking what you need from the list:
// min/max are items, so both .name and .value are there
var maxItem = items.First();
var minItem = items.Last();
// average is just a number
var average = items.Average(x => (double)x.value);
I have a list with several words. I want to filter out some of them, which don't match a specific pattern. Is it quicker to add all the matches to a temporary list and copy this list to the main list afterwards? Or is it quicker to remove all the mismatches from the main list?
I have to filter 10000 words as quickly as possible, so I'm looking forward to every little speed increasement.
Edit:
string characters = "aAbBcC";
// currentMatches contains all the words from the beginning
List<string> currentMatches = new List<string>();
List<string> newMatches = new List<string>();
foreach (string word in currentMatches)
{
if (characters.IndexOf(word[0]) > -1)
// word match
{
newMatches.Add(word);
}
}
currentMatches = newMatches;
The foreach loop should check whether word begins with one of the characters of characters. Here I copy every match to newMatches before I copy all the new matches to currentMatches.
Assuming a List<T> then you'll have to take in consideration the following:
If Count is less than Capacity, the Add method is an O(1) operation. If the capacity needs to be increased to accommodate the new element, this method becomes an O(n) operation, where n is Count;
The RemoveAt method is an O(n) operation, where n is (Count - index).
If you create the list to hold the matches with an initial capacity set to the total word count then Add will always be O(1) and faster. However you need to take in consideration the overhead of creating this new list with a capacity set to the total word count.
Bottom line, you need to test it and see what works better for your specific scenario.
Here is an example I threw together on how to time methods. There are many ways to do this, and I think you're going to have to try out a few. You can use information like in João Angelo's post to help direct you towards good approaches, but here are a few. Also, if you're willing to spend the time, you could put this all in a loop that would create a new list, run all of the tests, put the TimeSpan results into a collection instead of Console.WriteLine'ing them, and then give you an average of however many iterations of the test. That would help give you an average.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
List<string> testList = CreateTestList();
const string filter = "abc";
TimeNewListMethod(FilterIntoNewListWithLinq, testList, filter);
TimeInPlaceMethod(FilterInPlaceWithLinq, testList, filter);
TimeNewListMethod(FilterIntoNewListWithForEach, testList, filter);
TimeInPlaceMethod(FilterInPlaceWithRemoveAll, testList, filter);
Console.Read();
}
public static void TimeInPlaceMethod(Action<List<string>, string> testMethod, List<string> toFilter, string filter)
{
List<string> toFilterCopy = new List<string>(toFilter);
DateTime time = DateTime.Now;
testMethod(toFilterCopy, filter);
Console.WriteLine(DateTime.Now - time);
}
public static void TimeNewListMethod(Func<List<string>, string, List<string>> testMethod, List<string> toFilter, string filter)
{
List<string> toFilterCopy = new List<string>(toFilter);
List<string> resultList;
DateTime time = DateTime.Now;
resultList = testMethod(toFilterCopy, filter);
Console.WriteLine(DateTime.Now - time);
}
public static List<string> FilterIntoNewListWithLinq(List<string> toFilter, string filter)
{
return toFilter.Where(element => element.IndexOf(filter) > -1).ToList();
}
public static void FilterInPlaceWithLinq(List<string> toFilter, string filter)
{
toFilter = toFilter.Where(element => element.IndexOf(filter) > -1).ToList();
}
public static List<string> FilterIntoNewListWithForEach(List<string> toFilter, string filter)
{
List<string> returnList = new List<string>(toFilter.Count);
foreach (string word in toFilter)
{
if (word.IndexOf(word[0]) > -1)
{
returnList.Add(word);
}
}
return returnList;
}
public static void FilterInPlaceWithRemoveAll(List<string> toFilter, string filter)
{
toFilter.RemoveAll(element => element.IndexOf(filter) == -1);
}
public static List<string> CreateTestList(int elements = 10000, int wordLength = 6)
{
List<string> returnList = new List<string>();
StringBuilder nextWord = new StringBuilder();
for (int i = 0; i < elements; i++)
{
for (int j = 0; j < wordLength; j++)
{
nextWord.Append(RandomCharacter());
}
returnList.Add(nextWord.ToString());
nextWord.Clear();
}
return returnList;
}
public static char RandomCharacter()
{
return (char)('a' + rand.Next(0, 25));
}
public static Random rand = new Random();
}
}
The whole
characters.IndexOf(word[0]) > -1
was a little unfamiliar to me and so I would go for something more readable and maintainable for future programmers. It took me a minute to figure out you are checking the first char in each string in the list looking for a match in the range of { a A, B, C, a, b, c }. It works, but to me, it was a little cryptic. I'm starting having taken the time to read through it, but I would do it like this:
foreach (string word in currentMatches)
{
if (Regex.IsMatch(word, "^([A-Ca-c])"))
{
newMatches.Add(word);
}
}
I would not worry about copying the temp list back to the initial list. You're already defined it filled it, go ahead and use it.
I wrote the follow c# codes to generate a set of numbers and then compare with another set of numbers to remove the unwanted numbers.
But its taking too long at run time to complete the process. Following is the code behind file.
The numbers it has to generate is like 7 figures large and the numbers list which I use it as to remove is around 700 numbers.
Is there a way to improve the run time performance?
string[] strAry = txtNumbersToBeExc.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
int[] intAry = new int[strAry.Length];
List<int> intList = new List<int>();
for (int i = 0; i < strAry.Length; i++)
{
intList.Add(int.Parse(strAry[i]));
}
List<int> genList = new List<int>();
for (int i = int.Parse(txtStartSeed.Text); i <= int.Parse(txtEndSeed.Text); i++)
{
genList.Add(i);
}
lblStatus.Text += "Generated: " + genList.Capacity;
var finalvar = from s in genList where !intList.Contains(s) select s;
List<int> finalList = finalvar.ToList();
foreach (var item in finalList)
{
txtGeneratedNum.Text += "959" + item + "\n";
}
First thing to do is grab a profiler and see which area of your code is taking too long to run, try http://www.jetbrains.com/profiler/ or http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/.
You should never start performance tuning until you know for sure where the problem is.
If the problem is in the linq query than you could try sorting the intlist and doing a binary search for each item to remove, though you can probably get a similar behavour with the right linq query.
string numbersStr = txtNumbersToBeExc.Text;
string startSeedStr = txtStartSeed.Text;
string endSeedStr = txtEndSeed.Text;
//next, the input type actually is of type int, we should test if the strings are ok ( they do represent ints)
var intAry = numbersStr.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Select(s=>Int32.Parse(s));
int startSeed = Int32.Parse(startSeedStr);
int endSeed = Int32.Parse(endSeedStr);
/*FROM HERE*/
// using Enumerable.Range
var genList = Enumerable.Range(startSeed, endSeed - startSeed + 1);
// we can use linq except
var finalList = genList.Except(intAry);
// do you need a string, for 700 concatenations I would suggest StringBuilder
var sb = new StringBuilder();
foreach ( var item in finalList)
{
sb.AppendLine(string.Concat("959",item.ToString()));
}
var finalString = sb.ToString();
/*TO HERE, refactor it into a method or class*/
txtGeneratedNum.Text = finalString;
They key point here is that String is a immutable class, so the "+" operation between two strings will create another string. StringBuilder it doesn't do this. On your situation it really doesn't matter if you're using for loops, foreach loops, linq fancy functions to accomplish the exclusion. The performance hurt was because of the string concatenations. I'm trusting more the System.Linq functions because they are already tested for performance.
Change intList from a List to a HashSet - gives much better performance when determining if an entry is present.
Consider using Linq's Enumerable.Intersect, especially combined with #1.
Change the block of code that create genList with this:
List<int> genList = new List<int>();
for (int i = int.Parse(txtStartSeed.Text); i <= int.Parse(txtEndSeed.Text); i++)
{
if (!intList.Contains(i)) genList.Add(i);
}
and after create txtGeneratedNum looping on genList. This will reduce the number of loop of your implementation.
Why not do the inclusion check when you are parsing the int and just build the result list directley.
There is not much point in iterating over the list twice. In fact, why build the intermediate list at all !?! just write straight to a StringBuilder since a newline delimited string seems to be your goal.
string[] strAry = txtNumbersToBeExc.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
var exclusions = new HashSet<T>();
foreach (string s in txtNumbersToBeExc.Text.Split(new string[] { Environment.NewLine })
{
int value;
if (int.TryParse(s, value)
{
exclusions.Add(value);
}
}
var output = new StringBuilder();
for (int i = int.Parse(txtStartSeed.Text); i <= int.Parse(txtEndSeed.Text); i++)
{
if (!exclusions.Contains(i))
{
output.AppendFormat("959{0}\n", i);
}
}
txtGeneratedNum.Text = output.ToString();