Getting top values from a list in c sharp - c#

So I'm having some trouble with a C# program that is meant to sum the 8 highness value from a list.
The program works by declaring a variable currenthigh, which stores a value from the gradelist. It then compares itself to the value abshigh to see if it is greater than the established highest value. If it is, it sets currenthigh as the new highest value.
Once the loop has run through the list and confirmed the highest value, it adds it to the uppertotal variable and uses the ejector variable to remove it from the list. The program then iterates, this time without the previous highest value. It iterates 8 times so that in the end the top 8 values are added to uppertotal.
Trouble is, the highest variable remains in the list, despite the code having instructions to delete it, so it just adds the highest value to itself 8 times.
int currenthigh = 0;
int abshigh = 0;
int ejector = 0;
int uppertotal = 0;
for (int g = 0; g < 8; g++)
{
for (int z = 0; z < gradelist.Count; z++)
{
Console.WriteLine("PASS STARTED");
currenthigh = Convert.ToInt32((gradelist[z]));
Console.WriteLine("currenthigh" + currenthigh);
if (currenthigh > abshigh)
{
abshigh = currenthigh;
ejector = z;
}
}
Console.WriteLine("ejector" + ejector);
uppertotal = uppertotal + currenthigh;
gradelist.RemoveAt(ejector);
Console.WriteLine("PASS COMPLETE");
Console.WriteLine("RESETING");
}
Note - gradelist is a list of integers containing at least 12 items at all time.

This happens because you do not remove the highest value from gradelist. Pay attention, you put Z in ejector, but Z is an index in gradelist and when you try to remove it, you remove nothing because there is no Z in gradelist as a member! Instead of
gradelist.RemoveAt(ejector);
you should do this:
gradelist.RemoveAt(gradelist[ejector]);
But I'd recommend you completely different approach. If you just want to get your uppertotal which is the sum of top 8 members in gradlist, use this simple trick:
uppertotal += gradelist.OrderByDescending(p => p).Take(8).Sum();

Your code is extremely big for the relatively simple task.
You can select the top X of a list as follows:
top_list = gradelist.OrderByDescending(i => i).Take(X)
When working with lists/collections System.Linq is your friend

Related

C# - How to place a given number of random mines in an array

