53
while (temp->left->oper == '+' || 
       temp->left->oper == '-' || 
       temp->left->oper == '*' || 
       temp->left->oper == '/' || 
       temp->right->oper == '+' || 
       temp->right->oper == '-' || 
       temp->right->oper == '*' || 
       temp->right->oper == '/')
{
    // do something
}

For clarity: temp is a pointer that points to following node structure:

struct node
{
    int num;
    char oper;
    node* left;
    node* right;
};
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • 1
    Without knowing about the dependencies between `temp->left` and `temp->right` you cannot optimize within all equal operators. Optically you could use regular expressions, but internally, it's probably much the same or even less efficient. – U. Windl Jul 24 '19 at 00:53
  • 3
    I'd be interested to know why you think you have this problem. It smacks of runtime interpretation of an expression tree, and if so there are much better ways to do it – user207421 Jul 24 '19 at 09:59

11 Answers11

60

Sure, you could just use a string of valid operators and search it.

#include <cstring>

// : :

const char* ops = "+-*/";
while(strchr(ops, temp->left->oper) || strchr(ops, temp->right->oper))
{
     // do something
}

If you are concerned about performance, then maybe table lookups:

#include <climits>

// : :

// Start with a table initialized to all zeroes.
char is_op[1 << CHAR_BIT] = {0};

// Build the table any way you please.  This way using a string is handy.
const char* ops = "+-*/";
for (const char* op = ops; *op; op++) is_op[*op] = 1;

// Then tests require no searching
while(is_op[temp->left->oper] || is_op[temp->right->oper])
{
     // do something
}
paddy
  • 60,864
  • 6
  • 61
  • 103
  • 15
    Have to be slightly careful with `strchr` because (it's a little known fact) that this will also be true if either `temp->left->oper` or `temp->right->oper` is equal to `'\0'`. But in practice this is probably a good solution. – john Jul 23 '19 at 05:52
  • Yeah, I've exploited that feature in the past. In any case, I've added an alternative with a lookup table... for speed :) – paddy Jul 23 '19 at 05:54
  • 3
    You probably want to extract the implementation into a separate function. – L. F. Jul 23 '19 at 10:04
  • 10
    Is there a reason to use `strchr` over an `std::string` with `.find()` here? – scohe001 Jul 23 '19 at 14:40
  • 2
    Yes, the OP's goal was to shorten the loop condition. Using `ops.find(temp->left->oper) != std::string::npos` was not as short as using `strchr`. But of course, as pointed out in the comments, the behavior of `strchr` is different in the case of searching for `\0` so using it might also be considered a latent bug or an actual bug, depending on the inputs. – paddy Jul 24 '19 at 01:29
  • 4
    @paddy I don't think he was looking for a golfed answer! just one that avoids the long chain of `||` statements. – Baldrickk Jul 24 '19 at 12:37
  • 1
    Beware of negative vanilla `char`. – Deduplicator Jul 24 '19 at 21:28
  • 1
    Better to use `is_op[(unsigned char) temp->left->oper]` than `is_op[temp->left->oper]` to prevent UB, else might as well use `char is_op[CHAR_MAX+1u] = {0};` – chux - Reinstate Monica Jul 25 '19 at 02:17
  • 1
    There are no benefits of using C style code in C++ in this scenario. +1 @scohe001 https://stackoverflow.com/a/63210165/10063119 –  Aug 01 '20 at 21:36
38

Yes, indeed you can!

Store the valid-characters to a std::array or even a plain array and apply the standard algorithm std::any_of to it for checking the condition.

#include <array>     // std::array
#include <algorithm> // std::any_of

static constexpr std::array<char, 4> options{ '+', '-', '*', '/' };
const auto tester = [temp](const char c) { return temp->left->oper == c || temp->right->oper == c; };
const bool isValid = std::any_of(options.cbegin(), options.cend(), tester);

while(isValid) // now the while-loop is simplified to
{
    // do something
}

This can be more cleaned by packing into a function, which accepts the node object to be checked.

#include <array>     // std::array
#include <algorithm> // std::any_of

bool isValid(const node *const temp) /* noexcept */
{
   static constexpr std::array<char, 4> options{ '+', '-', '*', '/' };
   const auto tester = [temp](const char c) { return temp->left->oper == c || temp->right->oper == c; };
   return std::any_of(options.cbegin(), options.cend(), tester);
}

which can be called in the while-loop

