6
\$\begingroup\$

I am looking to simplify my main() function. The nested if statements contain four lines of the same code, and I'm unsure of how to refactor and simplify it. I was attempting to use booleans, which seemed like a safe bet, but the logic gets skewed in the process.

#include <bits/stdc++.h>#include <iostream>#include <string>int bits(unsigned long d) {    int l;    if (d <= 255) {        l = 8;    }    else if (d > 255 && d <= 65535) {        l = 16;    }    else if (d > 65535 && d <= 4294967295U) {        l = 32;    }    else {        std::cout << "32 bits (4294967295) or smaller." << std::endl;    }    std::cout << "Bits..................... " << l << std::endl;    return l;}std::string convertToBinary(unsigned long decimal) {    int l = bits(decimal);    int i, r;    std::string str;    //  creates array    for (i = 0; i < l; i++) {        r = decimal % 2;        decimal /= 2;        str += std::to_string(r);    }    //  reverses array for binary value    reverse(str.begin(), str.end());    std::cout << "Binary Value............. " << str << std::endl;    return str;}int main(void) {    std::string input;    std::cout << "------------ Input -----------" << std::endl;    std::cin >> input;    std::cout << "Input: " << input << std::endl;    std::cout << "Length: " << input.length() << std::endl;    int i, count = 0, j = 0;    int length = input.length();    int ascii;    unsigned long nums;    int numbers[33];    std::string binaries;    std::string chars;    for (i = 0; i < length; i++) {        //  if next input is digit        if (isdigit(input[i])) {            numbers[j] = input[i];            // place digit in next decimal            count = (numbers[j] - '0') + count * 10;            //  if next input is char            if (i == length - 1) {                if (isdigit(input[length - 1])) {                    nums = count;                    std::cout << "------------ " << nums << " ------------" << std::endl;                    binaries += convertToBinary(nums) + ' ';                    count = 0;                }            }            //  next number            j++;        }        //  if next input is char        else {            chars[i] = input[i];            ascii = (int) chars[i];            //  if next input is digit            if (count != 0) {                nums = count;                std::cout << "------------ " << nums << " ------------" << std::endl;                binaries += convertToBinary(nums) + ' ';                count = 0;            }            //  != end of letters            std::cout << "------------ " << chars[i] << " ------------" << std::endl;            std::cout << "ASCII.................... " << ascii << std::endl;            binaries += convertToBinary(ascii) + ' ';        }    }    std::cout << "\n------- Binary Value of " << input << " -------" << std::endl;    std::cout << binaries << std::endl;    return 0;}

The program takes an input, then outputs the corresponding number of bits, binary value, and if necessary, an ASCII value. If the user inputs "c357g98", then the program should get ASCII and binary values of "c", then retrieve the binary value of "357" (rather than "3", "5", then "7" individually), so any adjacent digits will be treated as a single int instead of separate integers. Each time I attempt to refactor, the program loses its ability to store the adjacent digits together as a single int.

In the first attempt at refactoring, I tried this, which takes thecount andbinary parameters:

void getNumbers(int c, std::string b) {    std::cout << "------------ " << c << " ------------" << std::endl;    b += convertToBinary(c) + ' ';    c = 0;}

Since thecount is never returned, the input "c357g98" is treated as "c357g35798" and the numbers never reset.

So in my second attempt, I return thecount variable as an int:

int getNumbers(int c, std::string b) {    std::cout << "------------ " << c << " ------------" << std::endl;    b += convertToBinary(c) + ' ';    c = 0;    return c;}

And replace the four lines of code with a reference to the function as:

count = getNumbers(count, binaries);

This method converts each value correctly, but does not store the final binary values of the digits, only the characters (since the reference to the stringbinaries was moved and not returned).

I've also done away with thenums variable entirely.

I'm coming to terms with the idea that my entire program may need heavily refactored in order for the logic to be simplified, but I was hoping to start with the messymain() function for now.

200_success's user avatar
200_success
146k22 gold badges191 silver badges481 bronze badges
askedMar 14, 2019 at 21:17
J Pex's user avatar
\$\endgroup\$
4
  • \$\begingroup\$What have you attempted to do to refactor it? Assuming the four lines you are referring to are the ones beginning withnums = count;, why can't you put those four lines in a function that accepts references tonums andcount?\$\endgroup\$CommentedMar 14, 2019 at 21:25
  • \$\begingroup\$@Null I created a function that takescount andbinaries, then returns thecount. So the four lines are then replaced withcount = getNumbers(count, binaries); This method will skip the digits entirely, and when the program is due to print all final binary values at the end, only the characters are converted. In my second attempt, I changed the function tostring getNumbers and had thebinaries string be returned instead, and the four lines of code in the nestedifstatements were replaced withbinaries += getNumbers(count, binaries); This method does not reset the counter.\$\endgroup\$CommentedMar 14, 2019 at 21:46
  • \$\begingroup\$It's hard to follow that in a comment. Pleaseedit your question with the attempted refactor code.\$\endgroup\$CommentedMar 14, 2019 at 21:56
  • \$\begingroup\$@Null I apologize, I was unaware of the correct protocol for editing posts. I've edited the post now to include my attempts.\$\endgroup\$CommentedMar 14, 2019 at 22:13

3 Answers3

4
\$\begingroup\$

It's not only the main function that you should try to improve upon. Rather, when you build your supporting functions in a nice way, a simplified main function follows naturally.

