17

Say I have 3 char variables, a, b and c.
Each one can be '0', which is a special case and means it matches every char.

So if a is '0', I only need to check if b == c.
I want to check if a == b == c, but found the implementation in C# goes chaotic and lengthy.

Is there any creative or pretty solution you can offer?

update

for performance driven, take Erik A. Brandstadmoen's approach. for simplicity, use M4N's apprach, also i did some modification: !(query.Any() && query.Distinct().Skip(1).Any())

alain.janinm
  • 19,951
  • 10
  • 65
  • 112
colinfang
  • 20,909
  • 19
  • 90
  • 173
  • 1
    @BoltClock: The logic is `a == b == c` and if any one of them is `0` then it's considered to be an automatic match. – Yuck Aug 10 '11 at 19:49
  • 1
    "each one can be "0", which means it matches every char." ??? – Dan Abramov Aug 10 '11 at 19:49
  • @Yuck: Do you mean if any of them is '0' then the other two don't have to match at all? – Jon Skeet Aug 10 '11 at 19:50
  • 1
    The way I read it, let's say `a = 't'`, `b = 't'` and `c = 0`...that should return `true` meaning that all 3 variables "match". – Yuck Aug 10 '11 at 19:51

9 Answers9

36

Something like this:

var a = '1';
var b = '0';
var c = '1';

var chars = new List<char> { a, b, c };
var filtered = chars.Where(ch => ch != '0');
var allEqual = filtered.Count() == 0 || filtered.Distinct().Count() == 1;

To explain the solution:

  • first put all chars into a list
  • exclude all chars which are '0': .Where(ch => ch != '0')
  • all remaining chars are equal if either:
    • the remaining collection contains no elements: chars.Count() == 0
    • or the number of unique remaining elements is 1: chars.Distinct().Count() == 1

Update: here's a different version, which does not use LINQ but is still and readable (IMO). It is implemented as a method and can be called with any number of characters to be tested:

public bool AllEqualOrZero(params char[] chars)
{
    if (chars.Length <= 1) return true;
    char? firstNonZero = null;
    foreach (var c in chars)
    {
        if (c != '0')
        {
            firstNonZero = firstNonZero ?? c;
            if (c != firstNonZero) return false;
        }
    }
}

