Why does non-equality check of one variable against many values always return true?
Asked Answered
N

3

33

I have a variable v in my program, and it may take any value from the set of values

"a", "b", "c", ..., "z"

And my goal is to execute some statement only when v is not "x", "y", or "z".

I have tried,

  • for C-like languages (where equality operators compare the actual string values; e.g. , , )

    if (v != "x" || v != "y" || v != "z")
    {
        // the statements I want to be executed
        // if v is neither "x", nor "y", nor "z"
    }
    
  • for Pascal-like languages (e.g. )

    IF (v != 'x' OR v != 'y' OR v != 'z') THEN
        -- the statements I want to be executed
        -- if v is neither "x", nor "y", nor "z"
    END IF;
    

The statements inside the if condition always get executed. Am I doing anything wrong?

Nedry answered 13/10, 2014 at 9:45 Comment(4)
This was an attempt to create a canonical question/answer pair for the types of question described by the meta post Canonical, language-agnostic question for if(var != “x” || var != “y” …).Nedry
Note that in Java, comparing String with == and != (as in v != "x") is incorrect. We need to use the .equals method instead, as in !v.equals("x") (or !"x".equals(v) if v might be null). See How do I compare strings in Java? for more information.Gwenni
See also: De Morgan's rules explainedCassel
"where equality operators compare the actual string values" What is this supposed to mean? The difference in the example seems to be about the name/symbol of boolean operators, not how equality operators work. What is the deciding factor to know if one should look at C-like or Pascal-like language categories for another language?Bowing
B
50

Use &&/AND/and, not ||/OR/or:

v != "x" && v != "y" && v != "z"

Problem

If an if block is always executed, the condition for the if block always evaluates to true. The logical expression must be wrong.

Let us consider v != "x" || v != "y" || v != "z" for each value of v.

  • When v = "x",

    v != "x" becomes "x" != "x", which is false.
    v != "y" becomes "x" != "y", which is true.
    v != "z" becomes "x" != "z", which is true.

    The expression evaluates to false || true || true, which is true.

  • When v = "y", the expression becomes

      "y" != "x" || "y" != "y" || "y" != "z"
    

    or true || false || true, which is true.

  • When v = "z", the expression becomes

      "z" != "x" || "z" != "y" || "z" != "z"
    

    or true || true || false, which is true.

  • For any other value for v, the expression evaluates to true || true || true, which is true.

Alternatively, consider the truth-table:

       │     A          B          C      │
  v    │  v != "x"   v != "y"   v != "z"  │  A || B || C
───────┼──────────────────────────────────┼──────────────
 "x"   │    false      true       true    │     true
 "y"   │    true       false      true    │     true
 "z"   │    true       true       false   │     true
other  │    true       true       true    │     true

As you can see, your logical expression always evaluates to true.

Solution

What you want to do is, find a logical expression that evaluates to true when

(v is not "x")and(v is not "y")and(v is not "z").

The correct construction is,

  • for C-like languages (eg. , -(may need the strict equality operator !==), )

      if (v != "x" && v != "y" && v != "z")
      {
          // the statements I want to be executed
          // if v is neither "x", nor "y", nor "z"
      }
    
  • for Pascal-like languages

      IF (v != 'x' AND v != 'y' AND v != 'z') THEN
          -- the statements I want to be executed
          -- if v is neither "x", nor "y", nor "z"
      END IF;
    

De Morgan's law

By De Morgan's law, the expression can also be rewritten as (using C-like syntax)

!(v == "x" || v == "y" || v == "z")

meaning

not((v is "x")or(v is "y")or(v is "z")).

This makes the logic a bit more obvious.

Specific languages

Some languages have specific constructs for testing membership in sets, or you can use array/list operations.

Brunk answered 13/10, 2014 at 9:45 Comment(0)
E
5

I figured I'd contribute an answer for Bourne shell script, since the syntax is somewhat peculiar.

In traditional/POSIX sh the string equality test is a feature of the [ command (yes, that is a distinct command name!) which has some pesky requirements on quoting etc.

#### WRONG
if [ "$v" != 'x' ] || [ "$v" != 'y'] || [ "$v" != 'z' ]; then
    : some code which should happen when $v is not 'x' or 'y' or 'z'
fi

Modern shells like Ksh, Bash, Zsh etc also have [[ which is somewhat less pesky.

#### STILL WRONG
if [[ $v != 'x' || $v != 'y' || $v != 'z' ]]; then
    :  some code which should happen when $v is not 'x' or 'y' or 'z'
fi

We should highlight the requirement to have spaces around each token, which is something many beginners overlook (i.e. you can't say if[[$v or $v!='y' without whitespace around the commands and operators), and the apparent optionality of quoting. Failing to quote a value is often not a syntax error, but it will lead to grave undesired semantical troubles if you fail to quote a value which needs to be quoted. (More on this elsewhere.)

The obvious fix here is to use && instead of || but you should also note that [[ typically sports support for regular expressions, so you can say something like

if [[ ! $v =~ ^(x|y|z)$ ]]; then
    : yeah
fi

and don't forget the trusty old case statement which is quite natural for this, and portable back into the late 1970s:

case $v in
    x | y | z)
       ;; # don't actually do anything in this switch
    *) # anything else, we fall through to this switch
       yeah
       some more yeah
       in fact, lots of yeah;;
 esac

The trailing double semicolons cause aneurysms at first, but you quickly recover, and learn to appreciate, even love them. POSIX lets you put an opening parenthesis before the match expression so you don't have unpaired right parentheses, but this usage is rather uncommon.

(This is obviously not a suitable answer for Unix shells which are not from the Bourne family. The C family of shells -- including the still somewhat popular tcsh -- use a syntax which is supposedly "C-like" but that's like being unable to tell apart Alice Cooper from the girl who went to Wonderland; and the Fish shell has its own peculiarities which I'm not even competent to comment on.)

Expulsive answered 8/1, 2018 at 5:55 Comment(0)
M
2

You could use something like this, for PHP:

if(strpos('xyz',$v[0])===false)//example 1
//strpos returns false when the letter isn't in the string
//returns the position (0 based) of the substring
//we must use a strict comparison to see if it isn't in the substring

if(!in_array($v[0],array('x','y','z')))//example 2

//example 3
$out=array('x'=>1,'y'=>1,'z'=>1); //create an array
if(!$out[$v[0]]) //check if it's not 1

if(!preg_match('/^[xyz]$/',$v))//example 4, using regex

if(str_replace(array('x','y','z'),'',$v[0]))//example 5


if(trim($v[0],'xyz'))//example 6

For Javascript:

if(~'xyz'.search(v[0]))//example 1(.indexOf() works too)

if(!(v[0] in {x:0,y:0,z:0}))//example 2

if(~['x','y','z'].indexOf(v[0]))//example 3, incompatible with older browsers.

if(!/^[xyz]$/.match(v))//example 4

if(v.replace(/^[xyz]$/))//example 5

For MySQL:

Select not locate(@v,'xyz'); -- example 1

select @v not in ('x','y','z'); -- example 2

-- repetition of the same pattern for the others

For C:

if(!strstr("xyz",v))//example 1, untested

There are more ways, I'm just too lazy.

Use your imagination and just write the one that you like more!

Minoan answered 14/10, 2014 at 0:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.