Organize values in an array but keep items "paired together" - c#

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);

Related

Creating new class instance in a loop

I've just started learning about methods and classes, I would like to know if I can have something like,
CarsSold Day1 = new CarsSold();
in a for loop where it will create a new instance of a new day each time it runs. For example on the second time the loop runs I want it to create an instance like this,
CarsSold Day2 = new CarsSold();
I have tried to use an array but either it cant be done with arrays or I'm using the wrong syntax. Thanks in advance.
Full code
class Program
{
static void Main(string[] args)
{
int[] weekDay = new int[7];
int userInput;
int x;
for (x = 0; x < weekDay.Length; x++)
{
Console.Write("Enter the number of cars sold: ");
bool ifInt = int.TryParse(Console.ReadLine(), out userInput);
CarsSold Day[x] = new CarsSold(userInput);
}
}
}
The problem is how you're trying to define your array. The syntax is invalid, and you're doing it in the wrong place.
You should define the array before your loop, and then only assign values to the array within the loop.
static void Main(string[] args)
{
int userInput;
CarsSold[] days = new CarsSold[7]; // create an array with 7 items
int x;
for (x = 0; x < days.Length; x++)
{
Console.Write("Enter the number of cars sold: ");
bool ifInt = int.TryParse(Console.ReadLine(), out userInput);
days[x] = new CarsSold(userInput); // assign a value to the days array at position x.
}
}
Note also that arrays start from 0, not 1, so the first item is actually days[0], and the last is days[6] (for a 7-item array). I took the liberty of removing weekDays since it wasn't needed here, and replaced it in the loop with days.Length.
Arrays can have set amount of things in them, so if you declare an array like this
object[] objectArray = new object[10];
Then that array can hold only 10 objects. If you want to add anything to an array you have to chose an index to which that thing will be assigned to, for example:
objectArray[2] = "some value";
in Your case you could iterate through the array and add new object to each index like this
for (var i = 0; i < objectArray.Length; i++)
{
objectArray[i] = new YourObject();
}
If the amount of objects you want to store is unknown and can change then you should use collections, for example a List
List<object> listOfObjects = new List<object>();
Now if you want to add anything to that list you simply do
listOfObjects.Add(itemYouWantToAddToTheList);
You access lists the same way you would access arrays, so you can use indexes like
var someValue = listOfObjects[0];
As you probably noticed this list has a generic parameter <object>, it tells the list what type of data it can store, in my example its the object type so it can pretty much store anything, but you can set it to string or int or any other type like your own class types and then this list would store only those types of objects.
If you don't know the number of days, then:
IList<CarsSold> soldCarsList = new List<CarsSold>();
foreach(var day in Days)
{
soldCarsList.Add(new CarsSold());
}
If you know the number of days(e.g:7), then:
CarsSold[] soldCarsArray = new CarsSold[7];
for (int i = 0; i < days.Length; x++)
{
soldCarsArray[i] = new CarsSold();
}

select randomly from string array without repetitions

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;
}
}

C# Sorting a list of class objects

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));

How do I split an array into two different arrays?

