Help comparing strings to make a standard output

I have a project where I am making a conversion table, part of the requirement is to have a standard output for unit type regardless of the input. I am trying to write a function to perform this task, and after researching some of the forums, came up with the following bit of code. I tried at first doing straight comparisons for each 'if' statement (i.e. if (dim == 'foot' || dim == 'feet'...) but that didn't work at all (gave an error about operands using std::string). This code works, except that the output is always "ft", even if "inches" is the input. I think it has to do with how I wrote the 'if-else if' statements, but not sure. I am fairly new to coding and can't think of how to figure this one out.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  #include <iostream>  	//Calls up input/output library
#include <cmath>	//Calls up math library
#include <iomanip>	//Calls up input/output manipulation library
#include <string>	//Calls up string library


using namespace std;	//Uses standard syntax
string dimswitch(string);
int main()		//Begins application
{
	string dim, unitStart, unitEnd;
	double valueStart, valueEnd, increment; 
	int accurStart, accurEnd;
	
	cout << "This program will provide a table of conversions from one unit type to another unit type." << endl;
	cout << "Please enter the dimension type to convert (length, weight, volume, or time): ";
	cin >> dim;
	dim = dimswitch(dim);
	

	cout << "dimension is " << dim;
	
	return 0;	//Ends application

}

string dimswitch(string dim)
{
	if (dim.compare("feet") || dim.compare("feets") || dim.compare("foot") || dim.compare("ft") || dim.compare("fts") || dim.compare("f"))
	dim = "ft";
	else if (dim.compare("Feet") || dim.compare("Feets") || dim.compare("Foot") || dim.compare("Ft") || dim.compare("Fts") || dim.compare("F"))
	dim = "ft";
	else if (dim.compare("FEET") || dim.compare("FEETS") || dim.compare("FOOT") || dim.compare("FT") || dim.compare("FTS") || dim.compare("F"))
	dim = "ft";
	else if (dim.compare("meter") || dim.compare("meters") || dim.compare("m") || dim.compare("Meters") || dim.compare("Meters") || dim.compare("M") || dim.compare("METER") || dim.compare("METERS"))
	dim = "m";
	else if (dim.compare("inch") || dim.compare("inches") || dim.compare("in") || dim.compare("i"))
	dim = "in";
	else if (dim.compare("Inch") || dim.compare("Inches") || dim.compare("In") || dim.compare("I"))
	dim = "in";
	else if (dim.compare("INCH") || dim.compare("INCHES") || dim.compare("IN"))
	dim = "in";
	else
	dim = "Bad Input";
	
	return dim;
}
I deleted the other parts of the program that I wrote and commented out, hence the extra variables declared in the beginning. Tried to make this as small as possible.
you can eliminate half the junk with a to-upper or to-lower on the data before checking it, then if it is say iNcHes it will still match (for to-lower) inches ..

did you know that string.compare is zero when its true? (so its false return if the strings are equal).


try
if(dim == "Feet")
Last edited on
Nope, did not know that. We have only learned some real basic stuff and the string.compare is something I grabbed from a forum post, so wasn't sure how it would work. I haven't learned the to-upper or to-lower yet. Is there a reference post you can point me to? I would like to shorten this program as much as possible. Thanks in advance for the help.
fatmurphy wrote:
if (dim == 'foot' || dim == 'feet'


fatmurphy wrote:
but that didn't work at all (gave an error about operands using std::string).


Try :

if (dim == "foot" || dim == "feet") // double quotes for strings

With the function, I would use an extra variable, say result.

In the function declaration, it is much clearer if you provide a variable name, and pass by reference:

std::string dimswitch(std::string& DimArg);

In the function definition, make the parameter const , it means the function can't alter it's value - that's a good thing:

1
2
3
4
5
6
7
8
std::string dimswitch(const std::string& DimArg)
{ 

double result{0.0};  // use brace initialization - it prevents narrowing

// .....

}


One could pass the result variable to the function by reference, assign to it in the function - then it will be available in the scope that called the function - main in this case.

1
2
3
4
5
6
7
8
void dimswitch(const std::string& DimArg,
                 std::string& result
                 )
{ 
// ...
// assign to result variable
// ....
}


Another methodology that maybe too advanced for this assignment (meaning that your teacher will notice straight away that you had help ) is to put all legal values into a std::vector, then use the std::find algorithm to see if the input matches any of them.

One thing you can do, is to do the conversion in two parts: always convert any length dimension to metres, then convert that to the target unit. That way you only need much less the number of conversion factors. Specifically 2n rather than n2

Some of your units are incorrect English : Feets ; by implication Fts, Feet is the correct plural. It another example of bizarre exceptions in English, as meters is OK . Not included is metres, the French invented it, so one should include their spelling.

Another thing, it is a good idea to get away from using namespace std; It will bite you one day. Believe me, it's easiest to put std:: before each std thing, that is what all the experienced people do. Google it, there are zillions of comments about it on the web.

Good Luck !!

Edit:

What do int accurStart, accurEnd; mean ? Something to do with accuracy? I shouldn't have to guess :+)

Use meaningful names, don't abbreviate. Put one variable per line, wait until you have a sensible value, then do the declaration and assignment in one statement


Last edited on
Thanks for the input TheIdeasMan, I'm going to have to try and digest a lot of what you wrote, I don't understand the syntax yet, for instance, "std::string&" (or any such code that uses ::) is completely foreign to me, we haven't been taught to use that yet.

I figured out the issue with my original code, my input and output variable in the function were the same name, so that caused some issues. I went back to just using a straight comparison:
1
2
3
if (unit == "feet" || unit == "feets" || unit == "foot" || unit == "ft" || unit == "fts" || unit == "f")
	dim = "ft";
return dim;

as well as adjusted variable names to make more sense, and put this function call where it belongs... the result is I got it to work with what I know, I'm not very good with functions yet, they really confuse me for some reason, but I got this one to work, so the rest of the program should be a bit easier.

As far as learning from people like you things which she hasn't taught yet, she is all for it, actually she gives extra credit for adding bits that are more advanced than what we have learned in class, but for now I will stick with what I know, and if I have time before it is due I may revisit and try to make the code a bit more streamlined.

The bad english part was on purpose, giving as many possible options as I could think of for inputs from the user.

Thanks again all for your help!
Last edited on
.... I don't understand the syntax yet, for instance, "std::string&" (or any such code that uses ::) is completely foreign to me, we haven't been taught to use that yet.


That is all to do with namespaces. The idea of them is to help naming conflicts. In a larger project, we don't want 2 coders to use the same variable unit, so they each put their code in it's own namespace: fatmurphy::unit and TheIdeasMan::unit. The problem with using namespace std; is that there exists for example std::distance - we have a problem, Houston :+). There are dozens of example like this, so put std::distance if you want the distance between two item indices in a container, and without std:: to refer to something else. Personally, I use PascalCase for my variable and function names, so they will never conflict with the STL.

As far as learning from people like you things which she hasn't taught yet, she is all for it, actually she gives extra credit for adding bits that are more advanced than what we have learned in class, ...


Well, you are very lucky there IMO, so many teachers don't do that. Usually they want you to write your own sort function, not use the one from the library, in which case you won't learn anything. So that is why I suggest you won't get away with using std::find. You could try to write own version that does the same thing. I guess the main thing is that you need to be able to explain why you used a particular syntax or feature, for example why you now put std:: before every std thing, and your variables/ functions/ classes are all in their own namespace, and why you use const or constexpr. If I was your teacher, I would be over the moon if you had these concepts down already.

The reference (ampersand in const std::string& DimArg) is conceptually like passing the address (a pointer) of the variable, instead of passing the whole thing by value. A std::string could potentially have thousands or even millions of characters, so we pass it's address in memory (64 bit) instead of copying KB or MB of data. We use this passing by reference for any of the STL containers or classes, or any class that you have created.

The use of const (called const correctness) is a very useful thing, it actually helps one code properly. If one tries to change something which is const the compiler will produce an error. It's not just for function arguments, one can use it for ordinary variables, and class functions (it doesn't change any class member values). There is also constexpr :