// Usage:
AllEqualOrZero('0', '0', '0'); // -> true
AllEqualOrZero('0', '1', '1'); // -> true
AllEqualOrZero('2', '1', '0'); // -> false
AllEqualOrZero();              // -> true
AllEqualOrZero('1');           // -> true
M4N
  • 94,805
  • 45
  • 217
  • 260
  • Do you think it would be easier to check for `chars.Where(c => c != '0').Distinct().Count() <= 1` instead as [I did below](http://stackoverflow.com/questions/7016836/what-is-an-elegant-way-to-check-if-3-variables-are-equal-when-any-of-them-can-be/7016889#7016889)? Maybe I missed something.. – Dan Abramov Aug 10 '11 at 20:26
  • I also think that, concerning code readability, `chars.Count() == 0` is slightly misleading (because `chars` is already filtered of wildcards but it *suggests* we're checking the original characters). Also, there's `Any` for this kind of check. – Dan Abramov Aug 10 '11 at 20:28
  • Good, I think it looks much finer now. Thanks a lot. – Dan Abramov Aug 10 '11 at 20:34
  • 3
    The 60s are calling and want their lists and `lambda` back. "All this has happened before, and will happen again." (I convey this comment in a slightly ironic but entirely supportive fashion.) – Greg Hendershott Aug 10 '11 at 23:56
  • Very nice solution, and it is a perfect answer to the original question. However, if you are to run this for arrays/lists of an arbitrary size, using `Distinct` is not a very efficient way of saying `NOT EXISTS` (in SQL speak). Say you have an array with a million elements, where the first ones are 1 and 2, you would run through all elements to get the results of `Distinct`, as my answer further below would return false on the second element. Just a note. – Erik A. Brandstadmoen Aug 11 '11 at 06:34
18

The Elegant Solution

This requires basic understanding of LINQ and is based on the solution by M4N:

static bool IsMatch(params char[] chars)
{
    return chars.Where(c => c != '0')
                .Distinct().Count() <= 1;    
}

Edit

Originally my solution was different from M4N's solution but after some simplifications, I came to something (almost) exactly the same. While credits go completely to him, I'll just leave it for reference.

It returns true when there is at most one distinct non-wildcard character in the collection.
I made it accept a variable number of parameters so you may call it for 2, 3 or more chars:

bool match = IsMatch('3', '3', '4', '0');

The Simple Solution

This is pure translation of your logic in your code, no fancy stuff.

static bool IsMatch(char x, char y)
{
    return x == y || x == '0' || y == '0';
}

static bool IsMatch(char a, char b, char c)
{
    return IsMatch(a, b) && IsMatch(b, c) && IsMatch(a, c);
}

First IsMatch overload returns true when its argument are equal or one of them is '0'.
The second overload simply calls the first one for each pair.

(Note that due to wildcards we cannot use the transitive property and compare just two pairs.)

Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
2

Does this count as chaotic and lengthy?

Seems ok for me, providing you can only ever have the three of them...

return ((a == "0" || b == "0" || a == b) && (b =="0" || c =="0" || b == c) && (a =="0" || c =="0" || a == c));
Paddy
  • 33,309
  • 15
  • 79
  • 114
  • It seems very chaotic and lengthy to me. – FishBasketGordo Aug 10 '11 at 19:55
  • @Dan why doesn't the third check fail in that case? – Lily Chung Aug 11 '11 at 02:02
  • @Arafinwe: It wasn't there by the time of my comment :-) – Dan Abramov Aug 11 '11 at 02:09
  • Paddy, I couldn't comment originally b/c I didn't have enough rep yet, so I apologize for editing your question directly instead of just commenting, but the edit was actually correct - As noted in the comment I left on my edit, @Dan Abramov's false positive was '4 0 5' (if b==0, your result is True regardless of the other variables' values...) – johnny Aug 16 '11 at 14:27
2
bool MatchTwo(char a, char b)
{
    return a == '0' || b == '0' || a == b;
}

bool MatchThree(char a, char b, char c)
{
    return MatchTwo(a, b) && MatchTwo(a, c) && MatchTwo(b, c);
}

Not sure I'd call it elegant, but it isn't horrible (and might even be correct...) (note, this is more or less a refinement of Paddy's answer above).

johnny
  • 4,024
  • 2
  • 24
  • 38
SirPentor
  • 1,994
  • 14
  • 24
  • http://ideone.com/3hqd3 actually shows the exact result expected; from the original question: "if a is '0', I only need to check if b == c," e.g. something like a0a or bb0 should be true whereas a0c or ab0 should be false, so all the tests provided except aaa and 000 *should* be false... – johnny Aug 16 '11 at 14:17
2

You could write a struct "MYChar" that implements char and overrides Equals, equality operators and implicit conversion so you could do :

MyChar a = 'a';
MyChar b = '0';

bool eq = a == b; //true

Edit

It turns out that you can't inherit from char because it is sealed, but I tried the following code. It compiles, but I'm not sure it works. I compiled it from http://compilr.com/IDE/34853, but I don't have anything to test at the time.

here it goes :

public struct MyChar
{
    private static char _wild = '0';

    private char _theChar;

    public MyChar(char c)
    {
        _theChar = c;
    }

    public MyChar ()
        :this (_wild)
    {}

    private bool IsWildCard ()
    {
        return _theChar.Equals (_wild);
    }        

    public static implicit operator char (MyChar c)
    {
        return c._theChar;
    }

    public static implicit operator MyChar (char c)
    {
        return new MyChar (c);
    }


    public override bool Equals (object obj)
    {
        if (!(obj is MyChar))
        {
            return base.Equals (obj);
        }
        else
        {
            if (IsWildCard ())
            {
                return true;
            }
            else
            {
                MyChar theChar = (MyChar) obj;
                return theChar.IsWildCard () || base.Equals ((char) theChar);
            }
        }
    }

    public override int GetHashCode ()
    {
        return _theChar.GetHashCode ();
    }
}
Johnny5
  • 6,664
  • 3
  • 45
  • 78
  • 1
    You should probably make it a `struct` (and don't forget to implement `IEquatable`). – Dan Abramov Aug 10 '11 at 23:28
  • Indeed it must be a struct. I will not implement IEquatable here, but sure that's a good idea. – Johnny5 Aug 10 '11 at 23:33
  • Yes, it would be necessary to avoid constant (un)boxing. – Dan Abramov Aug 11 '11 at 01:27
  • Is is just me, or are you building a spaceship to fly 500 meters? – Erik A. Brandstadmoen Aug 11 '11 at 06:35
  • 1
    @Erik : Not really, he needs a char that handle a wildcards, I build a char that handle wildcards. Once done, you can add any wildcards to it, or any functionality you need. – Johnny5 Aug 11 '11 at 12:29
  • Nice solution, if you changed the name to contain a warning for the odd behavior, something like WildcardableChar (extending that to WildcardableString is interesting) and it's a very useful and elegant solution. +1. – Abel Aug 11 '11 at 15:13
2

Something like this should work for any number of char values:

public class Comparer
{
    public static bool AreEqualOrZero(params char[] values)
    {
        var firstNonZero = values.FirstOrDefault(x => x != '0');
        return values.All(x => x == firstNonZero || x == '0');
    }
}

Passes the following unit tests:

[TestClass()]
public class ComparerTest
{

    [TestMethod()]
    public void Matches_With_Wildcard()
    {
        char[] values = {'0', '1', '1', '1'};
        Assert.IsTrue(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Matches_With_No_Wildcard()
    {
        char[] values = {'1', '1', '1', '1'};
        Assert.IsTrue(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Matches_With_Only_Wildcards()
    {
        char[] values = {'0', '0', '0'};
        Assert.IsTrue(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Matches_With_Zero_Length()
    {
        char[] values = {};
        Assert.IsTrue(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Matches_With_One_Element()
    {
        char[] values = {'9'};
        Assert.IsTrue(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Matches_With_One_Wildcard_And_Nothing_Else()
    {
        char[] values = {'0'};
        Assert.IsTrue(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Does_Not_Match_On_NonEqual_Sequence_No_Wildcard()
    {
        char[] values = {'1', '2', '1', '1'};
        Assert.IsFalse(Comparer.AreEqualOrZero(values));
    }

    [TestMethod()]
    public void Does_Not_Match_On_NonEqual_Sequence_With_Wildcard()
    {
        char[] values = {'1', '2', '1', '0'};
        Assert.IsFalse(Comparer.AreEqualOrZero(values));
    }
}
Andrew
  • 7,630
  • 3
  • 42
  • 51
Erik A. Brandstadmoen
  • 10,430
  • 2
  • 37
  • 55
2

If you limit chars to ASCII and not unicode then, I like: http://ideone.com/khacx. (editted in response to comment pointing out I hadn't quite got the specs right, but I still like the basic idea. Added additional test as verification).

using System;
class example {
    static void elegant(char a, char b, char c) {
      int  y =  ((int) a - 48) + ((int) b - 48) + ((int) c - 48);
      int  z =  ((int) a - 48) * ((int) b - 48) * ((int) c - 48);

      bool result = y == ((int) a-48)*3 || (z ==0 && (a==b || b==c || a==c));
      Console.WriteLine(result);
    }
    static void Main() {

      elegant('0', 'b', 'c'); // false
      elegant('a', '0', 'c'); // false
      elegant('a', 'b', '0'); // false
      elegant('a', 'b', 'c'); // false
      elegant('0', '0', '0'); // true
      elegant('a', 'a', 'a'); // true
      elegant('0', 'a', 'a'); // true
      elegant('a', '0', 'a'); // true
      elegant('a', 'a', '0'); // true
      elegant('0', '0', 'a'); // true
      elegant('0', 'a', '0'); // true
      elegant('a', '0', '0'); // true
     }
}

For a more general solution that covers an unlimited number of characters, thats what regexs are for: ^(.)(\1|0)*$

jmoreno
  • 12,752
  • 4
  • 60
  • 91
  • From the original question, "if a is '0', I only need to check if b == c," so the 'wildcard' concept here doesn't indicate immediate match, but rather that character should be ignored. Therefore, all of your tests except 000 and aaa should return False, and this solution doesn't capture the asker's intent. – johnny Aug 16 '11 at 14:13
  • @johnny: you're right, changed the condition and the test. This makes it not quite so elegant, but still better than adding it to a List). – jmoreno Aug 18 '11 at 08:26
1

What about:

if ((a==b) && (b==c) && (a==c)) 
....
....

Is my logic wrong?

abalter
  • 9,663
  • 17
  • 90
  • 145
0

That is not very different from the accepted answer but anyway

var list = new List<Char> {'1', '1', '0'};

var check = list.Where(ch => ch != '0')
                .Distinct()
                .Count() < 2;
Mehran
  • 1,977
  • 1
  • 18
  • 33