Is there a way I can check every item in a list and produce a result based on that check?

I have a variable:

public static List<CardSetWithWordCount> cardSetWithWordCounts;

Where the class looks like this;

public class CardSetWithWordCount
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsToggled { get; set; }
    public int TotalWordCount { get; set; }
}

How can I check if:

  • Exactly one of the elements in the list has the value of IsToggled set to true and then return the Name for that list item
  • More than one element has the value of IsToggled set to true and then return the word "mixed"
  • None of the element has the value of IsToggled set to true and then return a null;

I was thinking about LINQ as I have used this before but I am not sure this level of checking is possible with LINQ. I am interested to see if anyone knows if it's possible or will I just need to code a forEach loop or some other construct. Any tips would be much appreciated.

Jon Skeet
people
quotationmark

This is a slightly more efficient version of felix-b's answer - it doesn't require creating a new list. It will return as soon as it's sure of the result, without any need for checking the rest of the elements.

string GetDescription(IEnumerable<CardSetWithWordCount> cardSets)
{ 
    CardSetWithWordCount firstMatch = null;
    foreach (var match in cardSets.Where(x => x.IsToggled))
    {
        if (firstMatch != null)
        {
            // We've seen one element before, so this is the second one.
            return "mixed";
        }
        firstMatch = match;
    }
    // We get here if there are fewer than two matches. The variable
    // value will be null if we haven't seen any matches, or the first
    // match if there was exactly one match. Use the null conditional
    // operator to handle both easily.
    return firstMatch?.Name;
}

For a more purely LINQ version, I'd use felix-b's answer

To explore other pure LINQ alternatives that don't need to materialize results, you could use Aggregate.

First, a version that relies on Name being non-null:

static string GetDescription(IEnumerable<CardSetWithWordCount> cardSets) =>
    cardSets
        .Where(x => x.IsToggled)
        .Take(2)
        .Select(match => match.Name)
        .Aggregate<string, string>(
            null, // Seed
            (prev, next) => prev == null ? next : "mixed");

An alternative that doesn't rely on Name being non-null, but does create a new object if the result is going to be "mixed":

static string GetDescription(IEnumerable<CardSetWithWordCount> cardSets) =>
    cardSets
        .Where(x => x.IsToggled)
        .Take(2)
        .Aggregate(
            (CardSetWithWordCount) null, // Seed
            (prev, next) => prev == null 
                   ? next : new CardSetWithWordCount { Name = "mixed" })
        ?.Name;

All of these ensure that they only evaluate the input once, and stop as soon as the result is known.

people

See more on this question at Stackoverflow