1
2
constexpr double PI = acos(-1.0);
constexpr double PIOverTwo = PI / 2.0;



constexpr is stronger than const , it is set in stone at compile time, where as const is able to be cast away with const_cast, the latter is inadvisable though.

You could constexpr for you conversion values between the units.

The other concept to think about: At this stage you will be writing programs that use a very small number of variables / data. In the real world, programs deal with millions or even billions of pieces of data. Just keep that in the back of your mind for now :+)

Good Luck !!
Last edited on
I got through the issue with my function not working properly, now trying to clean it up a bit and make it a bit smaller. I'm trying to use 'tolower' as previously suggested, but I keep getting an argument error with the 'transform' function. My code for the line to transform my cin to all lower is

transform(unitStart.begin(), unitStart.end(), unitStart.begin(), tolower);
The error I get is "40 79 [Error] no matching function for call to 'transform(std::basic_string<char>::iterator, std::basic_string<char>::iterator, std::basic_string<char>::iterator, <unresolved overloaded function type>)'

I'm not sure what that means, so I don't know how to debug the line.
cout << "Please enter the dimension type to convert (length, weight, volume, or time): ";

lesser chances of 'bad' input if you ask users to choose numbers instead of typing in text: so offer a menu like "choose 1 for feet, 2 for meter etc" ... and then a simple switch-case
I use a switch case later to select the appropriate conversion factors. Part of the requirements were to enter a string type for that particular input, as well as for the units later in the program.

