February 26th, 2024
heart1 reaction

A C# LINQ one-liner to check if exactly one of a set of conditions is met

I was writing a tool that needed to check whether exactly one of a list of conditions was true. One way of doing this is to count them manually:

bool DoesExactlyOneConditionApply(DataSet data)
{
    int count = 0;
    if (data.Widgets.Count == 1)
    {
        ++count;
    }
    if (data.Gadgets.Count == 1)
    {
        ++count;
    }
    if (data.Doodads.Count > data.Sockets.Count)
    {
        ++count;
    }
    if (data.Approvers.Contains(Approver.Director))
    {
        ++count;
    }
    return count == 1;
}

This works and is efficient, but is rather wordy and susceptible to copy/pasta bugs.

Performance was not an issue in this code because most of the time was being spent in calculating the DataSet, not in checking whether conditions apply.

Here’s what I came up with:

bool DoesExactlyOneConditionApply(DataSet data)
{
    return new[] {
        data.Widgets.Count == 1,
        data.Gadgets.Count == 1,
        data.Doodads.Count > data.Sockets.Count,
        data.Approvers.Contains(Approver.Director),
    }.Count(v => v) == 1;
}

This builds a temporary array that holds the results of each test, and we ask LINQ to tell us how many of them are true.

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

21 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • 紅樓鍮 · Edited

    A semi-straightforward translation to C++ (z/8hY9841j9), using Range-v3 in place of LINQ and for the C# array, can generate reasonably efficient code that doesn't use heap memory; GCC and clang can even break up the and enregister all four booleans, so it doesn't even use stack memory. (Semi-straightforward because godbolt's version of Range-v3 doesn't have .)

    Unfortunately, neither GCC nor clang seems to be able to use the hint to optimize away calls when 2 trues have already been found (maybe that's too tall an order, considering the amount of C++ machinery in Range-v3 that the compilers need...

    Read more
  • Shawn Van Ness
    bool DoesExactlyOneConditionApply(DataSet data)
    {
        int n = 0;
        n += (data.Widgets.Count == 1) ? 1 : 0;
        n += (data.Gadgets.Count == 1) ? 1 : 0;
        n += (data.Doodads.Count > data.Sockets.Count) ? 1 : 0;
        n += (data.Approvers.Contains(Approver.Director)) ? 1 : 0;
        return (n == 1);
    }
    • Ivan K

      Yeah. Can’t you just do that on one line, and possibly omit the ternary if true/false decay to 1/0?
      And if you want to short circuit could also maybe use crazy parentheses to group statements with a logical operator that stopped evaluation if both sides are “1”.

  • Darkwolf0815

    I like Extension Methods for such things.
    I also like generics. So when we put that together, we can write a generic extension method that can use any number of conditions:
    <code>

    It would be used like this:
    <code>

    As performance is not a requirement here but readability I think this should work quite well.

    Read more
    • Andrew Ducker

      I like this one because it only checks as many conditions as it has to.
      And it would be trivial to extend it to allow the number of wanted conditions to be a parameter.

    • alan robinson

      Awesome, reminds me of the “good” “old” days of lisp/smalltalk.

      I would name it slightly differently, such as OnlyOneOfTheseIsTrue

      But it’s hard to beat a solution that’s a “one” liner, and where the function called doubles as a fairly complete comment about what’s being done/tested.

    • Bwmat

      I would just use a boolean instead of an integer, IMO it’s easier to understand.

  • Akash Bagh

    Not a one-liner. I’m just having fun.

    bool DoesExactlyOneConditionApply(params bool[] conditions)
    {
    	IEnumerable<bool?> helper()
    	{
    		bool found = false;
    		for(int idx = 0; idx < conditions.Length; ++idx)
    		{
    			if(found && conditions[idx]) yield return false;
    			found = found || conditions[idx];
    			yield return null;
    		}
    		yield return found;
    	};
    	foreach(bool? result in helper()){
    		if(result != null) return (bool)result;
    	}
    	return false;
    }