Combining lists in linq - c#

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 }
);

Related

Combining values of two dictionaries in C# with distinct keys

I am having difficulty combining two dictionaries into a dictionary with two values combined to a list for identical keys. For example, having D1 and D2
D1 = {2:"a",
3:"b",
4: "c"}
D2 = {1:"e",
2:"f",
4:"h",
5:"i"}
I would like to create D3.
D3= { 1:["", "e"]
2:["a", "f"]
3:["b", ""]
4:["c":"h"]
5:["", "i"]}
Thank you.
This can be done in a single Linq expression like so:
Flatten and concatenate both d1 and d2 to a single flat sequence of (Int32,String) value-tuples.
Re-group them by the Int32 key (this is the main step).
Then convert each group into a separate output dictionary entry.
Dictionary<Int32,String> d1 = new Dictionary<Int32,String>()
{
{ 2, "a" },
{ 3, "b" },
{ 4, "c" },
};
Dictionary<Int32,String> d2 = new Dictionary<Int32,String>()
{
{ 1, "e" },
{ 2, "f" },
{ 4, "h" },
{ 5, "i" },
};
Dictionary<Int32,List<String>> d3 = Array
.Empty<( Int32 k, String v )>()
// Step 1:
.Concat( d1.Select( kvp => ( k: kvp.Key, v: kvp.Value ) ) )
.Concat( d2.Select( kvp => ( k: kvp.Key, v: kvp.Value ) ) )
// Step 2:
.GroupBy( t => t.k )
// Step 3:
.ToDictionary(
grp => grp.Key,
grp => grp.Select( t => t.v ).OrderBy( v => v ).ToList()
);
An advantage of this approach is that it works for any number of duplicated values (not just two). Also, the use of ValueTuple means this approach should have fewer heap-allocations.
Screenshot proof of it working in LinqPad:
The expression can be made more succint - I use a more verbose style myself, but if you want to be cryptic about it by re-using KeyValuePair instead of ValueTuple, and if you don't care about ordering, then you can do this:
var d3 = d1
.Concat( d2 )
.GroupBy( kvp => kvp.Key )
.ToDictionary( g => g.Key, g => g.Select( kvp => kvp.Value ).ToList() );
Simplest solution would be with Dictionary.Keys
var D1 = new Dictionary<int,string>(){{2,"a"}, {3,"b"},{4,"c"}};
var D2 = new Dictionary<int,string>(){{1,"e"},{2,"f"}, {4,"h"},{5,"i"}};
var keys = D1.Keys.Union(D2.Keys).OrderBy(key => key);
var test = keys.Select(key => new {Key = key, Value= new string[] {D1.ContainsKey(key) ? D1[key] : "", D2.ContainsKey(key) ? D2[key] : ""} });
Console.WriteLine(test);
Interactive: https://rextester.com/UXQ51844
Alternatively, you could do something similar to this: LINQ - Full Outer Join
Sounds to be a job for LINQ. Here is one possibility to solve this issue:
public class Element
{
public int Index { get; set; }
public string Value { get; set; }
}
public class GroupedElement
{
public int Index { get; set; }
public IReadOnlyList<string> Values { get; set; }
}
public static class Program
{
public static void Main(string[] args)
{
var d1 = new[]
{
new Element { Index = 2, Value = "a" },
new Element { Index = 3, Value = "b" },
new Element { Index = 4, Value = "c" },
};
var d2 = new[]
{
new Element { Index = 1, Value = "e" },
new Element { Index = 2, Value = "f" },
new Element { Index = 4, Value = "h" },
new Element { Index = 5, Value = "i" },
};
var result = d1.Concat(d2)
.GroupBy(element => element.Index)
.Select(group => new GroupedElement { Index = group.Key, Values = group.Select(g => g.Value).ToList() })
.ToList();
foreach (var item in result)
{
Console.WriteLine($"{item.Index}: {string.Join(",", item.Values)}");
}
}
}
I think this is simpler without LINQ (LINQ is a hammer, not every problem is a nail)
Let's loop from 1 to 5, putting a new List for each int. The list is inited with d1's value and d2's value
var d3 = new Dictionary<int, List<string>>();
for(int x=1;x<6;x++)
d3[x] = new() { d1.GetValueOrDefault(x,""), d2.GetValueOrDefault(x,"") };
If your ints aren't always contiguous you could (use a bit of LINQ 😀 and..)
foreach(int x in d1.Keys.Union(d2.Keys))
d3[x] = new() { d1.GetValueOrDefault(x,""), d2.GetValueOrDefault(x,"") };
This doesn't need another answer the others answers are plenty good enough and well done, however here is another (convoluted) approach
Given
var d1 = new Dictionary<int, string> {{2, "a"}, {3, "b"}, {4, "c"}};
var d2 = new Dictionary<int, string> { { 1, "e" }, { 2, "f" }, { 4, "h" }, { 5, "i" } };
Usage
static string[] Stuff((string, string)[] v) =>
new[] {v[0].Item1 ?? v.ElementAtOrDefault(1).Item1 ?? "", v[0].Item2 ?? v.ElementAtOrDefault(1).Item2 ?? "" };
var result = d1
.Select(x => (x.Key, (x.Value,(string)null)))
.Concat(d2.Select(x => (x.Key, ((string)null, x.Value ))))
.GroupBy(element => element.Key)
.ToDictionary(x => x.Key, x => Stuff(x.Select(y =>y.Item2).ToArray()))
Output
foreach (var item in result.OrderBy(x => x.Key))
Console.WriteLine($"{item.Key}: {string.Join(",", item.Value)}");
---
1: ,e
2: a,f
3: b,
4: c,h
5: ,i
Please see, here is another approach for this query -
Input -
Dictionary<int, string> first_dict = new Dictionary<int, string>()
{
{ 2,"a" },
{ 3,"b" },
{ 4, "c"}
};
Dictionary<int, string> second_dict = new Dictionary<int, string>()
{
{ 1,"e" },
{ 2,"f" },
{ 4, "h"},
{ 5, "i"}
};
First, I got common keys from both dictionaries like this -
var allKeys = first_dict.Concat(second_dict).OrderBy(b => b.Key).Select(b => b.Key).Distinct().ToList();
and then I created two another dictionaries and inserted data into them like this -
Dictionary<int, string> first_dict_res = new Dictionary<int, string>();
Dictionary<int, string> second_dict_res = new Dictionary<int, string>();
foreach (var keyItem in allKeys)
{
var first_dict_res_value = (first_dict.ContainsKey(keyItem)) ? first_dict[keyItem] : null;
first_dict_res.Add(keyItem, first_dict_res_value);
var second_dict_res_value = (second_dict.ContainsKey(keyItem)) ? second_dict[keyItem] : null;
second_dict_res.Add(keyItem, second_dict_res_value);
}
and then I concatenated the result from both dictionaries to get the desired result-
var res_dict = first_dict_res.Concat(second_dict_res).GroupBy(b => b.Key)
.Select(c => new { key = c.Key, values = string.Join(",", c.Select(b => b.Value)) }).ToList();

