41

Can I return more than one item in a select? For instance I have a List of Fixtures (think football (or soccer for the yanks) fixtures). Each fixture contains a home and away team and a home and away score. I want to get all the teams that drew. I want to use something like

IEnumerable<Team> drew = from fixture in fixtures
                         where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
                         select fixture.HomeTeam && fixture.AwayTeam;

I know this syntax is incorrect, what I don't know is if it's possible to do this. Would I need two queries and then concatenate them?

Edit: this is really a learning thing so it's not critical to achieve this in any particular way. Basically, at this stage all i want is a list of teams that have drawn. An example usage might be that for a given list of fixtures i can find all of the drawn teams so that i could update their standings in a table by 1 point (3 for a win, 0 for a loss).

7 Answers 7

35

The following will return an IEnumerable<Team>:

IEnumerable<Team> drew =
    from fixture in fixtures
    where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
    from team in new[]{fixture.HomeTeam, fixture.AwayTeam}
    select team;

Or, with the fluent style of LINQ:

IEnumerable<Team> drew =
    fixtures
    .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
    .SelectMany(fixture => new[]{fixture.HomeTeam, fixture.AwayTeam});

Flattening and FlatMap

This requirement is often called 'flattening'. That is, taking a <Collection of <Collections of Things>> and converting it to a <Collection of Things>.

SelectMany both maps (a fixture to an Array of Teams) and flattens (a sequence of Team Arrays to a sequence of Teams). It is similar to the "flatMap" function in other languages such as Java and JavaScript.

It is possible to separate the Mapping and the Flattening:

IEnumerable<Team> drew =
    fixtures
    .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
    // map 
    .Select(fixture => new[]{fixture.HomeTeam, fixture.AwayTeam})
    // flatten
    .SelectMany(teams => teams);

Other Approaches

Iterator Block

The same can be achieved with an iterator block, but I suspect this is rarely the best approach:

IEnumerable<Team> Drew(IEnumerable<Fixture> fixtures){
    var draws = 
      fixtures
      .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore));

    foreach(var fixture in draws){
        yield return fixture.HomeTeam;
        yield return fixture.AwayTeam;
    }
}

Union

Union is also an option but has the potential to produce different results from the above:

  1. The order of results will be different. All Home results are returned then all Away results.

  2. Union enumerates fixtures twice, so, depending on how fixtures is implemented, there is the potential for fixtures to be updated between calls. E.g., if a new drawn fixture were added between calls then the Away team could be returned but not the Home team.

As Mike Powell describes:

IEnumerable<Team> drew =
    ( from fixture in fixtures
      where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
      select fixture.HomeTeam
    ).Union(
      from fixture in fixtures
      where fixture.Played  && (fixture.HomeScore == fixture.AwayScore)
      select fixture.AwayTeam );

Depending on how fixtures is sourced/implemented it may be worth considering 'caching' the drawn fixtures to avoid having to enumerate fixtures twice.

var draws = 
    ( from fixture in fixtures
      where fixture.Played  && (fixture.HomeScore == fixture.AwayScore)
      select fixture
    ).ToList();

IEnumerable<Team> drew =
    (from draw in draws select draw.HomeTeam)
    .Union(from draw in draws select draw.AwayTeam);

Or using the fluent style:

var draws = 
    fixtures
    .Where(fxtr => fxtr.Played && (fxtr.HomeScore == fxtr.AwayScore))
    .ToList();

IEnumerable<Team> drew =
    draws.Select(fixture => fixture.HomeTeam)
    .Union(draws.Select(fixture => fixture.AwayTeam));

Modifying the Fixture class

One could consider adding "ParticipatingTeams" to the Fixture class to get:

IEnumerable<Team> drew =
    from fixture in fixtures
    where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
    from team in fixture.ParticipatingTeams
    select team;

but as @MattDeKrey points out that requires a contract change.

Code Samples

Code samples are available on Repl.it

Sign up to request clarification or add additional context in comments.

1 Comment

+1 for your first query - doesn't require a contract change and is more efficient than the leading answer.
34

101 LINQ Samples, namely Select - Anonymous Types 1

... select new { HomeTeam = fixture.HomeTeam, AwayTeam = fixture.AwayTeam };

6 Comments

