Exiting 'do while ReadLine' loop not working - c#

I am trying to learn C# as I have little experience there. I wanted to set up a Console.Readline in a Do While loop so I can read in an unknown amount of values into an array. It never exits so the comparison in the while is not working. What is wrong?
do
{
line = Console.Read();
if (line != null)
numbers[c++] = line;
numbers = new int[c+1];
} while (line != null);

First of all, I would use a List<int> to save the input values. This avoids the necessity to redimension the array, and you could use the list as a normal array.
Second, there is a lot of difference between the Console.Read and Console.ReadLine. The first returns one by one the character codes of the characters typed and pressing Enter will simply return the character code (13) of the Enter key. To exit the loop you need to press Ctrl + Z that returns -1, not null.
List<int> numbers = new List<int>();
int line;
do
{
line = Console.Read();
if (line != -1)
numbers.Add(line);
} while (line != -1);
for(int x = 0; x < numbers.Count(); x++)
Console.WriteLine(numbers[x]);
However, this is probably not what you really want. If you need to store the numbers typed as real integer numbers you need code like this:
List<int> numbers = new List<int>();
string line = Console.ReadLine();
int number;
do
{
if(int.TryParse(line, out number))
numbers.Add(number);
} while (!string.IsNullOrWhiteSpace(line = Console.ReadLine()));
for(int x = 0; x < numbers.Count(); x++)
Console.WriteLine(numbers[x]);
In this version I get the line input before entering the loop and then continue until the Enter key is pressed. In every loop I try to convert the input to a valid integer number.
Note also the usage of TryParse to convert the string to an integer. If your user types something like "abcdef" the TryParse will return false without throwing an exception.
(Thanks #Patrick for its suggestion.)

If you're using Console.Readline() then line will be null if you press Ctrl+Z (as mentioned by #Patrick). You should check for empty string instead:
do
{
line = Console.ReadLine();
if (line != null)
numbers[c++] = int.Parse(line); //assigning string to the last element in the array of ints?
//if you need to parse string to int, use int.Parse() or Convert.ToInt32()
//whatever suits your needs
numbers = new int[c+1];
} while (!string.IsNullOrEmpty(line));
Anyway, it's not clear what you're trying to accomplish with this code as you're creating new array on every iteration. Moreover, this will not work as you have an array of int but assign a string value to it.

Related

How to check and change the values in an array in C#?

I'm working on a very simple tic tac toe project where I check if a user input is in an array (looking for an integer from 1 to 9) and, if not, I want to change an index to that user input. Below is code, don't know what I'm doing wrong. CallSquare() returns an int.
int [] numbersPlayed = {0,0,0,0,0,0,0,0,0};
int callResult;
int totalPlayed = 0;
while (totalPlayed != 10)
{
callResult = CallSquare();
foreach (int i in numbersPlayed)
{
if (numbersPlayed.Contains(callResult))
{
Console.WriteLine("\n\nError, number already in array");
break;
}
else
{
numbersPlayed.SetValue(callResult, i);
}
}
totalPlayed++;
}
Basically, what it does after input is giving me the Error message above, even though I type an integer between 1 and 9, and then, only changes the value in the index number I have entered on the first input (for example, if I enter 1 on the first input, it will only change the first index on following inputs). Help please?
Edit: what I'm trying to do is to keep a record of the numbers that have been played. I figured an array like that was the way to go, but if you have a better solution, I'm listening.
I think, you'd be better off using a List:
List<int> numbersPlayed = new List<int>();
int callResult;
int totalPlayed = 0;
while (totalPlayed < 10)
{
callResult = CallSquare();
if( numbersPlayed.Contains(callResult) )
{
Console.WriteLine("\n\nError, number already in array");
}
else
{
numbersPlayed.Add(callResult);
}
totalPlayed++;
}

Range in for loop using Array

I'm pretty new to C# and want the users to be able to write in 5 numbers between 1 to 25. The issue I'm having is that I don't want the user to type a number over 25 or a number below 1.
Also this is a task for my studies and my teacher want us to use arrays so I'm not allowed to use List.
int[] usernum = new int[4];
for (int i = 0; i < usernum.Length; i++)
{
usernum[i] = Convert.ToInt32(Console.ReadLine());
}
Ok, to start off, some annotations to your code:
int[] usernum = new int[4]; // should be: new int[5];
for (int i = 0; i < usernum.Length; i++)
{
usernum[i] = Convert.ToInt32(Console.ReadLine()); // use int.TryParse instead
}
Now, I don't want to just give you the code, since this should obviously be a learning experience.
What you need to do, though is integrate a "validation" cycle. That means:
Read in string from user
Try to parse string to number
If that fails: back to 1.
Check if number < 1 or > 25
If so: back to 1.
If you are here, you passed both checks and can
set usernum[i] = number
Next "i"
Obviously, there are some slight variations in how you twist and turn your checks and arrange loops which are equally valid.
For example: You can decide if you want to check if number is inside bounds or if you want to check if the number is outside bounds and jump or not jump accordingly ...
Why int.TryParse instead of Convert.ToInt32?
There are some rule of thumbs that can spare you from severe headaches:
"Never trust user input"
"Do not use exceptions for control flow"
Using Convert here, breaks both.
For one, Convert.ToInt32 throws if the string does not represent an integer value (chars other than +-0..9, value > int.Max or < int.Min). So in using it, you trust the user to type in a valid integer. Not a good idea.
Then, it throwing means: the case, that a user (maybe just made a typo) did not provide valid input is controlling your flow to error handling. But this case is not at all "exceptional". In fact, you should expect it. int.TryParse makes this possible, in that it returns you a flag (boolean) that informs you about success or failure of the conversion attempt (instead of throwing).
Though I would recommend you to learn if else loop first https://www.w3schools.com/cs/cs_conditions.asp
here is the code if needed
int[] usernum = new int[4];
for (int i = 0; i < usernum.Length; i++)
{
var result = Console.ReadLine();
int currentResult;
if (!int.TryParse(result, out currentResult))
{
Console.WriteLine("Invalid input - must be a valid integer value");
i--;
continue;
}
if(currentResult < 1 || currentResult > 25)
{
Console.WriteLine("Invalid input - must be between 1 & 25");
i--;
continue;
}
usernum[i] = currentResult;
}
for-loop might not be the ideal solution for this use-case where you need to conditionally increment the index.
This should do the trick:
int[] userNumbers = new int[5];
int i = 0;
while (i < userNumbers.Length)
{
string rawInput = Console.ReadLine();
bool isNumberValid = int.TryParse(rawInput, out int inputNumber); // as suggested by #Fildor
if(isNumberValid && inputNumber >= 1 && inputNumber <= 25) // increment counter only if 1 <= input <= 25
{
userNumbers[i] = inputNumber;
i++;
}
}

Displaying 3 smallest variables in an array on console?

I'm currently doing a beginner's coding exercise, and have run into a problem. The program I made takes 5 numbers, turns them into an array, and displays the three smallest numbers on the console. I have the input separated into an array, and created a new variable containing the three smallest values, but I'm not sure how to display each number in the array on the console.
I know that's a beginner question, but I've been coding less than a week. I tried searching StackOverflow and found a code to display each integer in a list, but am unsure what to change to display each value in an array.
bool isFive = new bool();
Console.WriteLine("Enter at least 5 numbers, separated by a comma.");
while (!isFive)
{
string text = Console.ReadLine();
string[] result = text.Split(',');
int[] resultInt = result.Select(s => int.Parse(s)).ToArray();
if (resultInt.Length < 5)
{
Console.WriteLine("Invalid list, retry.");
}
else
{
isFive = true;
var smallestThree = resultInt.OrderBy(x => x).Take(3);
????????????????
}
}
Almost there. All you need is string.Join:
Console.WriteLine(string.Join(", ", resultInt.OrderBy(x => x).Take(3)));
Also, instead of using int.Parse have a look at int.TryParse: Select parsed int, if string was parseable to int

Reading an int from a file returns ASCII

I'm currently making a game but I seem to have problems reading values from a text file. For some reason, when I read the value, it gives me the ASCII code of the value rather than the actual value itself when I wrote it to the file. I've tried about every ASCII conversion function and string conversion function, but I just can't seem to figure it out.
I use a 2D array of integers. I use a nested for loop to write each element into the file. I've looked at the file and the values are correct, but I don't understand why it's returning the ASCII code. Here's the code I'm using to write and read to file:
Writing to file:
for (int i = 0; i < level.MaxRows(); i++)
{
for (int j = 0; j < level.MaxCols(); j++)
{
fileWrite.Write(level.GetValueAtIndex(i, j) + " ");
//Console.WriteLine(level.GetValueAtIndex(i, j));
}
//add new line
fileWrite.WriteLine();
}
And here's the code where I read the values from the file:
string str = "";
int iter = 0; //used to iterate in each column of array
for (int i = 0; i < level.MaxRows(); i++)
{
iter = 0;
//TODO: For some reason, the file is returning ASCII code, convert to int
//keep reading characters until a space is reached.
str = fileRead.ReadLine();
//take the above string and extract the values from it.
//Place each value in the level.
foreach (char id in str)
{
if (id != ' ')
{
//convert id to an int
num = (int)id;
level.ChangeTile(i, iter, num);
iter++;
}
}
This is the latest version of the loop that I use to read the values. Reading other values is fine; it's just when I get to the array, things go wrong. I guess my question is, why did the conversion to ASCII happen? If I can figure that out, then I might be able to solve the issue. I'm using XNA 4 to make my game.
This is where the convertion to ascii is happening:
fileWrite.Write(level.GetValueAtIndex(i, j) + " ");
The + operator implicitly converts the integer returned by GetValueAtIndex into a string, because you are adding it to a string (really, what did you expect to happen?)
Furthermore, the ReadLine method returns a String, so I am not sure why you'd expect a numeric value to magically come back here. If you want to write binary data, look into BinaryWriter
This is where you are converting the characters to character codes:
num = (int)id;
The id variable is a char, and casting that to int gives you the character code, not the numeric value.
Also, this converts a single character, not a whole number. If you for example have "12 34 56 " in your text file, it will get the codes for 1, 2, 3, 4, 5 and 6, not 12, 34 and 56.
You would want to split the line on spaces, and parse each substring:
foreach (string id in str.Split(' ')) {
if (id.Length > 0) {
num = Int32.Parse(id);
level.ChangeTile(i, iter, num);
iter++;
}
}
Update: I've kept the old code (below) with the assumption that one record was on each line, but I've also added a different way of doing it that should work with multiple integers on a line, separated by a space.
Multiple records on one line
str = fileRead.ReadLine();
string[] values = str.Split(new Char[] {' '});
foreach (string value in values)
{
int testNum;
if (Int32.TryParse(str, out testnum))
{
// again, not sure how you're using iter here
level.ChangeTile(i, iter, num);
}
}
One record per line
str = fileRead.ReadLine();
int testNum;
if (Int32.TryParse(str, out testnum))
{
// however, I'm not sure how you're using iter here; if it's related to
// parsing the string, you'll probably need to do something else
level.ChangeTile(i, iter, num);
}
Please note that the above should work if you write out each integer line-by-line (i.e. how you were doing it via the WriteLine which you remarked out in your code above). If you switch back to using a WriteLine, this should work.
You have:
foreach (char id in str)
{
//convert id to an int
num = (int)id;
A char is an ASCII code (or can be considered as such; technically it is a unicode code-point, but that is broadly comparable assuming you are writing ANSI or low-value UTF-8).
What you want is:
num = (int)(id - '0');
This:
fileWrite.Write(level.GetValueAtIndex(i, j) + " ");
converts the int returned from level.GetValueAtIndex(i, j) into a string. Assuming the function returns the value 5 for a particular i and j then you write "5 " into the file.
When you then read it is being read as a string which consists of chars and you get the ASCII code of 5 when you cast it simply to an int. What you need is:
foreach (char id in str)
{
if (id != ' ')
{
//convert id to an int
num = (int)(id - '0'); // subtract the ASCII value for 0 from your current id
level.ChangeTile(i, iter, num);
iter++;
}
}
However this only works if you only ever are going to have single digit integers (only 0 - 9). This might be better:
foreach (var cell in fileRead.ReadLine().Split(' '))
{
num = Int.Parse(cell);
level.ChangeTile(i, iter, num);
iter++;
}

How to parse a numbered sequence from a List of filenames?

I would like to automatically parse a range of numbered sequences from an already sorted List<FileData> of filenames by checking which part of the filename changes.
Here is an example (file extension has already been removed):
First filename: IMG_0000
Last filename: IMG_1000
Numbered Range I need: 0000 and 1000
Except I need to deal with every possible type of file naming convention such as:
0000 ... 9999
20080312_0000 ... 20080312_9999
IMG_0000 - Copy ... IMG_9999 - Copy
8er_green3_00001 .. 8er_green3_09999
etc.
I would like the entire 0-padded range e.g. 0001 not just 1
The sequence number is 0-padded e.g. 0001
The sequence number can be located anywhere e.g. IMG_0000 - Copy
The range can start and end with anything i.e. doesn't have to start with 1 and end with 9999
Numbers may appear multiple times in the filename of the sequence e.g. 20080312_0000
Whenever I get something working for 8 random test cases, the 9th test breaks everything and I end up re-starting from scratch.
I've currently been comparing only the first and last filenames (as opposed to iterating through all filenames):
void FindRange(List<FileData> files, out string startRange, out string endRange)
{
string firstFile = files.First().ShortName;
string lastFile = files.Last().ShortName;
...
}
Does anyone have any clever ideas? Perhaps something with Regex?
If you're guaranteed to know the files end with the number (eg. _\d+), and are sorted, just grab the first and last elements and that's your range. If the filenames are all the same, you can sort the list to get them in order numerically. Unless I'm missing something obvious here -- where's the problem?
Use a regex to parse out the numbers from the filenames:
^.+\w(\d+)[^\d]*$
From these parsed strings, find the maximum length, and left-pad any that are less than the maximum length with zeros.
Sort these padded strings alphabetically. Take the first and last from this sorted list to give you your min and max numbers.
Firstly, I will assume that the numbers are always zero-padded so that they are the same length. If not then bigger headaches lie ahead.
Secondly, assume that the file names are exactly the same apart from the increment number component.
If these assumptions are true then the algorithm should be to look at each character in the first and last filenames to determine which same-positioned characters do not match.
var start = String.Empty;
var end = String.Empty;
for (var index = 0; index < firstFile.Length; index++)
{
char c = firstFile[index];
if (filenames.Any(filename => filename[index] != c))
{
start += firstFile[index];
end += lastFile[index];
}
}
// convert to int if required
edit: Changed to check every filename until a difference is found. Not as efficient as it could be but very simple and straightforward.
Here is my solution. It works with all of the examples that you have provided and it assumes the input array to be sorted.
Note that it doesn't look exclusively for numbers; it looks for a consistent sequence of characters that might differ across all of the strings. So if you provide it with {"0000", "0001", "0002"} it will hand back "0" and "2" as the start and end strings, since that's the only part of the strings that differ. If you give it {"0000", "0010", "0100"}, it will give you back "00" and "10".
But if you give it {"0000", "0101"}, it will whine since the differing parts of the string are not contiguous. If you would like this behavior modified so it will return everything from the first differing character to the last, that's fine; I can make that change. But if you are feeding it a ton of filenames that will have sequential changes to the number region, this should not be a problem.
public static class RangeFinder
{
public static void FindRange(IEnumerable<string> strings,
out string startRange, out string endRange)
{
using (var e = strings.GetEnumerator()) {
if (!e.MoveNext())
throw new ArgumentException("strings", "No elements.");
if (e.Current == null)
throw new ArgumentException("strings",
"Null element encountered at index 0.");
var template = e.Current;
// If an element in here is true, it means that index differs.
var matchMatrix = new bool[template.Length];
int index = 1;
string last = null;
while (e.MoveNext()) {
if (e.Current == null)
throw new ArgumentException("strings",
"Null element encountered at index " + index + ".");
last = e.Current;
if (last.Length != template.Length)
throw new ArgumentException("strings",
"Element at index " + index + " has incorrect length.");
for (int i = 0; i < template.Length; i++)
if (last[i] != template[i])
matchMatrix[i] = true;
}
// Verify the matrix:
// * There must be at least one true value.
// * All true values must be consecutive.
int start = -1;
int end = -1;
for (int i = 0; i < matchMatrix.Length; i++) {
if (matchMatrix[i]) {
if (end != -1)
throw new ArgumentException("strings",
"Inconsistent match matrix; no usable pattern discovered.");
if (start == -1)
start = i;
} else {
if (start != -1 && end == -1)
end = i;
}
}
if (start == -1)
throw new ArgumentException("strings",
"Strings did not vary; no usable pattern discovered.");
if (end == -1)
end = matchMatrix.Length;
startRange = template.Substring(start, end - start);
endRange = last.Substring(start, end - start);
}
}
}

Categories

Resources