First, as general comments, your program suffers from a bad separation of logic. Your functions are doing too much: they do processing, print output to the user, and return some value. The same problem is echoed in the main: input parsing is tightly knit together with printing and processing.

  • Don't include<bits/stdc++.h>, it's not portable. Also, seehere for more on why not do it.

  • Your program crashes on multiple inputs due to out-of-indexing issues, so avoid C-arrays likeint numbers[33]. Also, avoid magic numbers like 33 which makes the reader go "Huh, what 33?".

  • As explained in the other review(s), declare variables as late as possible because you write C++ and not C.

  • Standard functions and data structures will simplify your code massively. We can logically think about the following: read the input, split it into sensible parts, and process each part. That's it!

So in full, we could write your program also as follows:

#include <iostream>#include <string>#include <vector>#include <algorithm>#include <bitset>#include <iterator>constexpr unsigned long MAX_BITSIZE = 32;int bits(unsigned long d){    if (d > 65535) {        return 32;    }    else if (d > 255) {        return 16;    }    else {        return 8;    }}// Return a bit string corresponding to the input.std::string to_binary(unsigned long decimal){    std::bitset<MAX_BITSIZE> bs(decimal);    return bs.to_string().substr(MAX_BITSIZE - bits(decimal));}// Split the input string into parts, // e.g., "abc123e6g" is split into ("abc", "123", "e", "6", "g").std::vector<std::string> get_parts(const std::string& str){    std::vector<std::string> parts;    for (auto first = str.cbegin(); first != str.cend(); )    {        auto change = std::adjacent_find(first, str.cend(),            [](char a, char b) { return isdigit(a) != isdigit(b); });        if (change != str.cend())        {            ++change;        }        parts.emplace_back(std::string(first, change));        first = change;    }    return parts;}int main(){    // Reading input could be done a function.    // We also omit all checks, and just assume it is valid.    std::string input;    std::cout << "------------ Input -----------\n";    std::cin >> input;    std::cout << "Input: " << input << "\n";    std::cout << "Length: " << input.length() << "\n";    const auto cont = get_parts(input);    std::vector<std::string> binary;    for (const auto& e : cont)    {        // Processing an integer        if (isdigit(e.front()))        {            const std::string b = to_binary(stoi(e));            std::cout << "------------ " << e << " ------------\n";            std::cout << "Bits..................... " << bits(stoi(e)) << "\n";            std::cout << "Binary value............. " << to_binary(stoi(e)) << "\n";            binary.push_back(b);        }        // Processing individual characters        else        {            for (const auto& ch : e)            {                const std::string b = to_binary(ch);                std::cout << "------------ " << ch << " ------------\n";                std::cout << "ASCII.................... " << int(ch) << "\n";                std::cout << "Bits..................... " << bits(ch) << "\n";                std::cout << "Binary value............. " << to_binary(ch) << "\n";                binary.push_back(b);            }        }    }    std::cout << "\n------- Binary value of " << input << " -------\n";    std::copy(binary.cbegin(), binary.cend(), std::ostream_iterator<std::string>(std::cout, " "));}

A few comments about the above program:

  • Luckily,std::bitset has methods for printing a binary string that we can use to massively simplify your "to-binary" conversion function. Notice that this function doesno printing; it's not its job. Remember:one function, one responsibility. Whoever uses this function decides when, how, and what to print (if anything).

  • The functionget_parts takes care of all that messy processing of your main function (which is buggy - but don't feel bad, it's hard to get it right when you go so low level). The magic is taken care ofstd::adjacent_find which is used by a suitable lambda function that checks if the two adjacent indices contain characters of "different type" (i.e., a digit and a non-digit). If we wanted to, we could also modify this slightly to further split "abc" to "a", "b", and "c".

  • The main function is quite pretty now (and very readable compared to your original): just get the parts and process each. Printing could be further divided into more functions.

answeredMar 16, 2019 at 13:32
Juho's user avatar
\$\endgroup\$
4
\$\begingroup\$

The first 3 lines inmain can be turned into a function:

std::string getInput(){    std::string input;    std::cout << "------------ Input -----------" << std::endl;    std::cin >> input;    return input;}int main()    std::string input = getInput();

There are several portions ofmain that could become functions.

Declare Variable when needed

In the functionconvertToBinary the integer variablesi andr are declared at the beginning of the function. They are only needed within thefor loop.

    for (int i = 0; i < l; i++) {        int r = decimal % 2;        decimal /= 2;        str += std::to_string(r);    }

This is also true in themain function fori and other variables.

The contents of thethen clause and theelse could both be moved into functions.

Does the innerif statement(if (i == length - 1)) need to be within thefor loop or could it be executed after thefor loop is done? You might want to think aboutloop invariants.

Reduce Complexity When You Can

In the functionbits, checking from the top down might make reading easier because the top limit isn't needed:

    if (d > 65535) {        l = 32;    } else if (d > 255) {        l = 16;    } else {        l = 8;    }
answeredMar 14, 2019 at 23:29
pacmaninbw's user avatar
\$\endgroup\$
4
\$\begingroup\$

The use of the termASCII is misleading. What's used here is actually theexecution character coding, which may or may not be ASCII. C++ is intentionally portable enough to work on systems regardless of the encoding they use for characters.

answeredMar 15, 2019 at 11:33
Toby Speight's user avatar
\$\endgroup\$

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.