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();
Related
Given the following query.
var query = files
.SelectMany(file => File.ReadAllLines(file))
.Where(_ => !_.StartsWith("*"))
.Select(line => new {
Order = line.Substring(32, 7),
Delta = line.Substring(40, 3),
Line = new String[] { line }
});
This clearly produces a list of objects with the properties Order: string, Delta: string and Line: string[]
I have a list of items that looks like this.
{ 1, 'A', {'line1'} },
{ 1, 'A', {'line2'} },
{ 2, 'B', {'line3'} },
{ 1, 'B', {'line4 } }
is it possible to use the Linq Aggregate or similar functional construct to collect all the adjacent Order and Delta combinations together whilst accumulating the lines.
So that the aggregate is a list of items containing all it's 'lines'
{ 1, 'A', {'line1', 'line2'} }
{ 2, 'B', {'line3'} }
{ 1, 'B', {'line4'} }
Since aggregation iterates sequentially it should be possible to collect all the adjacent lines that have the same fields equal.
It's easy to do in a loop, but I am trying to do it with a set of lambdas.
You'll need the following variation of GroupBy:
public static class EnumerableExtensions
{
public class AdjacentGrouping<K, T> : List<T>, IGrouping<K, T>
{
public AdjacentGrouping(K key) { Key = key; }
public K Key { get; private set; }
}
public static IEnumerable<IGrouping<K, T>> GroupByAdjacent<T, K>(
this IEnumerable<T> sequence, Func<T, K> keySelector)
{
using (var it = sequence.GetEnumerator())
{
if (!it.MoveNext())
yield break;
T curr = it.Current;
K currKey = keySelector(curr);
var currentCluster = new AdjacentGrouping<K, T>(currKey) { curr };
while (it.MoveNext())
{
curr = it.Current;
currKey = keySelector(curr);
if (!EqualityComparer<K>.Default.Equals(currKey, currentCluster.Key))
{
// start a new cluster
yield return currentCluster;
currentCluster = new AdjacentGrouping<K, T>(currKey);
}
currentCluster.Add(curr);
};
// currentCluster is never empty
yield return currentCluster;
}
}
}
Having this adjacent grouping, your code can be the same as in Chris's answer:
var query = files
.SelectMany(file => File.ReadAllLines(file))
.Where(_ => !_.StartsWith("*"))
.Select(line => new
{
Order = line.Substring(32, 7),
Delta = line.Substring(40, 3),
Line = new String[] { line }
})
.GroupByAdjacent(o => new { o.Order, o.Delta })
.Select(g => new { g.Key.Order, g.Key.Delta, Lines = g.Select(o => o.Line).ToList() });
Disclaimer: the function GroupByAdjacent is from my own pet project and not copied from anywhere.
Note: Does not group items by adjacency
You can produce the desired results using a simple GroupBy combined with a SelectMany:
var query = new[] {
new { order = 1, delta = "A", line = new[] { "line1" } },
new { order = 1, delta = "A", line = new[] { "line2" } },
new { order = 2, delta = "B", line = new[] { "line3" } },
new { order = 1, delta = "B", line = new[] { "line4" } },
};
query
.GroupBy(q => new { q.order, q.delta })
.Select(q => new {
order = q.Key.order,
delta = q.Key.delta,
lines = q.SelectMany(l => l.line)
});
Produces:
My Dictionary<int, List<MyObject>> result has..
Key Value
1 {"Chicago", 100}
1 {"Newyork", 200}
2 {"Minneapolis", 300}
want to convert it to List<List<string>> in below format.
{"Index", "City","Value"},
{1, "Chicago", 100},
{1, "Newyork", 200}
{2, "Minneapolis", 300}
This is what I have achieved so far
var list = result.Select(rec => new
{
Index = rec.Key,
City = rec.Value.Select(rec1 => rec1.City),
Value = rec.Value.Select(rec1 => rec1.Value)
}).ToList();
What I am getting is this..
{"Index", "City", "Value"},
{1, System.Linq.Enumerable.WhereSelectListIterator<MyObject, string>, System.Linq.Enumerable.WhereSelectListIterator<MyObject, int>},
{1, System.Linq.Enumerable.WhereSelectListIterator<MyObject, string>, System.Linq.Enumerable.WhereSelectListIterator<MyObject, int>},
{2, System.Linq.Enumerable.WhereSelectListIterator<MyObject, string>, System.Linq.Enumerable.WhereSelectListIterator<MyObject, int>}
May be I am missing Where condition. Please suggest.
public class MyObject
{
public MyObject(){}
public string City{get;set;}
public int Value{get;set;}
}
This is what you need:
var Result = result.SelectMany(r => r.Value.Select(x => new[] { r.Key.ToString(), x.City, x.Value.ToString() }.ToList()));
To prepend column names as the first element of the outer list:
Result.Insert(0, {"Index", "City","Value"}.ToList());
Do you need the output like this?
I have a solution for you. Try it.
Dictionary<int, List<MyObject>> result = new Dictionary<int, List<MyObject>>();
result.Add(1, new List<MyObject>() { new MyObject() { City = "Chicago", Value = 100 }, new MyObject() { City = "Newyork", Value = 200 } });
result.Add(2, new List<MyObject>() { new MyObject() { City = "Minneapolis", Value = 300 } });
var resultYouWant = result.SelectMany(p => p.Value.Select(a => new { Index = p.Key, a.City, a.Value })).ToList();
below code is work for you but not getting what is your usecase.
var list = result.Select(rec => new
{
Index = rec.Key,
City = rec.Value.City),
Value = rec.Value.Value)
}).ToList();
City = rec.Value.Select(rec1 => rec1.City),
That is creating an IEnumerable, not a string. Which is why you get System.Linq.Enumerable.WhereSelectListIterator out of it.
You may be better off using for loops here.
foreach(var kvp in result)
foreach(var value in kvp)
//Create string here and add it to your list.
I have a Dictionary that looks like the following, with the key being an Integer and the value being a List of strings:
var x = new Dictionary<int, List<string>>;
I would like to see if any of those Lists match each other (without being in order) so that I can group them together in a role.
The final solution will look like
var y = new Dictionary<string, List<int>>
Where the List<int> is the keys from var x. The string key will be a machine generated string such as a guid, etc.
You can map all values to their keys and then group them by value and then apply ToDictionary, for expected result.
var data = new Dictionary<int, List<string>>
{
{ 1, new List<string> { "Adam", "Lucie" } },
{ 2, new List<string> { "Adam", "Hannah" } },
{ 3, new List<string> { "John", "Rachel" } },
{ 4, new List<string> { "Bill", "Hannah" } },
};
var result = data.SelectMany(p => p.Value.Select(v => new {Key = p.Key, Value = v}))
.GroupBy(o => o.Value)
.ToDictionary(g => g.Key, g => g.Select(v => v.Key));
foreach (var keyValues in result)
{
Console.WriteLine(keyValues.Key + ": " + string.Join(", ", keyValues.Value));
}
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 }
);
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