linq selectmany flatten multiple levels - c#

I have the following relation (for example)
A contains one or more B's
Each B contains one or more C's and D's
I want to flatten everything using SelectMany along with some search conditions and get A,B,C and D's . This is what i have.
context.A.Where(a => (string.IsNullOrEmpty(name) || a.Name.Contains(name)))
.SelectMany(ab =>ab.b.Where(n=>n.bname.Contains(name) || string.IsNullOrEmpty(name)),
(aa, bb) => new { aa, bb }) //gets all a's and b's
.SelectMany(bc => bb.c.Where(w => w.KEYWORD.Contains(Keyword) || string.IsNullOrEmpty(Keyword)),
(bc,words) => new {bc,kwords}) //gets all b's and c's
Is what i am doing right? If so , then how to get B along with all D's adding to the above expression?

Data Selection using Lambda Syntax:
var flatData = context.A.SelectMany(a => a.B.SelectMany(b => b.Select(new {a,b,c = b.C,d = b.D})
Going further, following checks shall be done before applying the Where Clause, as they check the constant input supplied, name and keyword
string.IsNullOrEmpty(name)
string.IsNullOrEmpty(keyword)
Remaining checks would be simple:
if(!string.IsNullOrEmpty(name))
flatData = flatData.Where(data => data.a.Name.Contains(name))
.Where(data => data.b.Name.Contains(name));
if(!string.IsNullOrEmpty(keyword))
flatData = flatData.Where(data => data.c.Keyword.Contains(keyword));
Important points:
flatData above has a cascading filter, first on a.Name, b.Name and c.Keyword

Agreeing with what Ivan suggested you can flatten this 3 levels deep structure like this:
var query = (from a in A
from b in (List<dynamic>)a.b
from c in (List<dynamic>)b.c
from d in (List<dynamic>)b.d
select new { a, b, c, d });
if (!string.IsNullOrEmpty(name))
{
query = query.Where(record => record.b.bname.Contains(name));
}
if (!string.IsNullOrEmpty(keyword))
{
query = query.Where(record => record.c.keyword.Contains(keyword));
}
var result = query.ToList();
You can also add the where clauses in the query at the top but seeing that you are checking if you got any valid input at all I'd put it after
Tested it with this sample data:
List<dynamic> A = new List<dynamic>
{
new { b = new List<dynamic> { new { bname = "a", c = new List<dynamic> { new { keyword = "b" } }, d = new List<dynamic> { 1, 2, 3 } } } },
new { b = new List<dynamic> { new { bname = "a", c = new List<dynamic> { new { keyword = "d" } }, d = new List<dynamic> { 1, 2, 3 } } } }
};
string name = "a";
string keyword = "b";

Related

How can I create a new List<T> based on two other List<T> and account for duplicates?

My first post. Humbled by this community. Thank you.
The goal: Create a new List<PropertyB> based on two other lists:
List<PropertyA> and another List<PropertyB>.
For each PropertyA in the List<PropertyA>, create a new PropertyB(), assigning the DisplayName to the new PropertyB's Name property. For each property in 'List', if the name from PropertyA matches PropertyB, assign the value to the new list's value property.
The problem: Accounting for Duplicate values. No data loss can occur between the lists.
The new list should include: Every PropertyA and every Value of the PropertyB list where there is a Name match.
The types:
My thoughts: My gut says the inner loop should check whether something has already been added to the collection. Or perhaps an accounting of duplicate values (ie: the index of duplicates?)
Any assistance is appreciated!
public class PropertyA{
private string DisplayName{get; set;}
private string Name {get; set;}
private string Value {get; set;}
}
public class PropertyB{
private string Name{get; set;}
private string Value{get; set;}
}
Initialization:
List<PropertyA> listA = new List<PropertyA>()
{
new PropertyA(){ DisplayName="LOB", Name="lineofbusiness", Value="test"},
new PropertyA(){ DisplayName="ABC", Name="alpha", Value="test2"},
new PropertyA(){ DisplayName="DEF", Name="beta", Value="test3"},
new PropertyA(){ DisplayName="GHI", Name="zeta", Value="test4"},
new PropertyA(){ DisplayName"Line of Business", Name="lineofbusiness", Value="test5"
};
List<PropertyB> listB = new List<PropertyB>()
{
new PropertyB(){ Name="lineofbusiness", Value="test789"},
new PropertyB(){ Name="alpha", Value="test234"},
new PropertyB(){ Name="lineofbusiness", Value="test456"},
new PropertyB(){ Name="beta", Value="test123"},
};
In Main:
List<PropertyB> newList = new List<PropertyB>();
foreach(PropertyA propA in listA){
PropertyB newProp = new PropertyB();
newProp.Name = propA.DisplayName;
foreach(PropertyB propB in listB){
if(propA.Name == propB.Name){
newProp.Value = propB.Value;
break;
}
}
newList.Add(newProp);
}
UPDATE:
The console output (if you choose) should be as follows:
LOB test789
ABC test234
DEF test123
GHI null
Line of Business test456
if you simply remove the break; you end up with:
LOB test456
ABC test234
DEF test123
GHI null
Line of Business test456
The inner loop will always assign the LAST name match value. That's a problem.
you can just fix your code, add a check for duplicates
List<PropertyB> newList = new List<PropertyB>();
foreach(PropertyA propA in listA)
{
PropertyB newProp = new PropertyB();
newProp.Name = propA.DisplayName;
foreach (var propB in listB)
{
if (propA.Name == propB.Name)
{
if( newList.Any(l =>l.Value==propB.Value )) continue;
newProp.Value = propB.Value;
break;
}
}
newList.Add(newProp);
}
but to make it more reliable I would offer this
List<PropertyA> newList = new List<PropertyA>();
foreach (var propA in listA)
{
var newProp = new PropertyA();
newProp.Name = propA.DisplayName;
newProp.DisplayName = propA.Name;
foreach (var propB in listB)
{
if (propA.Name == propB.Name)
{
if (newList.Any(l => l.Value == propB.Value
&& l.DisplayName==propA.Name)) continue;
newProp.Value = propB.Value;
break;
}
}
newList.Add(newProp);
}
var result = newList.Select(l => new PropertyB {Name=l.Name, Value=l.Value} );
both algorithms show the same result during the test
LOB test789
ABC test234
DEF test123
GHI null
Line of Business test456
I understood the process:
list of A needs turning into a list of B
Some of the list of B items might have a Value copied from some other list of B
var d = bList.ToDictionary(b => b.Name, b => b.Value);
var newB = aList.Select(a => new B { Name = a.DisplayName, Value = d.GetValueOrDefault(a.Name) } ).ToList();
You said no data shall be lost but I think inherently you must have to throw something away because B has fewer properties than A and some properties from B are used to "overwrite"/take the place of those in A..
I note also you have duplicated Name in your sample data list B, which the ToDictionary won't tolerate. You didn't specify how to resolve this but you'll have to choose (if it truly does occur) what value to pick or if to take multiple. This, for example, would tolerate duplicate names
var d = bList.ToLookup(b => b.Name, b => b.Value);
var newB = aList.Select(a => new B { Name = a.DisplayName, Value = d[a.Name]?.First() } ).ToList();
Again, this throws stuff away.. if you want to keep all the values you'll have to encode the Value somehow
Value = string.Join(",", d[a.Name])
for example
So, it looks like you want to keep all the duplicates and dispense them in order. We could do that by grouping these things into a list that we pull the items out of as we enumerate
var d = bList.GroupBy(b => b.Name, b => b.Value).ToDictionary(g => g.Key, g => g.ToList());
var newB = new List<B>();
foreach(var a in aList){
var b = new B { Name = a.DisplayName };
if(d.TryGetValue(a.Name, out var lst)){
b.Value = lst[0];
lst.RemoveAt(0);
}
}

IEnumerable.Select() when attribute is known only at runtime

Say I have a data class like this and a list of its objects:
public class DataSet
{
public int A { get; set; }
public string B { get; set; }
public double C { get; set; }
}
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
I would like to do a Select() on the list, based on different properties. For example, if I need a list of property A, I could do this easily:
var listA = data.Select(x => x.A).ToList();
All good so far.
But in my program, I need to do the above, only, I wouldn't know whether I need a list of A or B or C until runtime. This 'knowledge' of what to select is stored in a list of strings, and I need to iterate it and extract only the appropriate lists. Something like this:
// GetKeys() will return the keys that I need to extract.
// So at one time keyList could have "A" and "B", another time "B" and "C" etc.
List<string> keyList = GetKeys();
foreach (var key in keyList)
{
// What do I do here?
data.Select(x =>???).ToList();
}
Is this possible at all? I'm fine with even a non-LINQ solution, if it achieves my goal.
EDIT:
Clarifying the requirement.
The end result I want is a separate list based on each 'key' mentioned above. So, something like
List<List<object>>
The count in outer list would be the count of keyList.
The inner list would have as many items as in DataSet.
This would probably not be the most efficient solution, but you could use Reflection for a fully dynamic solution:
private static List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties)
{
// get the properties only once per call
// this isn't fast
var wantedProperties = typeof(T)
.GetProperties()
.Where(x => properties.Contains(x.Name))
.ToArray();
var result = new Dictionary<string, List<object>>();
foreach (var item in data)
{
foreach (var wantedProperty in wantedProperties)
{
if (!result.ContainsKey(wantedProperty.Name))
{
result.Add(wantedProperty.Name, new List<object>());
}
result[wantedProperty.Name].Add(wantedProperty.GetValue(item));
}
}
return result.Select(x => x.Value).ToList();
}
And, of course, you'd need to do a double foreach or a LINQ query to print that. For example:
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
var selectedData = SelectDynamicData(data, new List<string> { "A", "C" });
foreach (var list in selectedData)
{
foreach (object item in list)
{
Console.Write(item + ", ");
}
Console.WriteLine();
}
Using Creating Expression Trees by Using the API you can build an expression tree to represent the linq query you were hard coding in order to make it more dynamic.
Expression<Func<TModel, object>> GetPropertyExpression<TModel>(string propertyName) {
// Manually build the expression tree for
// the lambda expression v => v.PropertyName.
// (TModel v) =>
var parameter = Expression.Parameter(typeof(TModel), "v");
// (TModel v) => v.PropertyName
var property = Expression.Property(parameter, propertyName);
// (TModel v) => (object) v.PropertyName
var cast = Expression.Convert(property, typeof(object));
var expression = Expression.Lambda<Func<TModel, object>>(cast, parameter);
return expression;
}
Review the comments to understand the building of the expression tree.
This now can be used with the data to extract the desired result.
Following similar to what was provided in another answer it would be simplified to
List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties) {
return properties
.Select(_ => data.Select(GetPropertyExpression<T>(_).Compile()).ToList())
.ToList();
}
Both methods are displayed in the following example
[TestMethod]
public void TestMethod1() {
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
var propertyKnownAtRuntime = "A";
var expression = GetPropertyExpression<DataSet>(propertyKnownAtRuntime);
var listA = data.Select(expression.Compile()).ToList();
//Produces
// { 1, 2, 3}
var listAC = SelectDynamicData(data, new List<string> { "A", "C" });
//Produces
//{
// { 1, 2, 3},
// { 1.1, 2.2, 3.3 }
//}
}
You can use reflection, for example
string key = "A";
var query = data.Select(x =>
{
var prop = x.GetType().GetProperty(key); //NOTE: if key does not exist this will return null
return prop.GetValue(x);
});
foreach (var value in query)
{
Console.WriteLine(value); //will print 1, 2, 3
}

Join Two Different Object DataType using Linq in C#

I am having two lists:
ListA:
[
{
Id = 1,
Name = "A",
Summary = ""
},
{
Id = 2,
Name = "B",
Summary = ""
}
]
ListB:
[
{
Id = 1,
Value = "SomeThing"
},
{
Id = 2,
Value = "EveryThing"
}
]
I want to join that two list using LINQ and want to return ListA which value is update as Below
[
{
Id = 1,
Name = "A",
Summary = "SomeThing"
},
{
Id = 2,
Name = "B",
Summary = "EveryThing"
}
]
I am joining ListA and ListB based on Id and assigning value to summary.
I tried below approach:
var query = from obj1 in ListA
join obj2 in ListB on obj1.Id equals obj2.Id
select obj1.Summary = obj2.Value, return obj1;
**=>so here i want assign data from obj2 to obj1 then want to return obj1 **
is that possible or how we can do this?
You could also update the existing ListA with a simple loop
foreach (var itemA in ListA)
{
itemA.Summary = ListB.FirstOrDefault(x => x.Id == itemA.Id)?.Value;
}
Join approach
var query = ListA.Join(ListB,
ia => ia.Id,
ib => ib.Id,
(ia, ib) => new aItem() //type of ListA here
{
Id = ia.Id,
Name = ia.Name,
Summary = ib.Value
});
You could try to join the two lists like this:
var listA = new List<ClassA>();
var listB = new List<ClassB>();
var list = listA.Join(listB, a => a.Id, b => b.Id, (a, b) =>
new ClassA
{
Id = a.Id,
Name = a.Name,
Summary = b.Value
});
Using method syntax Enumerable.Join is the easier one to use here:
var result = listA.Join(listB, // Join ListA and ListB
a => a.Id, // from every a in ListA take a.Id
b => b.Id, // from every b in ListB take b.Id
(a, b) => new // when they match, take the a and the b
{ // to create a new object with properties
Id = a.Id,
Name = a.Name,
Summary = b.Value,
});
Note that the result is of an anonymous type, If you want a result with the same type as the items in ListA (let's say they are of class A), change the last part of the join:
(a, b) => new A() // when they match, take the a and the b
{ // to create a new object of class A
Id = a.Id,
Name = a.Name,
Summary = b.Value,
});

Linq reducing one-to-many relationship to one-to-one

List<dynamic> a = new List<dynamic>();
a.Add(new { Foo = 1, Baz = "Inga", Name = "Alice"});
a.Add(new { Foo = 2, Baz = "Baz", Name = "Bob"});
a.Add(new { Foo = 3, Baz = "Hi", Name = "Charlie"});
List<dynamic> b = new List<dynamic>();
b.Add(new { Foo = 1, Value = "Bar", Code = "A"});
b.Add(new { Foo = 1, Value = "Quux", Code = "B"});
b.Add(new { Foo = 2, Value = "Bar", Code = "C"});
b.Add(new { Foo = 3, Value = "Mint", Code = "A"});
b.Add(new { Foo = 3, Value = "Seven", Code = "Q"});
b.Add(new { Foo = 3, Value = "Threeve", Code = "T"});
Ok....so I have a problem(naturally)
This is contrived and simplified to focus on the problem at hand.
I need to modify a Linq query to project the two Lists to the following response:
[
{ Foo = 1
, Baz = "Inga"
, Code = "A"
, Bars = [{ Value = "Bar", Code = "A"}
,{ Value = "Quux", Code = "B"}
]
}
,{ Foo = 2
, Baz = "Baz"
, Code = "C"
, Bars = [{ Value = "Fizz", Code = "C"}]
}
,{ Foo = 3
, Baz = "Hi"
, Code = "A"
, Bars = [{ Value = "Mint", Code = "A"}
,{ Value = "Seven", Code = "Q"}
,{ Value = "Threeve", Code = "T"}
]
}
]
First, the TL;DR
Is there any way to query collection b to select
(b.First Where Distinct By b.Foo) AsEnumerable() ?
...The long version
I need to select a projection of a but as it is being materialized, identify the first Code in list b where b.Foo == a.Foo and put b.Code directly on a. Then the items from b where b.Foo == a.Foo need to be put into a.Bars.
The problem I have on my hands is that I am not identifying a singular a so I can't preselect the a and b values to simplify this mess and there's no opportunity to remodel.
So, if I want to search Where Value = Bar; Alice and Bob need to be returned with the proper mapping and projection.
The naive attempt would be...
var results = a.Join( b
, master => master.Foo
, detail => detail.Foo
, (master, detail) => new { master, detail})
.Select(item => new
{
item.master.Foo
, item.master.Baz
, item.master.Name
, item.detail.Code
, Bars = b.Select(x => x.Foo.Equals(item.master.Foo))
};
but this causes my results to contain duplicate "Alice" records and duplicate "Charlie" records because it inner joined a and b. What I really want to do (pseudo) is
a.Join(
b.Where(b.Foo.Equals(a.Foo)).First()
, master => master.Foo
, detail => detail.Foo
, (master, detail) => new { master, detail}
)
.Select(item => new
{
item.master.Foo
, item.master.Baz
, item.master.Name
, item.detail.Code
, Bars = b.Select(x => x.Foo.Equals(item.master.Foo))
};
but no matter what I try, it comes out a mess.
...Note, I can't take the naive approach and then run a DistinctBy because the projection is anonymous.
Can anyone resolve this purely with Linq to Object queries? (Note: I'm not needing a single pass resolution)
I would just stick with grouping the second group (denoted b here) and then using that paired with a find to compose the projection.
var results = b.GroupBy( d => d.Foo ).Select( g => new {
Foo = g.Key,
Baz = a.First( i => i.Foo == g.Key ).Baz,
Code = g.First().Code,
Bars = g.Select( e => new { Value = e.Value, Code = e.Code }).ToArray()
});
If I understand correctly, the Code in a single result entry is just the Code of the first joined b element.
So try this:
var result = a.GroupJoin(b,
a0 => a0.Foo,
b0 => b0.Foo,
(a0, bs) =>
new
{
Foo = a0.Foo,
Baz = a0.Baz,
Code = bs.Select(b1 => b1.Code).FirstOrDefault(),
Bars = bs.Select(b1 => new {b1.Value, b1.Code}).ToArray()
}).ToArray();
GroupJoin is what you need here. You can think of Join like SelectMany while GroupJoin - like Select. The difference is the type of the second argument of the projection - TInner for Join and IEnumerable<TInner> for GroupJoin. In LINQ syntax the GroupJoin is achieved by into clause.
With all that being said, here is how it looks for your example in both syntaxes:
var resultsA = a.GroupJoin(b, master => master.Foo, detail => detail.Foo, (master, details) => new
{
master.Foo,
master.Baz,
master.Name,
Code = details.Select(detail => detail.Code).First(),
Bars = details.Select(detail => new { detail.Value, detail.Code })
});
var resultsB =
from master in a
join detail in b on master.Foo equals detail.Foo into details
select new
{
master.Foo,
master.Baz,
master.Name,
Code = details.Select(detail => detail.Code).First(),
Bars = details.Select(detail => new { detail.Value, detail.Code })
};
var query = from ai in a
let bs = b.Where(bi => bi.Foo == ai.Foo)
select new
{
ai.Foo,
ai.Baz,
Code = bs.Select(bi => bi.Code).FirstOrDefault(),
Bars = bs.Select(bi => new { bi.Value, bi.Code }),
};

Combining lists in linq

in linq, is it possible to combine many lists (of the same type), such that two lists,
list 1 = {a,b,c} and list 2 = {x,y,z}
turns into {[1,a] , [1,b] , [1,c] , [2,x] , [2,y] , [2,z] }
where [] represents a pair containing a "list identifier"
The problem is from having decks of arbitrary cards, where each deck is a list in a collection of lists.
I'm trying to create a query such that I can select only cards in a certain deck, or cards similar to 2 or more decks.
This is probably a duplicate question, but I don't know how to search for the question further then I already have.
List<List<int>> lists;
var combined = lists.Select((l, idx) => new { List = l, Idx = idx })
.SelectMany(p => p.List.Select(i => Tuple.Create(p.Idx + 1, i)));
var list1 = new List<string>() {a,b,c};
var list2 = new List<string>() {x,y,z};
var combined = list1.Select(x => new { id = 1, v = x }).Concat(list2.Select(x => new { id = 2, v = x }));
Normally I'd suggest Enumerable.Zip for combining multiple lists, however you seem to actually want to concatenate multiple lists with a list counter.
public IEnumerable<Tuple<int,T>> Combine<T>(params IEnumerable<T>[] lists) {
return lists.Select((x,i) => x.Select(y => Tuple.Create(i+1,y))).SelectMany (l =>l);
}
UPDATE
Completely missed that SelectMany has the index option so the above code can be written as
public IEnumerable<Tuple<int,T>> Combine<T>(params IEnumerable<T>[] lists) {
return lists.SelectMany((x,i) => x.Select(y => Tuple.Create(i+1,y)));
}
Then you can do
var list1 = new List<string> { "a", "b", "c" };
var list2 = new List<string> { "x", "y", "z" };
var combined = Combine(list1,list2);
Combined will be enumerable of tuples, with Item1 being the list index identifier (starting at 1) and Item2 being the value.
This method will handle multiple lists so you could just as easily call it with:
var list3 = new List<string> { "f", "g" };
var combined = Combine(list1,list2,list3);
You can merge the lists like:
var first = new List<string> {"a","b","c"};
var second = new List<string> {"x","y","z"};
var merged = first.Select(item => new { ListIndex = 1, Value = item}).ToList();
merged.AddRange(second.Select(item => new { ListIndex = 2, Value = item});
//or use concat
var merged = first.Select(item => new { ListIndex = 1, Value = item});
.Concat(second.Select(item => new { ListIndex = 2, Value = item});
Alternatively if you have the sources in something like:
List<List<string>> lists = new List<List<string>>
{
new List<string> {"a","b","c"},
new List<string> {"x","y","z"}
};
you can do:
var merged = lists.SelectMany((item, index) =>
item.Select(s => new { ListIndex = index, Value = s}));
Note that this will produce a 0-based list, so if you really need a 1-base list, just do ListIndex = index +1.
Also, if you will use this a lot, I would create it as an specific entity, something like
struct ListIdentValue
{
public int ListIndex {get; private set;}
public string Value {get; private set;}
public ListIdentValue(int listIndex, string value) {...}
}
Try using Concat
new[] {'a','b','c'}
.Select(v=>new Tuple<int,char>(1, v))
.Concat(
new[] {'x','y','z'}.Select(v=>new Tuple<int,char>(2, v))
)
string[] a = { "a", "b", "c" };
string[] b = { "x", "z", "y" };
var t =
(
from ai in a
select new { listNo = 1, Item = ai }
).Union
(
from bi in b
select new { listNo = 2, Item = bi }
);
or
var t =
(
from ai in a
select new object[] { 1, ai }
).Union
(
from bi in b
select new object[] { 2, bi }
);

Categories

Resources