Not the answer he's looking for. He wants a list of Teams, not a list of anonymous types with hometeam and awayteam properties.
This is true... i could get round it using anonymous types... just wondered if there was a way to get just a list of teams. If it's the only way it's the only way though
I agree that this doesn't return a list of teams, but i thinks its better for him to adapt his code to suport handling this anon type. If James Hay could update his question to describe his usuage that might help.
I think his question already describes his requirement perfectly: "I want to get a list of teams that drew." There are lots of reasons he might not want to use anonymous types here (needing to pass the list outside this method would be a common one).
Since the original post, tuples have been added to .Net/C# and might be an option in many places where an anonymous type can be used. blogs.msdn.microsoft.com/mazhou/2017/05/26/…
|
23

I think you're looking for the Union method as follows:

IEnumerable<Team> drew = (from fixture in fixtures
                     where fixture.Played 
                        && (fixture.HomeScore == fixture.AwayScore)
                     select fixture.HomeTeam)
                     .Union(from fixture in fixtures
                     where fixture.Played 
                        && (fixture.HomeScore == fixture.AwayScore)
                     select fixture.AwayTeam);

3 Comments

Is there any LINQ keyword equivalent of the .Union() method?
@NikhilGirraj No, I don't think there is. 'Union' is not one of the LINQ keywords: learn.microsoft.com/en-us/dotnet/csharp/language-reference/… And, I expect that if it exisited then it would have been used in this example: learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/…
For this Linq, The SQL Generated is INNER Join ?
14

Taking a stab at this myself I came up with the same version as 'it depends'.

Using query comprehension syntax:

IEnumerable<Team> drew =
    from fixture in fixtures
    where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
    from team in new[]{fixture.AwayTeam, fixture.HomeTeam}
    select team;

Using lambda with extension methods:

IEnumerable<Team> drew =
    fixtures.Where(f => f.Played && f.HomeScore == f.AwayScore)
    .SelectMany(f => new[]{f.HomeTeam, f.AwayTeam});

Edit: I don't know if a team could have possibly played and drawn more than once in your database, but if that's possible, then you might want to make use of the Distinct query operator:

IEnumerable<Team> drew =
    (from fixture in fixtures
     where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
     from team in new[]{fixture.AwayTeam, fixture.HomeTeam}
     select team).Distinct();

or:

IEnumerable<Team> drew =
    fixtures.Where(f => f.Played && f.HomeScore == f.AwayScore)
    .SelectMany(f => new[]{f.HomeTeam, f.AwayTeam})
    .Distinct();

1 Comment

Last sample worked for me. Also taught me that you can use type inference to declare arrays. Every day is a school day.
6

Or you can define a type to hold all that data:

IEnumerable<TeamCluster> drew = from fixture in fixtures
                         where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
                         select new TeamCluster {
                             Team1 = fixture.HomeTeam,
                             Team2 = fixture.AwayTeam,
                             Score1 = fixture.HomeScore,
                             Score2 = fixture.AwayScore
                         };

class TeamCluster {
    public Team Team1 { get; set; }
    public Team Team2 { get; set; }
    public int Score1 { get; set; }
    public int Score2 { get; set; }
}

Comments

5

Edit: Sorry, misunderstood your original question, so rewrote answer.

You could use the "SelectMany" operator to do what you want:

IEnumerable<Team> drew =
           (from fixture in fixtures
            where fixture.Played && (fixture.HomeScore == fixture.AwayScore)
                  select new List<Team>()
                             { HomeTeam = fixture.HomeTeam,
                               AwayTeam = fixture.AwayTeam
                             }).SelectMany(team => team);

This will return a flattened list of teams that drew.

Comments

1

I came up against this very issue, and couldn't find what I wanted so I wrote a small extension method which did what I wanted.

public static IEnumerable<R> MapCombine<M, R>(this IEnumerable<M> origList, params Func<M, R>[] maps)
{
    foreach (var item in origList)
    foreach (var map in maps)
    {
        yield return map(item);
    }
}

Following the problem in the question, you would then do something like this

var drew = fixtures.Where(fixture => fixture.Played && 
                        (fixture.HomeScore == fixture.AwayScore))
                    .MapCombine(f => f.HomeTeam, f => f.AwayTeam);

Interestingly intellisense isn't exactly happy about this, you don't get the lamdba expression in the top of the drop down, however after the '=>' its quite happy. But the main thing is the compiler is happy.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.