I've tested the program and it works, but now I need to clean it up and make it a little more streamlined. I am now working on searching input strings for the first character and last two characters to determine what the input is, for instance if "seconds" is entered and the search finds "s" is the first character, and the last character is "s", it then goes to the second to last character, looking for "d". If all those are true, the only possible unit could be "seconds" and it then standardizes the unit to "s". I think I know how to do it, just don't know the most efficient way. Since I can't get the 'transform' and 'tolower' functions working, I'm going to use what I know thus far.
Hi,

For std::transform, have a look at the example here:

http://en.cppreference.com/w/cpp/algorithm/transform

There is also std::for_each :
http://en.cppreference.com/w/cpp/algorithm/for_each

The std::for_each explicitly says it does things in order, std::transform does not guarantee it.

But both of those might mean you are getting too tricky by far, can you explain or even recognize what a lambda function is? Or do you really know what iterators and templates are?

It might be more appropriate to write your own simple function that has a for loop that calls the tolower function for each char in the string. Indeed it's not that hard to write your own tolower function as well. That should earn super brownie points from your teacher :+D. One can write a tolower function that works using the ASCII table. Realize the a char is just a small int, so one can do math with them. Have a look at the ASCII table, figure out what you have to do to a char to convert it to a different case. To apply the tolower function to the std::string, realize the sting is like an array: one can refer to an individual char by it's position with the [ ] operator. The size of the string can be found with it's size function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
std::string MyString = "fatmurphy";
std::size_t Size = MyString.size(); // std::size_t is the size type used throughout the Standard Template Library
                                                      // it is usually the largest unsigned type the system has. If one uses int instead, there
                                                      // should be warnings about implicit casting from unsigned to signed type - dangerous !
char TheFirstChar = MyString[0];
 // A C style for loop - we want to avoid doing this if we can

for (std::size_t item = 0; item < Size; ++item) {
// do something with MyString[item] which is a char
}

// Something a bit better, it avoids having to create variables beforehand, call the the function instead

for (std::size_t item = 0; item < MyString.size(); ++item) {
// do something with MyString[item] which is a char
}

// a range based for loop, when we want to iterate through everything, no need for any sizes

for (const auto item ; MyString ) {// item is automatically deduced to char, but can be explicitly stated
   // do something with item, no need to refer to object like it was an array
   //remove the const if changing the value of item
}

// if the container holds large items, pass by reference and by const if not changing anything
for (const auto& BigItem : SomeContainer) {
   // do something with BigItem
}

// The best of them all, and the most fancy and advanced - uses a forward reference
// it handles lvalues, rvlaues, non-const and const, has perfect forwarding, auto type deduction

for (auto&& Item : SomeContainer) {
   // do something with Item
} 


There you go, I given a whole lot of stuff that is probably way over your head. I do recommend you write those functions your self though.

Good Luck !!
Topic archived. No new replies allowed.