I can't seem to figure out how to fix my code so that it works. I need the user to be able to input their first name then space then the what they scored. Then I need to split the array into two different arrays and pass them to the four different methods to display to the user what they scored, etc. Can anyone help me figure this problem out?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace proj09LEA
{
class Program
{
static void Main(string[] args)
{
// declare and array of integers
int[] array = new int[10];
Console.WriteLine("\nSaturday Coder's Bowling Team");
Console.WriteLine("Enter in a name and score for each person on the team.");
Console.WriteLine("For example, Mary 143. Just hit Enter when you are done.\n");
// fill an array with user input
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine("Enter in a name and score: ");
string userInput;
string[] parsedInput;
parsedInput = userInput.Split();
string name = parsedInput[0];
int score = int.Parse(parsedInput[1]);
}
Console.WriteLine("------------ Input Complete ------------\n");
Console.WriteLine("Here are the scores for this game:");
DisplayScore(array);
HighScore(array);
LowScore(array);
AverageScore(array);
Console.WriteLine("Press Enter to continue. . .");
Console.ReadLine();
}
static void DisplayScore(int[] array)
{
foreach (int n in array)
{
Console.WriteLine("{0}'s score was {0}.\n", array);
}
}
static void HighScore(int[] array)
{
int max = array.Max();
Console.WriteLine("Congratulations {0}, your score of {0} was the highest.", max);
}
static void LowScore(int[] array)
{
int min = array.Min();
Console.WriteLine("{0}, your score of {0} was the lowest. Better get some practice.", min);
}
static void AverageScore(int[] array)
{
int sum = array.Sum();
int average = sum / array.Length;
Console.WriteLine("The average score for this game was {0:d}.", average);
}
}
}
If you absolutely have to use simple primitive arrays, you would need two distinct arrays of the same size, to hold the names as strings and scores as ints:
class Program
{
const int MaxScores = 10; // .. Use a constant to ensure the sizes remain in sync
static void Main(string[] args)
{ ///
string[] names = new int[MaxScores];
int[] scores = new int[MaxScores];
// ... parse names into names[] and scores into scores[]
DisplayScore(names, scores);
You would then need to pass both arrays to the various methods:
static void DisplayScore(string[] names, int[] scores)
{
for(int i=0; i < MaxScores; i++)
{
Console.WriteLine("{0}'s score was {1}.\n", names[i], scores[i]);
}
}
// etc
However, there are better ways to do this, e.g. by defining a custom class for the tuple of Name, Score:
class PersonScore
{
public string Name {get; set;}
public int Score {get; set;}
}
You can then declare and pass the single array of PersonScore[] around.
PersonScore[] personScores = new PersonScore[MaxScores];
for (... prompting the user for data)
{
... parsing user input
personScores[i] = new PersonScore{Name = name, Score = score};
}
DisplayScore(personScores); // Pass around the single array
static void DisplayScore(IEnumerable personScores)
{
foreach(var personScore in personScores)
{
Console.WriteLine("{0}'s score was {1}.\n", personScore.Name, personScores.Score);
}
}
// etc - other methods
As others have mentioned, other collections are also possible alternatives to an array, most commonly List.
You can do like this. Just use Console.ReadLine() to get user input. This is what you do in your code. There are better ways to do this but following will solve your problem.Also you need to perform validation as well.
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine("Enter in a name and score: ");
string userInput = Console.ReadLine();
string[] parsedInput;
parsedInput = userInput.Split(' ');
string name = parsedInput[0];
int score = int.Parse(parsedInput[1]);
array[i] = score;
}
Why you need to split array in to two arrays on containing names and other containing score. Its better to create a structure having String field for name and integer field for score and write Comparator for sorting the Array containing elements of this Data structure type and sort them.
It will solve all your problems and that too efficiently.
Not many data integrity checks in the methods you are using, but here are the extensions I use to split arrays or any type of enumerable. I have not tested these all that much, so I cannot guarantee that they will work. I have removed all my input validation, but I suggest you add those back your own way.
public static List<List<T>> Split<T>(this IEnumerable<T> collection, Int32 groupSize)
{
var collectionList = collection.ToList();
if (groupSize > collectionList.Count)
groupSize = collectionList.Count;
var chunks = new List<List<T>>();
while (collectionList.Any())
{
var chunk = collectionList.Take(groupSize);
chunks.Add(chunk.ToList());
collectionList = collectionList.Skip(groupSize).ToList();
}
return chunks;
}
public static List<List<T>> Split<T>(this IEnumerable<T> collection, Func<T, Boolean> splitFunction)
{
var collectionList = collection.ToList();
if (collectionList.IsNullOrEmpty())
return new List<List<T>>();
var indices = collectionList.FindIndices(splitFunction); // Custom method that searches for the indices that satisfy the predicate and returns the index of each matching item in the list.
if (indices.IsNullOrEmpty()) // equivalent to indices == null || !indices.Any()
return new List<List<T>> { collectionList };
var chunks = new List<List<T>>();
var lastIndex = 0;
if (indices[0] > 0)
{
chunks.Add(collectionList.Take(indices[0]).ToList());
lastIndex = indices[0];
}
for (var i = 1; i < indices.Count; i++)
{
var chunkSize = indices[i] - lastIndex;
var chunk = collectionList.Skip(lastIndex).Take(chunkSize).ToList();
if (chunk.IsNullOrEmpty())
{
break;
}
chunks.Add(chunk);
lastIndex = indices[i];
}
if (collectionList.Count - lastIndex > 0)
{
var lastChunk = collectionList.Skip(lastIndex).ToList();
chunks.Add(lastChunk);
}
return chunks;
}

only return random number when it is unique

My brain is melting today and i cannot think how to do this simple bit of code. numberList is a string of numbers seperated by commas like '2, 34, 10' etc.. when i request a random number i need to check if the string has the number, if it does i want to keep requesting a random number until the random number is definitely not in the string. i cant think what kind of loop i would do to get this to work:
Random r = new Random();
public int RandomPos(int max) {
int i;
do {
i = r.Next(max) + 1;
}
while (!numberList.Contains(i.ToString()));
return i;
}
I'll just explain in text instead of code because I'm too lazy to write the code right now:
Use String.Split to break your list into an array, then (if you need to) parse it into integers.
Use Enumerable.Range(0, max).ToArray() to create a list of all the numbers you could select.
Subtract the first list from the second.
Randomly select an element from the final list.
This has the benefit that you don't need to keep picking things randomly and retrying in a potentially-infinite-but-not-really-in-practice loop.
edit: here's some code
string[] invalid = numberList.Split(", ");
var list = Enumerable.Range(0, max).Where(x => !invalid.Contains(x.ToString())).ToArray();
return list[r.Next(list.Count)];
Remove the !
do
{
i = r.Next(max) + 1;
}
while (numberList.Contains(i.ToString()));
Try it with this:
static string invalidNumbers = "0,1,2,3,4,5";
static Random random = new Random();
static int Randomize()
{
var randomInt = random.Next(0, 10);
if (!invalidNumbers.Split(',').Contains(randomInt.ToString()))
{
return randomInt;
}
else
{
return Randomize();
}
}
Providing a simple answer, you don't need Split(). This assumes no spaces between numbers, modify accordingly:
String modifiedNumberList = "," + numberList + ",";
do {
i = r.Next(max) + 1;
}
while (modifiedNumberList.Contains("," + i.ToString() + ","));
edit: I believe BrokenGlass is also right, you shouldn't have the "!", removed from my solution.
Maybe this is what you want? I used a regular while instead since I think they are easier to read, and the only thing I think you get wrong was the !.
public int RandomPos(int max) {
int i = r.Next(max);
var intList = numberList.Split(',').ToDictionary<string,int>((n) => int.Parse(n));
while(intList.Contains(i))
{
i = r.Next(max);
}
return i;
}
Assuming I need to split the numberList first to if they are in a string. That would make the third row look like:
A modification of #Dave's reply:
static string invalidNumbers = "0,1,2,3,4,5";
static Random random = new Random();
static int Randomize()
{
var randomInt = random.Next(0, 10);
var splitString = invalidNumbers.Split(',');
while (splitString.Contains(randomInt.ToString()))
{
randomInt = random.Next(0, 10);
}
return randomInt;
}
A couple ways to improve this:
1) Use a List<int> or something instead of a string to make your life easier
2) if max is small (say <1000 or something) generate the list of all possible values, order them randomly, and return numbers in sequence from that list.
As the number of "used" numbers approaches "max" you could end up in a very long loop before you get an unused number. For values of max over a couple hundred, this could actually be of consequence. This may or may not be a problem in your situation.
This code will cover all cases:
"1,2,3,4,5"...
"1, 2, 3,4,5"...
private static int GetRandomNumber(string existingNumbers, int max)
{
string[] existingNumbersArray = existingNumbers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
List<string> existingNumbersList = new List<string>();
foreach (string number in existingNumbersArray)
{
existingNumbersList.Add(number.Trim());
}
while (true)
{
Random rnd = new Random();
int value = rnd.Next(max);
if (!existingNumbersList.Contains(value.ToString()))
{
return value;
}
}
}
You can even take out this part:
string[] existingNumbersArray = existingNumbers.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
List<string> existingNumbersList = new List<string>();
foreach (string number in existingNumbersArray)
{
existingNumbersList.Add(number.Trim());
}
so it will not be called each time you call GetRandomNumber function.
My addition to all the other answers..
const string invalidNumbers = "0,1,2,3,4,5";
Random random = new Random();
int value = 0;
List<int> tmpList = new List<int>();
foreach (var x in invalidNumbers.Split(','))
{
tmpList.Add(Int32.Parse(x));
}
do
{
value = random.Next(0, 10);
}
while (tmpList.Contains(value));
return value
Edit: missunderstood the question for the first post, anyway, here is a recursive solution.
It seems better to keep the numbers in a List, but if it is required to follow the format you asked, here it is:
const int MAX_ATTEMPTS = 10;
Random r = new Random();
string nlist = "2, 34, 10";
public int RandomPos(int max_val)
{
List<string> used = nlist.Replace(" ","").Split(',').ToList();
return _RandomPos(MAX_ATTEMPTS, max_val, used);
}
private int _RandomPos(int tl, int max, List<string> used)
{
if (tl <= 0)
throw new Exception("Could not generate random number. Too many tries.");
else
{
int rnum = r.Next(max);
if (!used.Contains(rnum.ToString()))
{
nlist += ", " + rnum.ToString();
return rnum;
}
else
return _RandomPos(tl - 1, max, used);
}
}
I realize there are a lot of entries, but I don't see any with some decent error checking. That being said, this will offer a few things:
Won't waste any effort when there's nothing to disqualify
Will only select from a range of possible choices
Will flag -1 if a number can't be chosen within the max range and not in the disqualifying list
So here goes:
public int RandomPos(int max)
{
// compile the list of numbers we need to disqualify
List<int> disqualified = numberList.Split(new[]{',',' '},StringSplitOptions.RemoveEmptyEntries).Select(n => int.Parse(n)).ToList();
// Nothing to check against, save the CPU cycles
if (disqualified.Count == 0)
return (new Random(DateTime.Now.Millisecond)).Next(max) + 1;
// make a list of everything that's possible for a choice
List<int> valid = Enumerable.Range(0, max).Where(r => !disqualified.Contains(r)).ToList();
// return either a valid result, or -1 if there are no valid results
return (valid.Count == 0 ? -1 : valid[(new Random(DateTime.Now.Millisecond)).Next() % valid.Count]);
}
.Split will do the work, the following code will work as well, just for fun (replace the line while (!numberList.Contains(i.ToString())); in your code instead of checking i.ToString() check ","+i.ToString()+"," PLUS the beginning and ending. You need to adjust it if you have a space after ","):
while (!numberList.StartsWith(i.ToString()+",")&&
!numberList.Contains(","+i.ToString()+",")&&
!numberList.EndsWith(","+i.ToString()));
// if numberList is large then use HashSet<int> rather than a plain int[] array
int[] nums = numberList.Split(new[] { ',', ' ' },
StringSplitOptions.RemoveEmptyEntries)
.Select(int.Parse)
.ToArray();
int i;
while (nums.Contains(i = r.Next(max) + 1));
return i;
(You should also add a check to ensure that you don't end up in an infinite loop if/when numberList contains all the possible values that might be produced by the rng.)

Categories

Resources