How to map two different lists to one list? - c#

I have two lists of Generic types A and B:
public class A {
int type;
string params;
bool isActive;
}
public class B {
int type;
}
How could I map them into one list of type A where B.type == A.type (not A.type == B.type!!) using linq?
Instances of class B contain int values that can be deleted or added whereas instances of class A contain values from my db.
So for example:
A[0] = {1, "11", true}, A[1] = {2, "22", true}, A[2] = {3, "33", false}
and
B = {2, 3}
The desired result consists of A[1] and A[2].

It sounds like what you mean is "filter the items from the first list by checking a property against a second list" - in which case, I would suggest:
build an index from the second list:
// create an index of the "type"s to look for
var index = new HashSet<int>(bList.Select(x => x.type));
use this to filter the data
// filter the primary list to values from the index
var matches = aList.FindAll(x => index.Contains(x.type));
This will very efficiently give you a list of just the A data that has corresponding values in the bList.
Here it is runnable:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
public class A
{
public int type;
public string #params;
public bool isActive;
}
public class B
{
public int type;
}
static void Main()
{
var aList = new List<A>
{
new A { type = 1, #params = "11", isActive = true },
new A { type = 2, #params = "22", isActive = true },
new A { type = 3, #params = "33", isActive = false },
};
var bList = new List<B>
{
new B { type = 2 },
new B { type = 3 },
};
// create an index of the "type"s to look for
var index = new HashSet<int>(bList.Select(x => x.type));
// filter the primary list to values from the index
var matches = aList.FindAll(x => index.Contains(x.type));
foreach (var match in matches)
{
Console.WriteLine($"{match.type}, {match.#params}, {match.isActive}");
}
}
}
with output:
2, 22, True
3, 33, False

You want to join both lists, so find all A which are in both lists?
var query = from a in aList
join b in bList
on a.type equals b.type
select a;
List<A> resultList = query.ToList();

Is this what you are looking for !?
var result = arrayA.Where(a => arrayB.Select(b => b.type).Contains(a.type)).ToArray();

If you have two sequences, where both sequence have a value that should match, and you want to take zero or more properties form the first sequence and zero or more properties from the second sequence you use Enumerable.Join.
The syntax seems a bit difficult, but if used more often you get accustomed to it.
Suppose in your example you have a sequence of A objects and a sequence of B objects:
IEnumerable<A> myAobjects = ...
IEnumerable<B> myBobjects = ...
// do the join:
myAObjects.Join(myBobjects, // join the two sequences
myAobject => myAobject.type, // from the A sequence take property type
myBobject => myBobject.type, // from the B sequence also take property type
(myAobject, myBobject) => // whenever the values of these properties equal, take:
...);
The dots will be filed with what you want from the combination of a myAobject and a myBobject that have the same value for property type. Your question is simple: whenever a myAobject.type matches a myBobject.type, you want the complete myAObject. In that case the last part of the join is:
(myAobject, myBobject) => myAobject
If you wanted something else returned you would use something like:
(myAobject, myBobject) => new
{
MyParams = myAobject.Params,
MyOtherValue = myBObject.type,
}

Related

Use linq to remove elements in one list using a condition in another

I have
List<X> A = new List<X>{null,"1",null,"3"};
List<Y> B = new List<Y>{ 0 , 1 , 2 , 3 };
I want to use linq to list only the elemnts in B that have a corresponding value in A that is not null. so...
List<Y> C = [some linq expression using A and B];
C now has 1 and 3 in it.
How can this be done?
List<String> A = new List<String> { null, "1", null, "3" };
List<int> B = new List<int> { 0, 1, 2, 3 };
var C = A.Zip(B, (s, n) => new { a = s, b = n })
.Where(x => x.a != null)
.Select(x => x.b)
.ToList();
var c = B.Where((o, i) => A[i] != null).ToList();
Edit to note that it was unclear to me when this was written that both lists are aligned by index. Unsure of the value of this response given that information. It's certainly less valuable than I initially imagined.
Essentially what you want is an intersection. Here's an answer using Intersect() that works based on the data and parameters supplied in your example:
var a = new List<string> { null, "1", null, "3" };
var b = new List<int> { 0, 1, 2, 3 };
var intersection = a.Intersect(b.Select(x => x.ToString())).ToList();
You should be able to adapt to an intersection that works for you.
If both of your lists really have nullable items in them, then you'll need additional null checks on the b list (I'm just blindly calling ToString() on each item in it). But there's no reason to filter out nulls in A if B contains no nulls and you are doing an intersection, they will be filtered out as part of that process.
Consider also that:
b.Select(x => x.ToString()) ...
Could very easily be:
b.Select(x => ConvertTypeBToTypeA(x)) ...
List<string> A = new List<string> { null, "1", null, "3" };
List<int> B = new List<int> { 0, 1, 2, 3 };
var C = B.Where(x => A.Contains(x.ToString()));
How about an extension method to avoid some overhead?
public static class Ext {
public static IEnumerable<T1> WhereOther<T1, T2>(this IEnumerable<T1> src, IEnumerable<T2> filter, Func<T2, bool> pred) {
using (var isrc = src.GetEnumerator())
using (var ifilter = filter.GetEnumerator())
while (ifilter.MoveNext())
if (isrc.MoveNext())
if (pred(ifilter.Current))
yield return isrc.Current;
}
}
With that created, you can use
var ans = B.WhereOther(A, p => p != null);
You may also want an IQueryable variant, though creating one isn't that easy.
I guess you could cheat and return a lambda that applies AsEnumerable() and then uses IEnumerable.WhereOther.
try this:
var c = Enumerable.Range(0, Math.Min(B.Count, A.Count))
.Where(i => A[i] != null)
.Select(i => B[i]).ToList();

Auto sorted list c# with duplicate keys

I have a series of objects and a function
double P(Object a, Object b){...}
Now, for a fixed Object a, I would like to store inside a list L all the other objects in this way:
Objects a,b,c,d with P(a,b)=1, P(a,c)=2, P(a,d)=1 should have
L[0] = b or d, L[1] = b or d, L[2] = c
Note that I only need to access (not modify, delete ecc..) the items stored in L, if L could be a SortedList then IndexOfValue would be perfect but it doesn't support duplicate keys.
Is there an easy way to solve this problem?
From the c# interactive shell
// making up a class, since there aren't any details.
// make it have some kind of value, and a human friendly name
public class Thing { public int Val {get; set;} public string Name { get; set; } }
// since P isn't given, make something up. How about adding two numbers?
Func<Thing, Thing, double> P = (a, b) => { return a.Val + b.Val; };
// give starting values to match example function output
var a = new Thing() { Val = 0, Name = "a" };
var b = new Thing() { Val = 1, Name = "b" };
var c = new Thing() { Val = 2, Name = "c" };
var d = new Thing() { Val = 1, Name = "d" };
// others is the list of values, sorted by the output from the function "P",
// compared against the first Thing ("a" in this case")
var others = (new List<Thing>() { b,c,d }).OrderBy(x => P(a, x));
// interactive shell out gives:
. others.Select(x => x.Name)
Enumerable.WhereSelectEnumerableIterator<Submission#0.Thing, string> { "b", "d", "c" }

How to order IEnumerable based on a predefined sequence?

I have a IEnumerable<Process>.
public class Process
{
public string Id { get; set; }
public string Name { get; set; }
public int Type { get; set; }
}
I want to order this IEnumerable<Process> based on Type. The ordering should be based on a predefined sequence.
The sequence is: 7,3,4,1. So, if the collection has any of these values they should appear in the order first 7, then 3 etc.
Any other Type then on should be in ascending order.
What is the correct way to order this based on a predefined sequence? Should I define the sequence itself as a enum?
If the sequence of numbers is a List<int> you can order by the index:
var ordered = processes
.OrderByDescending(p => numbers.Contains(p.Type))
.ThenBy(p => numbers.IndexOf(p.Type));
or, a little bit more efficient and readable with LINQ's query syntax:
var ordered = from proc in processes
let index = numbers.IndexOf(proc.Type)
orderby index == -1, index
select proc;
index == -1 returns a bool where true is "higher" than false. That's the whole trick.
By the way, Array has also an IndexOf method:
let index = Array.IndexOf(numArray, proc.Type)
Create a lookup from those items to the value to sort on (in this case, the index in that list) and then you can easily project each of the values to the value to sort on:
var typeOrders = new int[]{7,3,4,1};
var typeOrderLookup = typeOrders.Select((type,i)=>new{type,i})
.ToDictionary(pair => pair.type, pair => pair.i);
var query = someData.OrderBy(process => typeOrderLookup[process.Type]);
What I've got from you question is, if your process list contains any Process with Type in sequence = (7, 3, 4, 1), then those processes should appear first with the ordering based on that sequence, then the rest of the process list should be ordered based on Type in ascending order.
What I've done is basically create two lists and finally union them. The first list contains only processes with Types in sequence which is sorted based on the sequence and the second list contains the rest of the original list simply ordered by Type.
Here is your answer with a sample data:
List<Process> processList = new List<Process>()
{
new Process() { Id = "1", name = "1", Type = 7},
new Process() { Id = "2", name = "2", Type = 1},
new Process() { Id = "3", name = "3", Type = 4},
new Process() { Id = "4", name = "4", Type = 3},
new Process() { Id = "5", name = "5", Type = 9},
new Process() { Id = "6", name = "6", Type = 8},
};
List<int> sequence = new List<int>() { 7, 3, 4, 1 };
var pl = processList.Where(p => sequence.Contains(p.Type))
.OrderBy(p => sequence.IndexOf(p.Type))
.Union(processList.Where(p => !sequence.Contains(p.Type))
.OrderBy(p => p.Type));
We can do it by one expression:
var order = new int[] { 7, 3, 4, 1 };
var orderedProccesses = order.Select(i => processes.FirstOrDefault(p => p.Type == i))
.Where(p => p != null)
.Concat(processes.Where(p => !order.Contains(p.Type)).OrderBy(p => p.Type))
.ToArray();
First map the defined list of order to the corresponding Processes:
order.Select((i) => processes.FirstOrDefault(p => p.Type == i))
Then, a sanity check, because it's not guaranteed that every item in the order to have a corresponding Process:
.Where((p) => p != null)
Then add the rest of the Processes and sort them by Type
.Concat(processes.Where((p) => !order.Contains(p.Type)).OrderBy((p) => p.Type))

Linq, select differences between two lists, select same between same list, and select duplicates same list

I want to know if it's possible in LINQ to achieve something along the lines of:
newList: { [1], [2] }
oldList: { [2], [3], [4], [5] }
resultantList = { [1], [2, 2], [3], [4], [5] }
Ok that was an oversimplification. Lets say:
Class A
{
public string Name;
public IList<B> Items;
public bool Equals(A obj)
{
return obj.Name == Name;
}
}
newList<A> = {{ Name = "A", Items[1]}, { Name = "B", Items[1] }}
oldList<A> = {{ Name = "C", Items[2]}, { Name = "A", Items[2] }, { Name = "D", Items[1] }, { Name = "E", Items[1] },}
mergedList<A> = {{ Name = "A", Items[3]}, { Name = "B", Items[1]}, { Name = "C", Items[2]}, { Name = "D" , Items[1]}, { Name = "E" , Items[1]}}
Notice that for the instance with Name="A" the list is actually the merged list of both lists. Order doesn't matter and in reality the equality is more complex.
I wish to achieve this on types (i.e. below works, but would be inefficient):
var fragments = newGeometryFragments.Except(oldGeometryFragments).ToList();
fragments.AddRange(oldGeometryFragments.Except(newGeometryFragments).ToArray());
var mergedFragments = (from newGeometry in newGeometryFragments
from oldGeometry in oldGeometryFragments
where newGeometry.Equals(oldGeometry)
select MergeGeometryFragments(newGeometry, oldGeometry)).ToArray();
fragments.AddRange(mergedFragments);
You can use the following code, assuming you have the oldList and newList already filled:
newList.Union(oldList) // unite the collections
.GroupBy(x => x /*, ComparerInstance*/) // will group by unique elemens
.Select(x => x.ToList()) // or .ToArray(), convert each group to array or list
.ToList(); // return a list of lists/arrays
The code above will produce a list of collections. Each collection will have one or more equal elements, depending on how many times that element is present in newList.Union(oldList).
If you are using specific elements and want to control the way they are compared (determined equal), pass a custom IEqualityComparer<YourType> instance to the GroupBy method.
You could use an outer join:
var oldList = new List<int?> { 2, 3, 4, 5 };
var newList = new List<int?> { 1, 2 };
var result = from all in oldList.Union(newList).OrderBy(num => num)
join o in oldList on all equals o into gjOld
from oldOuter in gjOld.DefaultIfEmpty()
join n in newList on all equals n into gjNew
from newOuter in gjNew.DefaultIfEmpty()
select new { newVal = newOuter, oldVal = oldOuter };
This works also with a custom class when you implement Equals and GetHashCode or implement a custom IEqualityComparer<T>. Then the Nullable<int> trick is also unnecessary
Demo
newVal: 1 oldVal:
newVal: 2 oldVal: 2
newVal: oldVal: 3
newVal: oldVal: 4
newVal: oldVal: 5

Difference Between Select and SelectMany

I've been searching the difference between Select and SelectMany but I haven't been able to find a suitable answer. I need to learn the difference when using LINQ To SQL but all I've found are standard array examples.
Can someone provide a LINQ To SQL example?
SelectMany flattens queries that return lists of lists. For example
public class PhoneNumber
{
public string Number { get; set; }
}
public class Person
{
public IEnumerable<PhoneNumber> PhoneNumbers { get; set; }
public string Name { get; set; }
}
IEnumerable<Person> people = new List<Person>();
// Select gets a list of lists of phone numbers
IEnumerable<IEnumerable<PhoneNumber>> phoneLists = people.Select(p => p.PhoneNumbers);
// SelectMany flattens it to just a list of phone numbers.
IEnumerable<PhoneNumber> phoneNumbers = people.SelectMany(p => p.PhoneNumbers);
// And to include data from the parent in the result:
// pass an expression to the second parameter (resultSelector) in the overload:
var directory = people
.SelectMany(p => p.PhoneNumbers,
(parent, child) => new { parent.Name, child.Number });
Live Demo on .NET Fiddle
Select many is like cross join operation in SQL where it takes the cross product.
For example if we have
Set A={a,b,c}
Set B={x,y}
Select many can be used to get the following set
{ (x,a) , (x,b) , (x,c) , (y,a) , (y,b) , (y,c) }
Note that here we take the all the possible combinations that can be made from the elements of set A and set B.
Here is a LINQ example you can try
List<string> animals = new List<string>() { "cat", "dog", "donkey" };
List<int> number = new List<int>() { 10, 20 };
var mix = number.SelectMany(num => animals, (n, a) => new { n, a });
the mix will have following elements in flat structure like
{(10,cat), (10,dog), (10,donkey), (20,cat), (20,dog), (20,donkey)}
var players = db.SoccerTeams.Where(c => c.Country == "Spain")
.SelectMany(c => c.players);
foreach(var player in players)
{
Console.WriteLine(player.LastName);
}
De Gea
Alba
Costa
Villa
Busquets
...
SelectMany() lets you collapse a multidimensional sequence in a way that would otherwise require a second Select() or loop.
More details at this blog post.
There are several overloads to SelectMany. One of them allows you to keep trace of any relationship between parent and children while traversing the hierarchy.
Example: suppose you have the following structure: League -> Teams -> Player.
You can easily return a flat collection of players. However you may lose any reference to the team the player is part of.
Fortunately there is an overload for such purpose:
var teamsAndTheirLeagues =
from helper in leagues.SelectMany
( l => l.Teams
, ( league, team ) => new { league, team } )
where helper.team.Players.Count > 2
&& helper.league.Teams.Count < 10
select new
{ LeagueID = helper.league.ID
, Team = helper.team
};
The previous example is taken from Dan's IK blog. I strongly recommend you take a look at it.
I understand SelectMany to work like a join shortcut.
So you can:
var orders = customers
.Where(c => c.CustomerName == "Acme")
.SelectMany(c => c.Orders);
The SelectMany() method is used to flatten a sequence in which each of the elements of the sequence is a separate.
I have class user same like this
class User
{
public string UserName { get; set; }
public List<string> Roles { get; set; }
}
main:
var users = new List<User>
{
new User { UserName = "Reza" , Roles = new List<string>{"Superadmin" } },
new User { UserName = "Amin" , Roles = new List<string>{"Guest","Reseption" } },
new User { UserName = "Nima" , Roles = new List<string>{"Nurse","Guest" } },
};
var query = users.SelectMany(user => user.Roles, (user, role) => new { user.UserName, role });
foreach (var obj in query)
{
Console.WriteLine(obj);
}
//output
//{ UserName = Reza, role = Superadmin }
//{ UserName = Amin, role = Guest }
//{ UserName = Amin, role = Reseption }
//{ UserName = Nima, role = Nurse }
//{ UserName = Nima, role = Guest }
You can use operations on any item of sequence
int[][] numbers = {
new[] {1, 2, 3},
new[] {4},
new[] {5, 6 , 6 , 2 , 7, 8},
new[] {12, 14}
};
IEnumerable<int> result = numbers
.SelectMany(array => array.Distinct())
.OrderBy(x => x);
//output
//{ 1, 2 , 2 , 3, 4, 5, 6, 7, 8, 12, 14 }
List<List<int>> numbers = new List<List<int>> {
new List<int> {1, 2, 3},
new List<int> {12},
new List<int> {5, 6, 5, 7},
new List<int> {10, 10, 10, 12}
};
IEnumerable<int> result = numbers
.SelectMany(list => list)
.Distinct()
.OrderBy(x=>x);
//output
// { 1, 2, 3, 5, 6, 7, 10, 12 }
Select is a simple one-to-one projection from source element to a result element. Select-
Many is used when there are multiple from clauses in a query expression: each element in the original sequence is used to generate a new sequence.
The formal description for SelectMany() is:
Projects each element of a sequence to an IEnumerable and flattens
the resulting sequences into one sequence.
SelectMany() flattens the resulting sequences into one sequence, and invokes a result selector function on each element therein.
class PetOwner
{
public string Name { get; set; }
public List<String> Pets { get; set; }
}
public static void SelectManyEx()
{
PetOwner[] petOwners =
{ new PetOwner { Name="Higa, Sidney",
Pets = new List<string>{ "Scruffy", "Sam" } },
new PetOwner { Name="Ashkenazi, Ronen",
Pets = new List<string>{ "Walker", "Sugar" } },
new PetOwner { Name="Price, Vernette",
Pets = new List<string>{ "Scratches", "Diesel" } } };
// Query using SelectMany().
IEnumerable<string> query1 = petOwners.SelectMany(petOwner => petOwner.Pets);
Console.WriteLine("Using SelectMany():");
// Only one foreach loop is required to iterate
// through the results since it is a
// one-dimensional collection.
foreach (string pet in query1)
{
Console.WriteLine(pet);
}
// This code shows how to use Select()
// instead of SelectMany().
IEnumerable<List<String>> query2 =
petOwners.Select(petOwner => petOwner.Pets);
Console.WriteLine("\nUsing Select():");
// Notice that two foreach loops are required to
// iterate through the results
// because the query returns a collection of arrays.
foreach (List<String> petList in query2)
{
foreach (string pet in petList)
{
Console.WriteLine(pet);
}
Console.WriteLine();
}
}
/*
This code produces the following output:
Using SelectMany():
Scruffy
Sam
Walker
Sugar
Scratches
Diesel
Using Select():
Scruffy
Sam
Walker
Sugar
Scratches
Diesel
*/
The main difference is the result of each method while SelectMany() returns a flattern results; the Select() returns a list of list instead of a flattern result set.
Therefor the result of SelectMany is a list like
{Scruffy, Sam , Walker, Sugar, Scratches , Diesel}
which you can iterate each item by just one foreach. But with the result of select you need an extra foreach loop to iterate through the results because the query returns a collection of arrays.
Some SelectMany may not be necessary. Below 2 queries give the same result.
Customers.Where(c=>c.Name=="Tom").SelectMany(c=>c.Orders)
Orders.Where(o=>o.Customer.Name=="Tom")
For 1-to-Many relationship,
if Start from "1", SelectMany is needed, it flattens the many.
if Start from "Many", SelectMany is not needed. (still be able to filter from "1", also this is simpler than below standard join query)
from o in Orders
join c in Customers on o.CustomerID equals c.ID
where c.Name == "Tom"
select o
Just for an alternate view that may help some functional programmers out there:
Select is map
SelectMany is bind (or flatMap for your Scala/Kotlin people)
Without getting too technical - database with many Organizations, each with many Users:-
var orgId = "123456789";
var userList1 = db.Organizations
.Where(a => a.OrganizationId == orgId)
.SelectMany(a => a.Users)
.ToList();
var userList2 = db.Users
.Where(a => a.OrganizationId == orgId)
.ToList();
both return the same ApplicationUser list for the selected Organization.
The first "projects" from Organization to Users, the second queries the Users table directly.
It's more clear when the query return a string (an array of char):
For example if the list 'Fruits' contains 'apple'
'Select' returns the string:
Fruits.Select(s=>s)
[0]: "apple"
'SelectMany' flattens the string:
Fruits.SelectMany(s=>s)
[0]: 97 'a'
[1]: 112 'p'
[2]: 112 'p'
[3]: 108 'l'
[4]: 101 'e'
Consider this example :
var array = new string[2]
{
"I like what I like",
"I like what you like"
};
//query1 returns two elements sth like this:
//fisrt element would be array[5] :[0] = "I" "like" "what" "I" "like"
//second element would be array[5] :[1] = "I" "like" "what" "you" "like"
IEnumerable<string[]> query1 = array.Select(s => s.Split(' ')).Distinct();
//query2 return back flat result sth like this :
// "I" "like" "what" "you"
IEnumerable<string> query2 = array.SelectMany(s => s.Split(' ')).Distinct();
So as you see duplicate values like "I" or "like" have been removed from query2 because "SelectMany" flattens and projects across multiple sequences.
But query1 returns sequence of string arrays. and since there are two different arrays in query1 (first and second element), nothing would be removed.
The SelectMany method knocks down an IEnumerable<IEnumerable<T>> into an IEnumerable<T>, like communism, every element is behaved in the same manner(a stupid guy has same rights of a genious one).
var words = new [] { "a,b,c", "d,e", "f" };
var splitAndCombine = words.SelectMany(x => x.Split(','));
// returns { "a", "b", "c", "d", "e", "f" }
One more example how SelectMany + Select can be used in order to accumulate sub array objects data.
Suppose we have users with they phones:
class Phone {
public string BasePart = "555-xxx-xxx";
}
class User {
public string Name = "Xxxxx";
public List<Phone> Phones;
}
Now we need to select all phones' BaseParts of all users:
var usersArray = new List<User>(); // array of arrays
List<string> allBaseParts = usersArray.SelectMany(ua => ua.Phones).Select(p => p.BasePart).ToList();
Suppose you have an array of countries
var countries = new[] { "France", "Italy" };
If you perform Select on countries, you will get each element of the array as IEnumerable<T>
IEnumerable<string> selectQuery = countries.Select(country => country);
In the above code, the country represents a string that refers to each country in the array. now iterate over selectQuery to get countries:
foreach(var country in selectQuery)
Console.WriteLine(country);
// output
//
// France
// Italy
If you want to print every character of countries you have to use nested foreach
foreach (var country in selectQuery)
{
foreach (var charOfCountry in country)
{
Console.Write(charOfCountry + ", ");
}
}
// output
// F, r, a, n, c, e, I, t, a, l, y,
OK. now try to perform SelectMany on countries. This time SelectMany gets each country as string (as before) and because of string type is a collection of chars, SelectMany tries to divide each country into its constituent parts (chars) and then returns a collection of chars as IEnumerable<T>
IEnumerable<char> selectManyQuery = countries.SelectMany(country => country);
In the above code, the country represents a string that refers to each country in the array as before, but the return value is the chars of each country
Actually SelectMany likes to fetch two levels inside of collections and flatten the second level as IEnumerable<T>
Now iterate over selectManyQuery to get chars of each country:
foreach(var charOfCountry in selectManyQuery)
Console.Write(charOfCountry + ", ");
// output
// F, r, a, n, c, e, I, t, a, l, y,
Here is a code example with an initialized small collection for testing:
class Program
{
static void Main(string[] args)
{
List<Order> orders = new List<Order>
{
new Order
{
OrderID = "orderID1",
OrderLines = new List<OrderLine>
{
new OrderLine
{
ProductSKU = "SKU1",
Quantity = 1
},
new OrderLine
{
ProductSKU = "SKU2",
Quantity = 2
},
new OrderLine
{
ProductSKU = "SKU3",
Quantity = 3
}
}
},
new Order
{
OrderID = "orderID2",
OrderLines = new List<OrderLine>
{
new OrderLine
{
ProductSKU = "SKU4",
Quantity = 4
},
new OrderLine
{
ProductSKU = "SKU5",
Quantity = 5
}
}
}
};
//required result is the list of all SKUs in orders
List<string> allSKUs = new List<string>();
//With Select case 2 foreach loops are required
var flattenedOrdersLinesSelectCase = orders.Select(o => o.OrderLines);
foreach (var flattenedOrderLine in flattenedOrdersLinesSelectCase)
{
foreach (OrderLine orderLine in flattenedOrderLine)
{
allSKUs.Add(orderLine.ProductSKU);
}
}
//With SelectMany case only one foreach loop is required
allSKUs = new List<string>();
var flattenedOrdersLinesSelectManyCase = orders.SelectMany(o => o.OrderLines);
foreach (var flattenedOrderLine in flattenedOrdersLinesSelectManyCase)
{
allSKUs.Add(flattenedOrderLine.ProductSKU);
}
//If the required result is flattened list which has OrderID, ProductSKU and Quantity,
//SelectMany with selector is very helpful to get the required result
//and allows avoiding own For loops what according to my experience do code faster when
// hundreds of thousands of data rows must be operated
List<OrderLineForReport> ordersLinesForReport = (List<OrderLineForReport>)orders.SelectMany(o => o.OrderLines,
(o, ol) => new OrderLineForReport
{
OrderID = o.OrderID,
ProductSKU = ol.ProductSKU,
Quantity = ol.Quantity
}).ToList();
}
}
class Order
{
public string OrderID { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
class OrderLine
{
public string ProductSKU { get; set; }
public int Quantity { get; set; }
}
class OrderLineForReport
{
public string OrderID { get; set; }
public string ProductSKU { get; set; }
public int Quantity { get; set; }
}
A select operator is used to select value from a collection and SelectMany operator is used to selecting values from a collection of collection i.e. nested collection.
It is the best way to understand i think.
var query =
Enumerable
.Range(1, 10)
.SelectMany(ints => Enumerable.Range(1, 10), (a, b) => $"{a} * {b} = {a * b}")
.ToArray();
Console.WriteLine(string.Join(Environment.NewLine, query));
Console.Read();
Multiplication Table example.

Categories

Resources