while (isValid(temp)) // pass the `node*` to be checked
{
    // do something
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Array is not required when string literals can do the job, `any_of` isn't required when `std::string` (or `std::string_view`) have `.find()` or `.find_first_of()`. // Shouldn't `isValid` also be a lambda so that you can keep it right above the while loop, not far away from the use. https://stackoverflow.com/a/63210165/10063119 // Also, `const node& temp` uses `.` access, not `->` access. You meant to pass `const node* temp` probably. –  Aug 02 '20 at 13:04
  • @ankii, Of course, there are other ways. Your sln, looks much compact and promising. It is no necessary that, `isValid` to be a lambda unless it has to be used only once in entire code base and does not become almost not an inline. BTW thanks for the pointing out the typo. I have corrected it. – JeJo Aug 02 '20 at 16:08
31

Create a sub function,

bool is_arithmetic_char(char)
{
// Your implementation or one proposed in another answers.
}

and then:

while (is_arithmetic_char(temp->left->oper)
    || is_arithmetic_char(temp->right->oper))
{
    // do something
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    As a note, such a function could also be added to `node`, or part of a class that inherits from `node` without adding any new data members, without breaking its standard-layoutness. – Justin Time - Reinstate Monica Jul 23 '19 at 18:22
  • 2
    I'm never convinced by that sort of "micro-refactorization." All it does is move any bugs in the code from where you can see them *in the context they are used in,* to some place where you can't. Of course if the same test occurs in other places in the app, that is a good reason for factoring it out. – alephzero Jul 23 '19 at 23:30
  • 2
    @alephzero I would prefer these kind of sub function in this case as (1) they abstract a possibly changing definition if you want to add operators later (2) they provide a small testable interface (3) they can be documented in the header file. – WorldSEnder Jul 24 '19 at 01:32
  • 6
    @alephzero: Watch the talk _The deep synergy between testability and good design_, if you haven’t. It may give you another perspective. – displayName Jul 24 '19 at 03:37
  • @alephzero: If it is only the only place where it is used (twice) and you don't want to expose it, you might still create lambda. – Jarod42 Jul 24 '19 at 16:18
  • 1
    @alephzero An answer with lambda https://stackoverflow.com/a/63210165/10063119 –  Aug 01 '20 at 21:40
14

C-style:

int cont = 1;
while(cont)
    switch(temp->left->oper) {
    case '+':
    case '-':
    ...
    case '/':
        // Do something
        break;
    default:
        cont = 0;
    }

You might need to enclose // Do something with curly braces if you're going to declare variables.

MCCCS
  • 1,002
  • 3
  • 20
  • 44
  • 8
    This isn't just "C-style". There are very good reasons to prefer doing this for this exact situation, as the compiler is able to construct a [jump table](https://en.wikipedia.org/wiki/Branch_table) much more efficiently than a mess of (theoretically) unrelated `if` checks. Even if it can't do that, "I'm comparing a mess of different constants to the same variable" is good information to give the compiler that can help it optimize much better. – T.E.D. Jul 23 '19 at 22:30
  • 3
    It would be even better to change the condition to `while (1)`; change the first `break` to `continue`; change the `default` case to `break`; and add another break after the `switch`. I've used this pattern many times in interpreters. – user207421 Jul 24 '19 at 08:24
  • @T.E.D. I'm skeptical of your premise. A switch case with a bunch of cascading cases isn't any different than if statements with disjunctive conditions (ORed together). Humans might not recognize that visually, but I would be surprised if compilers didn't support the two equally. – Alexander Jul 25 '19 at 05:43
  • 2
    @Alexander No, [GCC 9.1 added jump tables, bit tests and decision trees for switch cases](https://gcc.gnu.org/gcc-9/changes.html). [TCase1](https://godbolt.org/z/nxn-Pr) [TCase2](https://godbolt.org/z/b87f6N) – MCCCS Jul 25 '19 at 05:58
  • 1
    @MCCCS Interesting, thanks for providing that. This bugs me a lot to see. It seems to me that `switch` and `if`/`else if`/`else` are isomorphic (in the special case where all predicates are checking against a single "switched" value), and that empty cascading case bodies are the same as OR conditions on predicates. Why doesn't the compiler see that :| – Alexander Jul 25 '19 at 15:27
6

You can construct a string that contains the options and search for the character:

#include <string>

// ...

for (auto ops = "+-*/"s; ops.find(temp-> left->oper) != std::string::npos ||
                         ops.find(temp->right->oper) != std::string::npos;)
    /* ... */;

The "+-*/"s is a C++14 feature. Use std::string ops = "+-*/"; before C++14.

L. F.
  • 19,445
  • 8
  • 48
  • 82
5

Programming is the process of finding redundancies and eliminating them.

struct node {
    int num;
    char oper;
    node* left;
    node* right;
};

while (temp->left->oper == '+' || 
       temp->left->oper == '-' || 
       temp->left->oper == '*' || 
       temp->left->oper == '/' || 
       temp->right->oper == '+' || 
       temp->right->oper == '-' || 
       temp->right->oper == '*' || 
       temp->right->oper == '/') {
    // do something
}

What's the "repeated unit" here? Well, I see two instances of

   (something)->oper == '+' || 
   (something)->oper == '-' || 
   (something)->oper == '*' || 
   (something)->oper == '/'

So let's factor that repeated part out into a function, so that we only have to write it once.

struct node {
    int num;
    char oper;
    node* left;
    node* right;

    bool oper_is_arithmetic() const {
        return this->oper == '+' || 
               this->oper == '-' || 
               this->oper == '*' || 
               this->oper == '/';
    }
};

while (temp->left->oper_is_arithmetic() ||
       temp->right->oper_is_arithmetic()) {
    // do something
}

Ta-da! Shortened!
(Original code: 17 lines, 8 of which are the loop condition. Revised code: 18 lines, 2 of which are the loop condition.)

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • 1
    I'm not to familiar with modern C++, but isn't there a nice simple way of saying something like `['+', '-', '*', '/'].contains(this->oper)?` – Alexander Jul 25 '19 at 05:40
  • 1
    @Alexander: Nope. If you really wanted to shorten `oper_is_arithmetic()` further, you could write `return "+-*/"s.find(this->oper) != std::string::npos;` — but that Perl-style soup is much _less_ readable than just using plain old `this->oper == '+' || ...`. – Quuxplusone Jul 25 '19 at 06:03
  • Gah, there's a few APIs in the iOS world that use this pattern. Rather than having a method on String like `func contains(substring: String) -> Bool`, there's [`func range(of: String) -> NSRange`](https://developer.apple.com/documentation/foundation/nsstring/1410144-range), which returns the range of indices containing the matched substring, or a sentinel value if none is found (`NSNotFound`, akin to `std::string::npos`). I hate this pattern so much, is it really so hard to just add one extra function that returns a simple boolean? – Alexander Jul 25 '19 at 15:23
3

"+" "-" "*" and "/" are ASCII decimal values 42, 43, 45 and 47 thus

#define IS_OPER(x) (x > 41 && x < 48 && x != 44 && x != 46)

while(IS_OPER(temp->left->oper || IS_OPER(temp->right->oper){ /* do something */ }
user3281036
  • 115
  • 4
  • 3
    I would recommend making this a helper function rather than a macro, personally. And probably also documenting what it does, in case future maintainers aren't familiar with ASCII code points. – Justin Time - Reinstate Monica Jul 23 '19 at 17:10
  • 10
    You can write symbols directly as `'+'`. There is no need to remember any of those ASCII codes. – HolyBlackCat Jul 23 '19 at 18:18
  • 1st: prefer (inline) function to MACRO, 2nd: only work for ascii, Fail for [EBCDIC](https://en.wikipedia.org/wiki/EBCDIC) for example. – Jarod42 Jul 23 '19 at 21:59
  • 2
    I believe using ASCII codes might be better in this particular case, @HolyBlackCat, due to the use of range testing instead of direct comparison; `x > 41` is more readable than `x > ')'`, and `x < '0'` is _bound_ to catch at least a few people by surprise. (Although, in this case, it should note the usage of ASCII code points, and that it's thus only compatible with ASCII systems.) – Justin Time - Reinstate Monica Jul 23 '19 at 23:37
  • 3
    That said, I honestly do like the _idea_ behind this one, since it's a type of micro-optimisation that I don't believe compilers frequently do. ...Although, if optimising for performance is a concern, it may be best to start with `x < 48`, to [maximise the number of short-circuits from the leftmost comparison](https://coliru.stacked-crooked.com/a/31a83fccd44712ac). – Justin Time - Reinstate Monica Jul 23 '19 at 23:43
  • Sorry, I just don't see any improvement compared to the original code. It's more concise but also much more cryptic. – Eric Duminil Jul 24 '19 at 18:06
  • BTW, you change 4 comparisons by 4 other comparisons. so probably no gains. – Jarod42 Jul 25 '19 at 01:08
  • 3
    I think this is a pretty good idea even if the implementation is a bit buggy. the ASCII table order is designed to facilitate this kind of use. and this will probably perform much better if the condition is usually false since most letters will fail by the second check. if OP decides to support parentheses, then this could even be done with a bitmask – sudo rm -rf slash Jul 25 '19 at 07:06
3

Regex to the rescue!

#include <regex>

while (
    std::regex_match(temp->left->oper, std::regex("[\+\-\*\/]")) ||
    std::regex_match(temp->right->oper, std::regex("[\+\-\*\/]"))
) { 
// do something
}

EXPLANATION: Regex brackets [] denote a regex "character class." This means "match any character listed inside the brackets." For example, g[eiou]t would match "get," "git," "got," and "gut," but NOT "gat." The backslashes are needed because plus (+) minus (-) and star (*) and forward-slash (/) have meaning within a character class.

DISCLAIMER: I don't have time to run this code; you might have to tweak it, but you get the idea. You might have to declare/convert oper from a char to a std::string.

REFERENCES
1. http://www.cplusplus.com/reference/regex/regex_match/
2. https://www.rexegg.com/regex-quickstart.html
3. https://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124/ref=sr_1_1?keywords=regex&qid=1563904113&s=gateway&sr=8-1

kmiklas
  • 13,085
  • 22
  • 67
  • 103
  • 9
    This is quite inefficient, as the `std::regex` object is constructed up to _twice_ per loop iteration, and the regex expression needs to be compiled repeatedly. Extracting it as a constant would already help. – TheOperator Jul 23 '19 at 19:01
3

Trading space against time, you could build two "Boolean" arrays indexed by temp->left->oper and temp->left->oper, respectively. The corresponding array contains true when the condition is met, false otherwise. So:

while (array1[temp->left->oper] || array1[temp->right->oper]) {
// do something
}

As the sets for left and right seem identical, one array will actually do.

Initialization would be like this:

static char array1[256]; // initialized to "all false"

...

array1['+'] = array1['-'] = array1['*'] = array1['/'] = '\001';

Similar for array2. As jumps are bad for modern pipelining CPUs, you could even use a larger table like this:

while (array1[temp->left->oper << 8 | temp->right->oper]) {
    // do something
}

But initialization is more tricky:

static char array1[256 * 256]; // initialized to "all false"

...

void init(char c) {
    for (unsigned char i = 0; i <= 255; ++i) {
        array1[(c << 8) | i] = array1[(i << 8) | c] = '\001';
    }
}

init('+');
init('-');
init('*');
init('/');
U. Windl
  • 3,480
  • 26
  • 54
  • 1
    You also have to cast `char` index to `unsigned char` to avoid possible out of bound access when char is signed and negative. – Jarod42 Jul 25 '19 at 01:12
  • @Jarod42: It's long time since I programmed C++, so I just wrote it from my head. Feel free to suggest edit changed (e.e.: edit!). – U. Windl Jul 26 '19 at 00:18
2

Putting the operators in the unordered_set will be lot efficient and will provide O(1) access to operators.

unordered_set<char> u_set;                                                                                                                                                   
u_set.insert('+');                                                                                                                                                           
u_set.insert('*');                                                                                                                                                           
u_set.insert('/');                                                                                                                                                           
u_set.insert('-');                                                                                                                                                           


if((u_set.find(temp->left->oper) != u_set.end()) || (u_set.find(temp->right->oper) != u_set.end())) {     
                 //do something                                                                                                                
}
Spartan
  • 51
  • 5
  • 1
    In this case, O(1) will be **way** slower than O(n). – L. F. Aug 19 '19 at 15:49
  • Could you please explain it a little a bit. – Spartan Aug 20 '19 at 17:39
  • 2
    The O-notation is an asymptotic notation, meaning the constants are "absorbed." In this case, the overhead of `unordered_set` will significant dominate the cost of linear searching over 4 characters. – L. F. Aug 20 '19 at 17:41
1

Lambda & std::string_view

string_view gives lots of functionality of std::string and can operator on literals and it doesn't own the string.

Use Lambda instead of function for a highly local code which is not of use for the rest of the file. Also, no need to pass variables when lambda can capture them. Also get inline benefits without specifying it for a function you'd otherwise create.

Make char const:

auto is_arithm = [](const char c) {
  return std::string_view("+-/*").find_first_of(c) != std::string::npos;
};

while (is_arithm(temp->left->oper) || is_arithm(temp->right->oper)) {
}

You can change const char c to const node *t access its oper member inside the lambda. But that is not a good idea since members of left/right of temp can be modified.

auto is_arithm2 = [](const node *t) {
  return std::string_view("+-/*").find_first_of(t->oper) != std::string::npos;
};

while(is_arithm2(temp->left) || is_arithm2(temp->right)){
}
  • Looks good. However, it is [not a good idea to take `const-&` of primitive types](https://stackoverflow.com/questions/14013139/is-it-counter-productive-to-pass-primitive-types-by-reference). Also in the case of `node *temp`, should have been captured by value, as it is just a pointer and you do not what to alter the content of it. +1, for `string`/`string_view` approach. – JeJo Aug 02 '20 at 16:12
  • thanks for the tips, and upvote! I removed the capturing part due to the reasons mentioned now in the answer . The `left`/`right` members' members can be modified via `t`: `t->left = nullptr` (not allowed), `t->left->oper = 'a'` (allowed). –  Aug 02 '20 at 16:56