LINQ to Entities .Contains for a tuple (2 foreign keys)

Actually, a colleague of mine asked me this question and I haven't been able to come up with an answer. Here goes.
Given an entity with 2 foreign keys, say
public class MyTable
{
public int Key1 { get; set; }
public int Key2 { get; set; }
}
and 2 lists
public ICollection List1 => new List<int> { 1, 2, 3 };
public ICollection List2 => new List<int> { 4, 5, 6 };
he needs to query for all records where Key1 matches the value from List1 and Key2 matches the value from List2, e.g.
Key1 == 1 && Key2 == 4
that is, he wants to check for any given tuple from List1 and List2, (1, 4), (2, 5) and (3, 6).
Is there a straightforward way in EF to do this?
You can make a for loop to capture some local variable (in each loop) in the Where and use Concat (or Union - maybe with worse performance) to sum up all the result like this:
IQueryable<MyTable> q = null;
//suppose 2 lists have the same count
for(var i = 0; i < list1.Count; i++){
var k1 = list1[i];
var k2 = list2[i];
var s = context.myTables.Where(e => e.Key1 == k1 && e.Key2 == k2);
q = q == null ? s : q.Concat(s);
}
//materialize the result
var result = q.ToList();
NOTE: we can use Concat here because each sub-result should be unique (based on searching the keys). It surely has better performance than Union (ensuring uniqueness while we already know the sub-results are all unique beforehand - so it's unnecessary).
If you have a list of int (just integral numeric), you can also pair the keys into underscore separated string and use Contains normally like this:
var stringKeys = list1.Select((e,i) => e + "_" + list2[i]).ToList();
var result = context.myTables.Where(e => stringKeys.Contains(e.Key1 + "_" + e.Key2))
.ToList();
Building an Expression tree is also another approach but it's more complicated while I'm not sure if it has better performance.
Try this:
static IQueryable<TSource> WhereIn(this Table<TSource> table, List<object[]> list) where TSource : class
{
var query = table.AsQueryable();
foreach (object[] item in list)
{
Expression<Func<TSource, bool>> expr = WhereInExpression(item);
query = query.Where(expr);
}
return query;
}
static Expression<Func<TSource, bool>> WhereInExpression<TSource>(object[] item)
{
ParameterExpression parameterItem = Expression.Parameter(typeof(TSource), "expr");
BinaryExpression filter1 = Expression.Equal(LambdaExpression.PropertyOrField(parameterItem, "Key1"),
Expression.Constant(item[0]));
BinaryExpression filter2 = Expression.Equal(LambdaExpression.PropertyOrField(parameterItem, "Key2"),
Expression.Constant(item[1]));
BinaryExpression filter = LambdaExpression.And(filter1, filter2);
var expr = Expression.Lambda<Func<TSource, bool>>(filter, new ParameterExpression[] { parameterItem });
expr.Compile();
return expr;
}
Usage:
List<object[]> list = new List<object[]>() { new object[] { 1, 100 }, new object[] { 1, 101 }, new object[] { 2, 100 } };
var result = db.MyTable.WhereIn<MyTable>(list);
Confirmed to work with Entity framework
var tuples = List1.Cast<int>().Zip(List2.Cast<int>(), (l, r) => new { l, r });
var results = Orders.Where(o =>
tuples.Contains(new { l = (int)o.KeyOne, r = (int)o.KeyTwo })
);
Or simpler, if you define your lists as ICollection<int> or IList<int> (etc...):
var tuples = List1.Zip(List2, (l, r) => new { l, r });
var results = Orders.Where(o =>
tuples.Contains(new { l = (int)o.KeyOne, r = (int)o.KeyTwo })
);
Fiddle here : https://dotnetfiddle.net/YyyZBY
var List_1 = new List<int> { 1, 2, 3 };
var List_2 = new List<int> { 4, 5, 6 };
var TargetList = new List<MyTable>();
var index1=0;
var List1 = List_1.Select(x=>new { ind=index1++,value=x });
var index2=0;
var List2 = List_2.Select(x=>new { ind=index2++,value=x });
var values = from l1 in List1
join l2 in List2 on l1.ind equals l2.ind
select new {value1=l1.value,value2=l2.value };
var result = TargetList.Where(x=>values.Any(y=>y.value1==x.Key1&&y.value2==x.Key2));

C# Ordering 2 lists randomly

I have been trying to figure out how to randomly order two lists the same eg.
List<string> list = new List<string>();
list.Add("RedHat");
list.Add("BlueHat");
list.Add("YellowHat");
List<image> list2 = new List<image>();
list.Add(Properties.Resources.RedHat);
list.Add(Properties.Resources.BlueHat);
list.Add(Properties.Resources.YellowHat);
now if i wanted to order these so that redhat and the redhat image stay aligned how may i do this?And is there a way to combine these lists and then shuffle using a dictionary or keyvalue pair or something along those lines?
Wrap the two in an object:
class WrapperObject {
public string Name { get; set; }
public object Resource { get; set; }
}
Add them to a list:
var list = new List<WrapperObject>();
list.Add(new WrapperObject() {
Name = "RedHat",
Resource = Properties.Resources.RedHat
});
..randomize:
var rnd = new Random();
list = list.OrderBy(x => rnd.Next(50)).ToList();
Any specific reason why you want them in two lists, you could just create a list of keyvaluepairs like this:
var list = new List<KeyValuePair<string, image>> ();
list.Add(new KeyValuePair<string, image>("RedHat", (Properties.Resources.RedHat)));
list.Add(new KeyValuePair<string, image>("BlueHat", (Properties.Resources.BlueHat)));
list.Add(new KeyValuePair<string, image>("YellowHat", (Properties.Resources.YellowHat)));
You could store the data in a Tuple<,> but if you had more than 2 elements its worth just creating an explicit class to store the data.
Tuple example:
List<Tuple<string, image>> list = new List<Tuple<string, image>>();
list.Add(new Tuple<string,image>("RedHat", Properties.Resources.RedHat));
// etc...
LINQ-fu version:
var rng = new Random();
var res = Enumerable.Zip(list, list2, (e1, e2) => new { e1, e2 })
.OrderBy(x => rng.Next())
.Aggregate(new { list1 = new List<string>(), list2 = new List<image>() },
(lists, next) =>
{
lists.list1.Add(next.e1);
lists.list2.Add(next.e2);
return lists;
});
list = res.list1;
list2 = res.list2;
The following code should do what you want:
var list1 = new List<string>
{
"RedHat",
"BlueHat",
"YellowHat"
};
var list2 = new List<int>
{
1,
2,
3
};
var combined = list1.Zip(list2, (a, b) => new { a, b }).Shuffle(new Random()).ToList();
list1 = combined.Select(i => i.a).ToList();
list2 = combined.Select(i => i.b).ToList();
You'll need the following extension method:
public static class ShuffleExtension
{
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
for (int i = elements.Length - 1; i >= 0; i--)
{
int swapIndex = rng.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
}
First put the corresponding elements together, then apply random order:
var rnd = new Random();
var ordered = list.Zip(list2, Tuple.Create).OrderBy(el => rnd.Next()).ToArray();
You can easily extract back the individual lists, if needed:
var ordered_list = ordered.Select(tuple => tuple.Item1).ToList();
var ordered_list2 = ordered.Select(tuple => tuple.Item2).ToList();

unequal size lists to merge

I have searched without success to a similar situation as follows.
I have two lists, list A and list B.
List A is composed of 10 objects created from ClassA which contains only strings.
List B is composed of 100 objects created from ClassB which only contains decimals.
List A is the header information.
List B is the data information.
The relationship between the two lists is:
Row 1 of list A corresponds to rows 1-10 of list B.
Row 2 of list A corresponds to rows 11-20 of list B.
Row 3 of list A corresponds to rows 21-30 of list B.
etc.........
How can I combine these two lists so that when I display them on the console the user will see a header row followed immediately by the corresponding 10 data rows.
I apologize if this has been answered before.
Ok, that should work. Let me know in case I got anything wrong.
List<ClassA> listA = GetListA()// ...
List<ClassB> listB = GetListA()// ...
if(listB.Count % listA.Count != 0)
throw new Exception("Unable to match listA to listB");
var datasPerHeader = listB.Count / listA.Count;
for(int i = 0; i < listA.Count;i++)
{
ClassA header = listA[i];
IEnumerable<ListB> datas = listB.Skip(datasPerHeader*i).Take(datasPerHeader);
Console.WriteLine(header.ToString());
foreach(var data in datas)
{
Console.WriteLine("\t{0}", data.ToString());
}
}
Here is some code that should fulfill your request - I am going to find a link for the partition extension as I can't find it in my code anymore:
void Main()
{
List<string> strings = Enumerable.Range(1,10).Select(x=>x.ToString()).ToList();
List<decimal> decimals = Enumerable.Range(1,100).Select(x=>(Decimal)x).ToList();
var detailsRows = decimals.Partition(10)
.Select((details, row) => new {HeaderRow = row, DetailsRows = details});
var headerRows = strings.Select((header, row) => new {HeaderRow = row, Header = header});
var final = headerRows.Join(detailsRows, x=>x.HeaderRow, x=>x.HeaderRow, (header, details) => new {Header = header.Header, Details = details.DetailsRows});
}
public static class Extensions
{
public static IEnumerable<List<T>> Partition<T>(this IEnumerable<T> source, Int32 size)
{
for (int i = 0; i < Math.Ceiling(source.Count() / (Double)size); i++)
yield return new List<T>(source.Skip(size * i).Take(size));
}
}
That Partition method is the one that does the grunt work...
And here is the link to the article - LINK
EDIT 2
Here is better code for the Main() method... Rushed to answer and forgot brain:
void Main()
{
List<string> strings = Enumerable.Range(1,10).Select(x=>x.ToString()).ToList();
List<decimal> decimals = Enumerable.Range(1,100).Select(x=>(Decimal)x).ToList();
var detailsRows = decimals.Partition(10);
var headerRows = strings; //just renamed for clarity from other code
var final = headerRows.Zip(detailsRows, (header, details) => new {Header = header, Details = details});
}
This should be pretty straight forward unless I'm missing something.
var grouped = ListA.Select((value, index) =>
new {
ListAItem = value,
ListBItems = ListB.Skip(index * 10).Take(10)
})
.ToList();
Returns back an anonymous type you can loop through.
foreach (var group in grouped)
{
Console.WriteLine("List A: {0}", group.Name);
foreach (var listBItem in group.ListBItems)
{
Console.WriteLine("List B: {0}", listBItem.Name);
{
}
The easiest way may be something like this:
var listA = new List<string>() { "A", "B", "C", ... }
var listB = new List<decimal>() { 1m, 2m, 3m, ... }
double ratio = ((double)listA.Count) / listB.Count;
var results =
from i in Enumerable.Range(0, listB.Count)
select new { A = listA[(int)Math.Truncate(i * ratio)], B = listB[i] };
Or in fluent syntax:
double ratio = ((double)listA.Count) / listB.Count;
var results = Enumerable.Range(0, listB.Count)
.Select(i => new { A = listA[(int)Math.Truncate(i * ratio)], B = listB[i] });
Of course if you know you will always have 10 items in listB for each item in listA, you can simplify this to:
var results =
from i in Enumerable.Range(0, listB.Count)
select new { A = listA[i / 10], B = listB[i] };
Or in fluent syntax:
var results = Enumerable.Range(0, listB.Count)
.Select(i => new { A = listA[i / 10], B = listB[i] });
This will return a result set like
{ { "A", 1 },
{ "A", 2 },
{ "A", 3 }
..,
{ "A", 10 },
{ "B", 11 },
{ "B", 12 },
{ "B", 13 },
...
{ "B", 20 },
{ "C", 21 },
...
{ "J", 100 }
}

c# linq combine 2 dictionaries

I have two Dictionary<string, Item>s. Item has a has a public property int Level.
I want to combine these two dictionaries where keys are unique and I want to be able to specify level on both.
Something like
dictionary 1 = all items that level < 10
dictionary 2 = all items level < 20
combine dictionary 2 with 1 (where Value.Level < 10)
if the Key is unique and the Value.Level < 20
I can easily do this with foreach loops. I can also do this with multiple linq queries.
However i can't seem to figure out how to make this one single linq query.
Edit- Per your request John here is the code with foreach
Dictionary<string, Dictionary<string, Item>> itemDictionary = new Dictionary<string, Dictionary<string, Item>>();
Dictionary<string, Item> items = new Dictionary<string,Item>();
if (itemDictionary.ContainsKey(comboBox.Text))
{
foreach (KeyValuePair<string, Item> kvp in itemDictionary[comboBox.Text])
{
if (!items.ContainsKey(kvp.Key) && kvp.Value.Level <= int.Parse(textBox.Text))
items.Add(kvp.Key, kvp.Value);
}
}
if (itemDictionary.ContainsKey(comboBox1.Text))
{
foreach (KeyValuePair<string, Spell> kvp in itemDictionary[comboBox1.Text])
{
if (!items.ContainsKey(kvp.Key) && kvp.Value.Level <= int.Parse(textBox1.Text))
items.Add(kvp.Key, kvp.Value);
}
}
var query = from s in items
orderby s.Value.Level
select s;
foreach (var i in query)
listBox.Items.Add(string.Format("{0} {1}", i.Value.Level, i.Key));
If you can easily do what you want in foreach loops, then you've already written the code. That tells me that the foreach code would probably be more readable and easier to maintain than a complicated LINQ query.
This sounds like the perfect case for leaving foreach loop code in place. LINQ may be newer but that doesn't always mean it is better.
Ok, that code makes it clear what you want to accomplish. So the end result should be a dictionary comprised of the items from both dictionaries that meet the specified level. If an item exists in both dictionaries than the item from the first dictionary will be preferred. While it is possible to accomplish this in a single Linq query you would end up repeating some work. Here is what I came up with, it runs in LinqPad if you want to try it out easily.
var itemsOne = new[] {
new { Name = "A", Level = 1 },
new { Name = "B", Level = 2 },
new { Name = "C", Level = 3 },
new { Name = "D", Level = 4 }
}.ToDictionary(i => i.Name, i => i);
var itemsTwo = new[] {
new { Name = "C", Level = 10 },
new { Name = "D", Level = 20 },
new { Name = "E", Level = 30 },
new { Name = "F", Level = 40 }
}.ToDictionary(i => i.Name, i => i);
var itemsOneLevel = 3;
var itemsTwoLevel = 30;
var validFromItemsOne = (from item in itemsOne
where item.Value.Level <= itemsOneLevel
select item).ToDictionary(i => i.Key, i => i.Value);
var validFromItemsTwo = from item in itemsTwo
where item.Value.Level <= itemsTwoLevel
&& !validFromItemsOne.ContainsKey(item.Key)
select item;
var items = validFromItemsOne
.Concat(validFromItemsTwo)
.ToDictionary(i => i.Key, i => i.Value);
If I undestood you correct: you want to have records that exist in both dictionaries. Anyway my example can be tailored to your particular needs.
Dictionary<string, string> d1 = new Dictionary<string, string>(), d2 = new Dictionary<string, string>();
var merged = d1
.Join(d2, d1key => d1key.Key, d2key => d2key.Key,
(i1, i2) => new { Key = i1.Key, Value1 = i1.Value, Value2 = i2.Value })
.ToDictionary(t => t.Key);
Merged will contain only items that existed in both dictionaries, and will be able to get data for each of the items like merged["key"].Value1 and merged["key"].Value2
For example I`ve used anonymous type (new {}). So you will be able to access Value1 and Value2 only in the same method, or in binding because they are dynamic anyway.
In other cases you should create a type to store combined result.
Here is an extension that I think will work for this scenario.
public static class Extensions
{
public static IDictionary<TKey, TValue> Combine<TKey, TValue>(
this IDictionary<TKey, TValue> firstSet,
IDictionary<TKey, TValue> secondSet,
Func<KeyValuePair<TKey, TValue>, bool> firstFilter,
Func<KeyValuePair<TKey, TValue>, bool> secondFilter)
{
return firstSet
.Where(firstFilter)
.Concat(secondSet.Where(secondFilter))
.GroupBy(d => d.Key)
.ToDictionary(d => d.Key, d => d.First().Value);
}
}
Usage:
var itemsOne = new[] {
new { Name = "A", Level = 1 },
new { Name = "B", Level = 2 },
new { Name = "C", Level = 3 },
new { Name = "D", Level = 4 }
}.ToDictionary(i => i.Name, i => i);
var itemsTwo = new[] {
new { Name = "C", Level = 10 },
new { Name = "D", Level = 20 },
new { Name = "E", Level = 30 },
new { Name = "F", Level = 40 }
}.ToDictionary(i => i.Name, i => i);
itemsOne
.Combine(itemsTwo,
kvp => kvp.Value.Level <= 3,
kvp => kvp.Value.Level <= 30)
.ToDictionary(d => d.Key, d=> d.Value.Level)
Result:
A 1
B 2
C 3
D 20
E 30

Categories

Resources