I've been trying to refactor a C# console application that asks where a user would like to travel, records the location, repeats the question, and then either asks for another location or prints the locations the user has specified (ending the program).
Given the commented instructions in the code, I seem to have everything in place. However, in the ContinuePlanning() method it repeats a second time regardless of user response before continuing in the program and I'm having trouble figuring out why.
From what I can tell, the logic makes sense but I know I'm missing a simple step somewhere in the code. Can anyone lend some insight as to why this is occurring?
Program.cs
using System;
using System.Collections.Generic;
// Synopsis: Everyone loves a great vacation! This application helps you keep track of all the wonderful places on your bucket list.
namespace TravelPlanner
{
class Program
{
static void Main(string[] args)
{
// Display header
Console.WriteLine("");
Console.WriteLine("========================================");
Console.WriteLine(" TRAVEL PLANNER ");
Console.WriteLine("========================================");
Console.WriteLine("");
Console.WriteLine("Helping you keep track of the places you will travel. \r\n");
/*
3. PLAN ITINERARY
-------------------------
[ After completing steps #1 and #2, lets take the optimization even further...
Reviewing what you've accomplished, you will notice that each step now contains a single line
of code. We've compartmentalized each action into a method, excellent refactoring!
The while loop below contains these two actions. Looping over those actions is how we plan the
itinerary. So lets migrate the loop and the nested actions into the middle of the Plan() method.
The only thing left now is the creation of the list. Add one line that sets the value of
"locations" to the calling of that method. Planning the itinerary has now be accomplished with
one line within Main()!
The Main() method has become an outline of the application... after displaying the header, we Plan(),
then display what we've planned, and finally display a footer.
Uh, oh... don't forget the syntax problem!
] */
// Create List
List<string> locations = new List<string>();
// Repeat until all locations are added
bool planning = true;
while (planning)
{
/*
1. GET LOCATION
-------------------------
[ Migrate the following, except for the last line, into the Itinerary class' (Itinerary.cs)
GetLocation() method. Then change the last line so "location," within locations.Add()
calls that GetLocation() method. Refer to the lesson on static classes/methods to see
how you can call methods from other classes.
Also, make sure to fix the validation's logic errors!
] */
locations.Add(Itinerary.GetLocation());
/*
2. CONTINUE PLANNING
-------------------------
[ Migrate all of the following into the ContinuePlanning() method. Replace this with a
single line that sets the value of the "planning" variable to the returned value.
This validation also contains a logic error!
] */
planning = Itinerary.ContinuePlanning(planning);
}
/*
4. DISPLAY ITINERARY
-------------------------
[ The Itinerary class has allowed us to organize the code, containing all the methods (processes)
related to the planning of the itinerary: Plan(), GetLocation(), and ContinuePlanning().
Since the following code is used to display the itinerary, it should be there too.
Migrate the following into the Display() method. Then replace with a call to that method.
Of course you'll need an argument for the "locations" list to display.
Carefully read the output to spot a syntax error... we want to practice string interpolation!
] */
Itinerary.Display(locations);
// -------------------------
// Display footer
Console.WriteLine("");
Console.WriteLine("========================================");
Console.WriteLine("Safe travels on your future trips!");
Console.WriteLine("");
Console.ReadKey();
}
// End class
}
}
Itinerary.cs
using System;
using System.Collections.Generic;
// Synopsis: Collection of methods used in the planning and displaying of travel itinerary locations.
namespace TravelPlanner
{
public class Itinerary
{
// Plan itinerary
public static List<string> Plan()
{
// Create list for locations
List<string> locations = new List<string>();
return locations;
}
// Request location from user
public static string GetLocation()
{
// Initial request
Console.WriteLine("");
Console.WriteLine("--------------------");
Console.Write("Where would you like to travel? ");
string location = Console.ReadLine();
// Validate user input
while (String.IsNullOrWhiteSpace(location))
{
// Reminder not to leave blank
Console.WriteLine("Please do not leave this blank! \r\n");
// Ask again where they would like to go
Console.Write("Where would you like to travel? ");
// String for user input
string newlocation = Console.ReadLine();
}
return location;
}
// Question - another location?
public static bool ContinuePlanning(bool planning)
{
// Another location?
Console.Write("Another location? (yes/no) ");
string response = Console.ReadLine();
// Validate user input
if (response.ToLower() != "yes" || response.ToLower() != "no")
{
// Error message
Console.WriteLine("Please only enter yes or no.");
// Re-ask question
Console.Write("Another location? (yes/no) ");
response = Console.ReadLine();
}
// Continue planning?
if (response.ToLower() == "no")
{
planning = false;
}
return planning;
}
// Display itinerary locations
public static void Display(List<string> locations)
{
//Output the number of locations the user will visit
Console.WriteLine("");
Console.WriteLine("--------------------");
Console.WriteLine($" You will take {locations.Count} trip(s).");
//Output each location the user will visit
for (int i = 0; i < locations.Count; i++)
{
Console.WriteLine($" You will visit {locations[i]}.");
}
}
// End class
}
}
Thanks in advance!
"From what I can tell, the logic makes sense"
That's probably because you're reading it as someone might say it in english, but not as a logical code statement.
In English we would say, "If they didn't answer with "yes" or "no", then ask them again."
In code we phrase it differently: "If the answer wasn't "yes" and the answer wasn't "no", then ask them again"
Here's why:
The conditional logical OR operator (||) will return true if either operand is true (and evaluation will stop at the first true result):
false || false == false
false || true == true
true || false == true
true || true == true
This differs from the conditional logical AND operator (&&) which will return true only if both operands are true.
false && false == false
false && true == false
true && false == false
true && true == true
Now let's take a look at your if condition in ContinuePlanning:
if (response.ToLower() != "yes" || response.ToLower() != "no")
Now, if someone enters "yes", the first part is false but the second part is true, so the condition will evaluate to true. Similarly, if they enter "no" (or any other input), the first part is true, so the condition will evaluate to true
Instead, we want to test that both the first part AND the second part are true, so we should use the && operator:
if (response.ToLower() != "yes" && response.ToLower() != "no")
Now if they enter "yes" or "no", only of those operands will be true, so the condition will evaluate to false. If they enter any other input, then both the conditions are true, which is what we're looking for.
Another problem is that this method takes in a bool argument, modifies it to false if the user enters "no", and then returns it. The problem with this is that if the variable starts out to be false, then we return false no matter what the user enters. Also, this method doesn't need any arguments from the caller. We should just declare the variable locally instead.
We also only check once if they enter "yes" or "no", but we should do it in a loop just in case they're really clumsy typists. :)
// Question - another location?
public static bool ContinuePlanning()
{
// Start out assuming we will contiune planning
bool continuePlanning = true;
// Another location?
Console.Write("Another location? (yes/no) ");
// Get the user reponse and make it lower case
string response = Console.ReadLine().ToLower();
// Validate user input
while (response != "yes" && response != "no")
{
// Error message
Console.WriteLine("Please only enter yes or no.");
// Re-ask question
Console.Write("Another location? (yes/no) ");
response = Console.ReadLine();
}
// If the user doesn't want to continue, set our variable
if (response == "no") continuePlanning = false;
return continuePlanning;
}
Related
Im trying to do a check using a while loop I want the loop to only ask the user to re-enter their value if one of three conditions are triggered. That is if the response is blank, nor a "Y" or "N". I did this by using the ! operator. I've noticed that even if the response is the correct choice the while loop still asks to re-enter a value. I also noticed that when I remove the ! operator from in front of the second condition and the user enters the right response the code after the loop block works but when i add the ! operator back to the condition the loop works even if the response is correct.
PromptMessage("If you are using a different download path for your mods enter (Y)es. Or if you want to exit out the" +
" program enter (N)o!", ConsoleColor.Green);
string CustomPath = Console.ReadLine();
CustomPath.ToUpper();
Console.WriteLine(CustomPath);
while (!CustomPath.Contains("Y") || !CustomPath.Contains("N") || String.IsNullOrEmpty(CustomPath))
{
AlertMessage("Please enter either Y to continue or N to exit");
CustomPath = Console.ReadLine();
CustomPath.ToUpper();
}
You have a couple things wrong here. First, Strings are immutable in C#, so doing this:
string foo = "some string";
foo.ToUpper();
Means that foo is still equal to "some string" after running it. You need to assign the value to a variable (it can even be the same variable). Like this:
string foo = "some string";
foo = foo.ToUpper();
//foo = "SOME STRING"
The next problem is your loop and logic. I think a much easier way to do this is using a do/while loop and checking the "validity" of the input in the while condition. The do/while loop means you will always "do" something once before checking the while condition. You always want to ask for input one time so it makes more sense to use this loop:
public static void Main()
{
//defined in outer scope
string customPath = string.Empty;
do
{
Console.WriteLine("If you are using a different download path for your mods enter (Y)es. Or if you want to exit out the program enter (N)o!");
//Calling ToUpper() before assigning the value to customPath
customPath = Console.ReadLine().ToUpper();
}
while (customPath != "N" && customPath != "Y");
}
I made a fiddle here
I think you may have your logic reversed. Do you mean to have the conditions for the while as follows?
while (!CustomPath.Contains("Y") && !CustomPath.Contains("N") && !String.IsNullOrEmpty(CustomPath))
This would be logically equivalent to the following statement (but this one is much less readable IMO)
while (!(CustomPath.Contains("Y") || CustomPath.Contains("N") || String.IsNullOrEmpty(CustomPath))
This way the loop will continue while the entered path does not have contain "Y", "N", or an empty path.
Also note that as #maccettura pointed out you will want to change to using CustomPath = CustomPath.ToUpper();
Change into this
while ((!CustomPath.Contains("Y") && !CustomPath.Contains("N")) || String.IsNullOrEmpty(CustomPath))
I realize that your code will always return true.
For example, you input 'Y'
!CustomPath.Contains("Y") => false
!CustomPath.Contains("N") => true
Since you use ||, it will always return true.
int VillainId = -1;
Console.Write("Enter VillainId: ");
while (!int.TryParse(Console.ReadLine(), out VillainId))
{
Console.WriteLine("You need to enter a valid Villain Id!");
Console.Write("Enter VillainId: ");
}
Can someone tell me how the code inside the while(**this code here**){//rest of the code} works. I understand if it was inside the {} but its in the condition and its looping until it successfully parses a number. How does that work ?
int.TryParse returns true if it successfully parses the string it's getting from Console.ReadLine(). The ! in front of it means to reverse the boolean value returned by int.TryParse, so the while executes the code in the parens, and if int.TryParse returns false, the false gets reversed to a true and the while executes again -- and again and again, until int.TryParse returns true. "The while executes" means the code in the parens executes first, and then if the result of that is true, the body of the while executes as well.
Here's another way to write the same code. It's a little less compact, but might be easier to follow:
int VillainId = -1;
bool parseOK = false;
do
{
Console.Write("Enter VillainId: ");
parseOK = int.TryParse(Console.ReadLine(), out VillainId);
if (!parseOK)
{
Console.WriteLine("You need to enter a valid Villain Id!");
}
} while (! parseOK);
int.TryParse() returns true if the conversion was successful and the ! (logical negation operator) inverts the boolean value in his right side (!true is equals to false).
The condition in while is evaluated every loop, so, every invalid input the block code in while() will be executed.
The flow is, basically:
Console.Write("Enter VillainId: ");
// asks to user input
while (!int.TryParse(Console.ReadLine(), out VillainId))
// while the conversion is not successfull
{
Console.WriteLine("You need to enter a valid Villain Id!");
Console.Write("Enter VillainId: ");
// asks for user to input valid data
}
I'm fairly new to c#, and writing a simple console app as practice. I want the application to ask a question, and only progress to the next piece of code when the user input equals 'y' or 'n'. Here's what I have so far.
static void Main(string[] args)
{
string userInput;
do
{
Console.WriteLine("Type something: ");
userInput = Console.ReadLine();
} while (string.IsNullOrEmpty(userInput));
Console.WriteLine("You typed " + userInput);
Console.ReadLine();
string wantCount;
do
{
Console.WriteLine("Do you want me to count the characters present? Yes (y) or No (n): ");
wantCount = Console.ReadLine();
string wantCountLower = wantCount.ToLower();
} while ((wantCountLower != 'y') || (wantCountLower != 'n'));
}
I'm having trouble from string wantCount; onwards. What I want to do is ask the user if they want to count the characters in their string, and loop that question until either 'y' or 'n' (without quotes) is entered.
Note that I also want to cater for upper/lower case being entered, so I image I want to convert the wantCount string to lower - I know that how I currently have this will not work as I'm setting string wantCountLower inside the loop, so I cant then reference outside the loop in the while clause.
Can you help me understand how I can go about achieving this logic?
You could move the input check to inside the loop and utilise a break to exit. Note that the logic you've used will always evaluate to true so I've inverted the condition as well as changed your char comparison to a string.
string wantCount;
do
{
Console.WriteLine("Do you want me to count the characters present? Yes (y) or No (n): ");
wantCount = Console.ReadLine();
var wantCountLower = wantCount?.ToLower();
if ((wantCountLower == "y") || (wantCountLower == "n"))
break;
} while (true);
Also note the null-conditional operator (?.) before ToLower(). This will ensure that a NullReferenceException doesn't get thrown if nothing is entered.
If you want to compare a character, then their is not need for ReadLine you can use ReadKey for that, if your condition is this :while ((wantCountLower != 'y') || (wantCountLower != 'n')); your loop will be an infinite one, so you can use && instead for || here or it will be while(wantCount!= 'n') so that it will loops until you press n
char charYesOrNo;
do
{
charYesOrNo = Console.ReadKey().KeyChar;
// do your stuff here
}while(char.ToLower(charYesOrNo) != 'n');
I am currently working on a program and I am finalising it by going over with error handling. I have several cases which look like:
int stockbankInput = Convert.ToInt32(Console.ReadLine());
Here, the user must enter either 1, 2, 3. I have tried to use an if statement to catch the error if anybody inputs a blankspace/string/character or a number that is not 1,2 or 3 but it doesn't work in the same sense as a string input. Below is what I have tried:
if(stockbankInput == null)
{
Console.WriteLine("Error: Please enter either 1, 2 or 3");
stockbankInput = 0;
goto menuRestartLine;
}
However, you cannot link 'null' with an integer input, only a string. Can anybody help with this please?
Use the Int32 TryParse method:
int input;
var successful = Int32.TryParse(Console.ReadLine(), out input);
if (!successful)
// do something else
else
return input;
You're checking if an int is null, which will always return false because an int cannot be null.
You can use 'int?' (Nullable int) but Convert.ToInt32 will not return null. If the value of the int cannot be resolved it will resolve to the default value of zero. You can either check if the returned int is zero or do some further checking of the returned string:
int input = 0;
string errorMessage = "Error: Please enter either 1, 2 or 3";
while(true)
{
try
{
input = Convert.ToInt32(Console.ReadLine());
if (input == 0 || input > 3)
{
Console.WriteLine(errorMessage);
}
else
{
break;
}
}
catch(FormatException)
{
Console.WriteLine(errorMessage);
}
}
With this you your returned value "int input" will either be 0 or the number you entered and FormatExceptions caused by the string to convert containing symbols other than the digits 0-9 will be caught in the try/catch statement.
give this sample program a try:
static void Main(string[] args)
{
int stockbankInput = 0;
bool firstTry = true;
while(stockbankInput < 1 | stockbankInput > 3)
{
if(!firstTry)
Console.WriteLine("Error: Please enter either 1, 2 or 3");
firstTry = false;
Int32.TryParse(Console.ReadLine(), out stockbankInput);
}
}
First of all, don't use goto statements. They are considered bad practice, and it's like a blinding red light when reading your question - that's all I can focus on.
As per your question, an int or Int32 cannot be null. So you can't compare it to null. Give it a default value, and then check that.
This is a scenario where you don't need to check for an error, but just need to validate input. Use TryParse, which will set your out parameter if the parse is successful, or else set it to 0.
Next, you want to loop until you are given good input. An if statement is executed once, a loop will guarantee that when you leave it, your input will be valid.
Lastly, the firstTry is just a nice way to let the user know, after their first try, that they screwed up.
I have a program that needs to compare any given string with a predefined string and determine if an insertion error, deletion error, transposition or substitution error was made.
For example, if the word dog was presented to the user and the user submits dogs or doge, it should notify the user that an insertion error has been made.
How do I go about this?
You probably need to write a method for each of the individual error types to see if it's an error, like:
bool IsInsertionError(string expected, string actual) {
// Maybe look at all of the chars in expected, to see if all of them
// are there in actual, in the correct order, but there's an additional one
}
bool IsDeletionError(string expected, string actual) {
// Do the reverse of IsInsertionError - see if all the letters
// of actual are in expected, in the correct order,
// but there's an additional one
}
bool IsTransposition(string expected, string actual) {
// This one might be a little tricker - maybe loop through all the chars,
// and if expected[i] != actual[i], check to see if
// expected[i+1] = actual[i] and actual[i-1]=expected[i]
// or something like that
}
Once you build out all the individual rules, and you first check for regular equality, fire each of them off one at a time.
You've got to just break problems like this down into small components, then once you have a bunch of easy problems, solve them one at a time.
Off the top of my head but I think this should get you started:
Insertion and Deletion should be pretty simple; just check the lengths of the strings.
if(originalString.Length > newString.Length)
{
//deletion
}
else if(originalString.Length < newString.Length)
{
//insertion
}
To detect transposition, check if the lengths match and if so, you could create two List<char> from the two strings. Then check if they match using the expression below
bool isTransposed = originalList.OrderBy(x => x).SequenceEquals(newList.OrderBy(y => y));
To detect substitution, you could use the Hamming Distance and check if it's greater than 0.
I would suggest you to create a function which will take a parameter as the input sting. The function would look more or less like this. Use the function wherever you want then.
private void CheckString(string userString)
{
string predefinedString = "dog";
if (userString == predefinedString)
{
// write your logic here
}
else
{
MessageBox.Show("Incorrect word"); // notify the users about incorrect word over here
}
}