I'm new to c#. I have a task to make a type of minesweeper, but which immediately opens a solution.
static void Main(string[] args)
{
Console.Write("Enter the width of the field: ");
int q = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter the length of the field: ");
int w = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter the number of bombs: ");
int c = Convert.ToInt32(Console.ReadLine());
Random rand = new Random();
var charArray = new char[q, w];
var intArray = new int[q, w];
for (int i = 0; i < q; i++)
{
for (int j = 0; j < w; j++)
{
intArray[i, j] = rand.Next(2);
charArray[i, j] = intArray[i, j] == 0 ? '_' : '*';
Console.Write(charArray[i, j]);
}
Console.WriteLine();
}
}
}
}
Two arrays should be output. Everything should be closed on the first one, that is, there should be only the characters: _ and *
0 - these are places without mines, I replaced them with a symbol _
1 - these are places with mines, I replaced them with an asterisk symbol, but they do not accept the number of mines entered by the user. And it is necessary that there are as many "*" characters as there are mines.
And in the second array there should be an already open solution of the game. That is, the cells next to which there are mines should take a number meaning the number of mines next to this cell.
Please help me..
Compiling the current code
Random random = new Random();
while(c > 0)
{
var rq = random.Next(q);
var rw = random.Next(w);
if(intArray[rq,rw] == 0)
{
intArray[rq, rw] = 1;
c--;
}
}
I would suggest dividing the problem in smaller manageable chunks. For instance, you can place the bombs in a initial step, and on a second step build the solution. You can build the solution at the same time you place the bombs, although for clarity you can do it after.
Naming of variables is also important. If you prefer using single letter variable names, I believe that's fine for the problem limits, however I would use meaningful letters easier to remember. eg: W and H for the width and height of the board, and B for the total number of bombs.
The first part of the problem then can be described as placing B bombs in a WxH board. So instead of having nested for statements that enumerate WxH times, it's better to have a while loop that repeats the bomb placing logic as long as you have remaining bombs.
Once you generate a new random location on the board, you have to check you haven't placed a bomb there already. You can have an auxiliary function HasBomb that checks that:
bool HasBomb(char[,] charArray, int x, int y)
{
return charArray[x,y] == '*';
}
I'll leave error checking out, this function being private can rely on the caller sending valid coordinates.
Then the bomb placing procedure can be something like:
int remainingBombs = B;
while (remainingBombs > 0)
{
int x = rand.Next(W);
int y = rand.Next(H);
if (!HasBomb(charArray, x, y)
{
charArray[x,y] = '*';
remainingBombs--;
}
}
At this point you may figure out another concern. If the number B of bombs to place is larger than the available positions on the board WxH, then you wont be able to place the bombs on the board. You'll have to check for that restriction when requesting the values for W, H and B.
Then in order to create the array with the number of bombs next to each position, you'll need some way to check for all the neighbouring positions to a given one. If the position is in the middle of the board it has 8 neighbour positions, if it's on an edge it has 5, and if it's on a corner it has 3. Having a helper function return all the valid neighbour positions can be handy.
IEnumerable<(int X, int Y)> NeighbourPositions(int x, int y, int W, int H)
{
bool leftEdge = x == 0;
bool topEdge = y == 0;
bool rightEdge = x == W - 1;
bool bottomEdge = y == H - 1;
if (!leftEdge && !topEdge)
yield return (x-1, y-1);
if (!topEdge)
yield return (x, y-1);
if (!rightEdge && !topEdge)
yield return (x+1, y-1);
if (!leftEdge)
yield return (x-1, y);
if (!rightEdge)
yield return (x+1, y);
if (!leftEdge && !bottomEdge)
yield return (x-1, y+1);
if (!bottomEdge)
yield return (x, y+1);
if (!rightEdge && !bottomEdge)
yield return (x+1, y+1)
}
This function uses Iterators and touples. If you feel those concepts are too complex as you said are new to C#, you can make the function return a list with coordinates instead.
Now the only thing left is to iterate over the whole intArray and increment the value on each position for each neighbour bomb you find.
for (int x = 0; x < W; x++)
{
for (int y = 0; y < H; y++)
{
foreach (var n in NeighbourPositions(x, y, W, H))
{
if (HasBomb(charArray, n.X, n.Y))
intArray[x,y]++;
}
}
}
The answers here are mostly about generating random x and random y put in loop and trying to put the mine into empty cells. It is ok solution, but if you think of it, it is not that sufficient. Every time you try to find a new random cell, there is chance that cell is already a mine. This is pretty much alright, if you don't put too many mines into your field, but if you put some greater number of mines, this event can occur quite often. This means that the loop might take longer than usually. Or, theoretically, if you wanted to put 999 mines into 1000 cell field, it would be really hard for the loop to fill all the necessary cells, especially for the last mine. Now, I am not saying that the solutions here are bad, I think, it's really alright solution for many people. But if someone wanted a little bit efficient solution, I have tried to crate it.
Solution
In this solution, you iterate each cell and try to calculate a probability of the mine to be placed there. I have come up with this easy formula, which calculates the probability:
Every time you try to place a mine into one cell, you calculate this formula and compare it to random generated number.
bool isMine = random.NextDouble() < calcProbability();

Given an array of integers, how can I find all common multiples up to a maximum number?

This is my first question on this site. I am practicing on a problem on Hackerrank that asks to find numbers "Between two Sets". Given two arrays of integers, I must find the number(s) that fit the following two criteria:
1) The elements in the first array must all be factors of the number(s)
2) The number(s) must factor into all elements of the second array
I know that I need to find all common multiples of every element in the first array, but those multiples need to be less than or equal to the minimum value of the second array. I first sort the first array then find all the multiples of ONLY the largest number in the first array (again, up to a max of the second array's minimum) and store those multiples in a list. Then, I move on to the second largest element in the first array and test it against the array of existing multiples. All elements in the list of existing multiples that isn't also a multiple of the second largest element of the first array is removed. I then test the third largest value of the first array, all the way to the minimum value. The list of existing multiples should be getting trimmed as I iterate through the first array in descending order. I've written a solution which passes only 5 out of the 9 test cases on the site, see code below. My task was to edit the getTotalX function and I created the getCommonMultiples function myself as a helper. I did not create nor edit the main function. I am not sure why I am not passing the other 4 test cases as I can't see what any of the test cases are.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
class Solution {
/*
* Complete the getTotalX function below.
*/
static int getTotalX(int[] a, int[] b) {
//get minimum value of second array
int b_min = b.Min();
//create List to hold multiples
List<int> multiples = getCommonMultiples(a, b_min);
//create List to hold number of ints which are in solution
List<int> solutions = new List<int>();
foreach(int x in multiples)
{
foreach(int y in b)
{
if (y % x == 0 && !solutions.Contains(x))
{
solutions.Add(x);
}
else
{
break;
}
}
}
return solutions.Count;
}
static List<int> getCommonMultiples(int[] array, int max)
{
//make sure array is sorted
Array.Sort(array);
int x = array.Length - 1; //x will be the last # in array -- the max
int y = 1;
//find all multiples of largest number first and store in a list
int z = array[x] * y;
List<int> commonMultiples = new List<int>();
while(z <= max)
{
commonMultiples.Add(z);
y++;
z = array[x] * y;
}
//all multiples of largest number are now added to the list
//go through the smaller numbers in query array
//only keep elements in list if they are also multiples of smaller
//numbers
int xx = array.Length - 2;
for(int a = array[xx]; xx >= 0; xx--)
{
foreach(int b in commonMultiples.ToList())
{
if (b % a != 0)
{
commonMultiples.Remove(b);
}
else
{
continue;
}
}
}
return commonMultiples;
}
static void Main(string[] args) {
TextWriter tw = new StreamWriter(#System.Environment.GetEnvironmentVariable("OUTPUT_PATH"), true);
string[] nm = Console.ReadLine().Split(' ');
int n = Convert.ToInt32(nm[0]);
int m = Convert.ToInt32(nm[1]);
int[] a = Array.ConvertAll(Console.ReadLine().Split(' '), aTemp => Convert.ToInt32(aTemp))
;
int[] b = Array.ConvertAll(Console.ReadLine().Split(' '), bTemp => Convert.ToInt32(bTemp))
;
int total = getTotalX(a, b);
tw.WriteLine(total);
tw.Flush();
tw.Close();
}
}
Again, I can't see the test cases so I do not know what exactly the issue is. I went through the code line by line and can't find any OutOfBoundExceptions or things of that sort so it has to be a logic issue. Thanks for the help!
A typical sample involves 3 lines of input. The first line has 2 integers which gives the length of the first array and the second array, respectively. The second line will give the integers in the first array. The third line will give the integers in the second array. The output needs to be the total number of integers "in between" the two arrays. It will looks like this:
Sample Input
2 3
2 4
16 32 96
Sample Output
3
Explanation: 2 and 4 divide evenly into 4, 8, 12 and 16.
4, 8 and 16 divide evenly into 16, 32, 96.
4, 8 and 16 are the only three numbers for which each element of the first array is a factor and each is a factor of all elements of the second array.
I see two issues with the code you posted.
Firstly, as #Hans Kesting pointed out, a = array[xx] is not being updated each time in the for loop. Since the variable a is only used in one spot, I recommend just replacing that use with array[xx] and be done with it as follows:
for(int xx = array.Length - 2; xx >= 0; xx--)
{
foreach(int b in commonMultiples.ToList())
{
if (b % array[xx] != 0)
{
commonMultiples.Remove(b);
For your understanding of for loops: to properly increment a each time you'd write the for loop like this:
for(int xx = array.Length - 2, a = array[xx]; xx >= 0; xx--, a = array[xx])
The first part of the for loop (up until ;) is the initialization stage which is only called before the entering the loop the first time. The second part is the while condition that is checked before each time through loop (including the first) and if at any time it evaluates to false, the loop is broken (stopped). The third part is the increment stage that is called only after each successful loop.
Because of that in order to keep a up to date in the for loop head, it must appear twice.
Secondly, your solutions in getTotalX is additive, meaning that each multiple that works for each value in array b is added as a solution even if it doesn't fit the other values in b. To get it to work the way that you want, we have to use a Remove loop, rather than an Add loop.
List<int> multiples = getCommonMultiples(a, b_min);
//create List to hold number of ints which are in solution
List<int> solutions = multiples.ToList();
foreach(int x in multiples)
{
foreach(int y in b)
{
if (y % x != 0)
{
solutions.Remove(x);
break;
}
}
}
You could also use LINQ to perform an additive solution where it takes into account All members of b:
//create List to hold number of ints which are in solution
List<int> solutions = multiples.Where((x) => b.All((y) => y % x == 0)).ToList();

For Loop isnt being fully completed

I have a For Loop which handles taking cards from the master deck, and putting them in a random order into the players deck. The code is:
for(int a = 0; a < deckManager.DeckAllCardsPlayer.Count; a++){
int b = Random.Range(0, deckManager.DeckAllCardsPlayer.Count);
if(!PlayerDeck.Contains(deckManager.DeckAllCardsPlayer[b])){
PlayerDeck.Add(deckManager.DeckAllCardsPlayer[b]);
deckManager.DeckAllCardsPlayer.RemoveAt(b);
}
}
There are 16 cards in the master deck, but this for loop only does 8. Can someone figure out why? Originally, it was adding some cards multiple times, which is why I added the '!PlayerDeck.Contains' statement. I have no idea why it's only doing 8 of 16.
The issue is that deckManager.DeckAllCardsPlayer.Count is getting smaller at each iteration. Try this instead:
while (deckManager.DeckAllCardsPlayer.Count > 0) {
int b = Random.Range(0, deckManager.DeckAllCardsPlayer.Count);
PlayerDeck.Add(deckManager.DeckAllCardsPlayer[b]);
deckManager.DeckAllCardsPlayer.RemoveAt(b);
}
I removed the conditional, because it shouldn't be necessary. (Unless the starting deck has duplicates? If so, just put it back in.)
You start off with 16 cards, but remove one each time.
As a result, although a decrease by one each iteration, the value of deckManager.DeckAllCardsPlayer.Count goes down.
After 8 iterations, a is 7, but the size of DeckAllCardsPlayer has been reduced to 8. Hence the loop terminates on the next turn.
One way to work around would be to take the count up front and store in an integer:
int totalCards = deckManager.DeckAllCardsPlayer.Count;
for(int a = 0; a < totalCards; a++){
... etc.
Although there are many other ways, depending on the logic that you want to expose.
This question may be enlightening : Is the condition in a for loop evaluated each iteration?
The upperbound or limit of the loop is changing as you remove cards from deck.
To fix it with just 2 line code change,
Try this
int count = deckManager.DeckAllCardsPlayer.Count;
for(int a = 0; a < count; a++){
int b = Random.Range(0, deckManager.DeckAllCardsPlayer.Count);
if(!PlayerDeck.Contains(deckManager.DeckAllCardsPlayer[b])){
PlayerDeck.Add(deckManager.DeckAllCardsPlayer[b]);
deckManager.DeckAllCardsPlayer.RemoveAt(b);
}
}
Assuming PlayerDeck & deckManager.DeckAllCardsPlayer are both lists then just do this:
PlayerDeck.AddRange(deckManager.DeckAllCardsPlayer.OrderBy(x => Random.value));
deckManager.DeckAllCardsPlayer.Clear();
Then you don't have to worry about removing elements while you iterate (which you should never do).
int count = deckManager.DeckAllCardsPlayer.Count;
for(int a = 0; a < count; a++){
int b = Random.Range(0, deckManager.DeckAllCardsPlayer.Count);
if(!PlayerDeck.Contains(deckManager.DeckAllCardsPlayer[b])){
PlayerDeck.Add(deckManager.DeckAllCardsPlayer[b]);
deckManager.DeckAllCardsPlayer.RemoveAt(b);
}
}
You must use count variable. Because deckManager.DeckAllCardsPlayer.Count will be changed after run deckManager.DeckAllCardsPlayer.RemoveAt(b). I hope it will work for you.

C# Using .Count with Tuple Items

I am trying to compare the contents of a number of Tuples inside of a List. The code runs reasonable well, but on certain occasions, completeList[z].Item1.Count will come back with the wrong number. When I look in the debugger, in one example a = completeList[z].Item1.Count might return 18 when the count is 9. So this causes it to go out of range.
completeList.Count returns correctly, as does completeList[z].Item1[b], so I'm not sure what I could be doing wrong with completeList[z].Item1.Count.
Thanks for any insights!
int x = completeList.Count;
int y = 0;
while (y < x)
{
for (int z = 0; z < x; z++)
{
if (y != z && completeList[y].Item6.Equals(completeList[z].Item6))
{
int a = completeList[z].Item1.Count;
a = a -1;
for (int b = 0; b < a; b++)
{
if (completeList[y].Item1.Contains(completeList[z].Item1[b]))
{
completeList[z].Item1.RemoveAt(b);
completeList[z].Item2.RemoveAt(b);
completeList[z].Item3.RemoveAt(b);
completeList[z].Item4.RemoveAt(b);
completeList[z].Item5.RemoveAt(b);
}
}
}
}
y++;
}
You're removing items from the list. This is changing the count, and also moving around the indexes of the existing items. The count is correct at the start of the loop, but by the time you've actually removed an item, the count is no longer valid.
The most effective solution is to not iterate the list while removing items at all; instead rely on the list's RemoveAll method to remove all of the items that match your list of items to remove. While it's possible for you to manage the indexes properly to remove while iterating, you should avoid this complexity.

Random selection for a variable set with a guarantee of every item at least once

I am working on a game in c# but that detail is not really neccessary to solve my problem.
At I high level here is what I want:
I have a set that could have any number of items in it.
I want to randomly select 10 items from that set.
If the set has less than 10 items in then I expect to select the same
item more than once.
I want to ensure every item is selected at least once.
What would be the algorithm for this?
Sorry I'm not sure if this is an appropriate place to ask, but I've got no pen and paper to hand and I can't quite get my head round what's needed so appreciate the help.
In addition I might also want to add weights to the items to
increase/decrease chance of selection, so if you are able to
incorporate that into your answer that would be fab.
Finally thought I should mention that my set is actually a List<string>, which might be relevent if you prefer to give a full answer rather than psuedo code.
This is what I use to randomize an array. It takes an integer array and randomly sorts that list a certain amount of times determined by the random number (r).
private int[] randomizeArray(int[] i)
{
int L = i.Length - 1;
int c = 0;
int r = random.Next(L);
int prev = 0;
int curr = 0;
int temp;
while (c < r)
{
curr = random.Next(0, L);
if (curr != prev)
{
temp = i[prev];
i[prev] = i[curr];
i[curr] = temp;
c++;
}
}
return i;
}
If you look for effective code, my answer isnt it. In theory, create some collection you can remove from that will mirror your set. Then select random member of the object from it ...and remove, this will garantee items wont repeat(if possible).
Random rnd = new Random();
List<int> indexes = new List<int>(items.Count);
for (int i = 0; i < items.Count; i++)
indexes.Add(i);
List<string> selectedItems = new List<string>(10);
int tmp;
for(int i = 0; i < 10; i++)
{
tmp = rnd.Next(1,10000); //something big
if(indexes.Count > 0)
{
selectedItems.Add(yourItems[indexes[tmp%indexes.Count]]);
indexes.RemoveAt(tmp%indexes.Count);
}
else
selectedItems.Add(yourItems[rnd.Next(0,9)]); //you ran out of unique items
}
where items is your list and yourItems is list of selected items, you dont need to store them if you want process them right away
Perhaps shuffle the collection and pick elements from the front until you have the required amount.
Once you've gone through all the elements, you should perhaps shuffle it again, so you don't just repeat the same sequence.
The basic algorithm for shuffling: (in pseudo-code)
for i from n − 1 downto 1 do
j ← random integer with 0 ≤ j ≤ i
exchange a[j] and a[i]
With the above algorithm (or a minor variation), it's possible to just shuffle until you reach the required number of elements, no need to shuffle the whole thing.

Categories