Combine two list of lists in LINQ - c#

I have two lists of the following class:
public class KeyValues
{
public int Key { get;set;}
public List<double> DoubleList { get;set;}
}
var list1 = new List<KeyValues>();
list1.Add (new KeyValues(){Key = 33, DoubleList = new List<double>(){2.3,2.4,2.5}});
list1.Add (new KeyValues(){Key = 34, DoubleList = new List<double>(){3.3,3.4,3.5}});
list1.Add (new KeyValues(){Key = 35, DoubleList = new List<double>(){4.3,4.4,4.5}});
var list2 = new List<KeyValues>();
list2.Add (new KeyValues(){Key = 33, DoubleList = new List<double>(){20.3,20.4}});
list2.Add (new KeyValues(){Key = 34, DoubleList = new List<double>(){30.3,30.4}});
list2.Add (new KeyValues(){Key = 35, DoubleList = new List<double>(){40.3,40.4}});
I would like to combine those into a new list by mathing the keys and combining the sub lists. So the result should be:
list3 = [
[33, {2.3,2.4,2.5,20.3,20.4}]
[34, {3.3,3.4,3.5,30.3,30.4}]
[35, {4.3,4.4,4.5,40.3,40.4}]
]
Of course I can iterate over the keys, but isn't there a much better (faster) linq solution for this?

Using Concat and GroupBy methods can produce the expected result (after making a code compile, of course)
var result = list1.Concat(list2).GroupBy(kv => kv.Key, kv => kv.DoubleList)
.Select(g => new KeyValues { Key = g.Key, DoubleList = g.SelectMany(i => i).ToList() });

You can concatenate the two lists together and then use GroupBy, and then Select to project the output into the desired format. For example:
var list3 = list1.Concat(list2)
.GroupBy(x => x.Key)
.Select(x => new KeyValue
{
Key = x.Key,
DoubleList = x.SelectMany(x => x.DoubleList).ToList()
})
.ToList();

You're example is not syntactically correct:
Should be like this:
var list1 = new List<KeyValue>();
list1.Add(new KeyValue { Key = 33, DoubleList = new List<double>() { 2.3, 2.4, 2.5 } });
list1.Add(new KeyValue { Key = 34, DoubleList = new List<double>() { 3.3, 3.4, 3.5 } });
list1.Add(new KeyValue { Key = 35, DoubleList = new List<double>() { 4.3, 4.4, 4.5 } });
var list2 = new List<KeyValue>();
list2.Add(new KeyValue { Key = 33, DoubleList = new List<double>() { 20.3, 20.4 } });
list2.Add(new KeyValue { Key = 34, DoubleList = new List<double>() { 30.3, 30.4 } });
list2.Add(new KeyValue { Key = 35, DoubleList = new List<double>() { 40.3, 40.4 } });
list1.Zip(list2, (first, second) => (first.Key, first.DoubleList.Concat(second.DoubleList));
If the lists have the same element keys in the same order you can do this:
list1.Zip(list2, (first, second) =>
(first.Key, first.DoubleList.Concat(second.DoubleList)));

My first suggestion here is to use Dicitionary<int, List<double>> instead of creating a class KeyValues or if you want specifically to use Key-Value pair, then .net have that too, it's called: KeyValuePair.
Just in case if you are interested, here is the way to convert your class to Dictionary:
var list3 = list1.Concat(list2)
.ToDictionary(item => item.Key);
Note: this will actually fail because of key duplications, so in case if you don't want that to happen, make sure that Keys are different.
Update:
Or you can use Lookup<TKey, TElement> to make it work even with duplicate keys:
var list3 = list1
.Concat(list2)
.ToLookup(group => group.Key, group => group.DoubleList)
.ToDictionary(group => group.Key, group => group.First());
One suggestion: you can use a collection initializer instead of adding elements one by one:
var list1 = new List<KeyValues>
{
new KeyValues {Key = 33, DoubleList = new List<double>() {2.3, 2.4, 2.5}},
new KeyValues {Key = 34, DoubleList = new List<double>() {3.3, 3.4, 3.5}},
new KeyValues {Key = 35, DoubleList = new List<double>() {4.3, 4.4, 4.5}}
};
More information:
Dictionary<TKey,TValue> (official documentation here)
KeyValuePair VS DictionaryEntry (great post here)

Related

C# Linq query for matching lists exactly

I have a list of IDs and collection of objects, each with its own child list of IDs. I want a linq query that will return any of the objects for which its child list exactly matches the list of IDs. Here is what I have, and I think it works -- but it's ugly, and requires two steps. Is there a better way to do this?
var inputIDs = new List<int> {1, 5, 8, 10, 12};
var object1 = new {name = "object1", IDs = new List<int> {9, 10, 11, 12}};
var object2 = new {name = "object2", IDs = new List<int> {1, 5, 8, 12}};
var object3 = new {name = "object3", IDs = new List<int> {1, 5, 8, 10, 12}};
var objects = new List<object> {object1, object2, object3};
var candidateObjects = objects.Where(o => o.IDs.All(i => inputIDs.Contains(i)));
var exactMatches = candidateObjects.Where(o => inputIDs.All(i => o.IDs.Contains(i)));
// exactMatches should only contain object3
I think what you want is Enumerable.SequenceEqual:
var inputIDs = new List<int> { 1, 5, 8, 10, 12 };
var object1 = new { name = "object1", IDs = new List<int> { 9, 10, 11, 12 } };
var object2 = new { name = "object2", IDs = new List<int> { 1, 5, 8, 12 } };
var object3 = new { name = "object3", IDs = new List<int> { 1, 5, 8, 10, 12 } };
var objects = new[] { object1, object2, object3 }.ToList();
var exactMatches = objects.Where(o => o.IDs.SequenceEqual(inputIDs));
Per #AnupSharma, you will need to sort if your sequences could be out of order (and now performance rears its head):
var inputIDs = new[] { 1, 5, 8, 10, 12 }.OrderBy(i => i).ToList();
//...
var exactMatches = objects.Where(o => o.IDs.OrderBy(i => i).SequenceEqual(inputIDs));
A (slight) performance improvement can be had by testing for Count since we know the sources are Lists:
var exactMatches = objects.Where(o => o.IDs.Count == inputIDs.Count && o.IDs.OrderBy(i => i).SequenceEqual(inputIDs));
A similar but faster solution would be using Except with a count check:
objects.Where(o => !o.IDs.Except(inputIDs).Any() && o.IDs.Count == inputIDs.Count);
I think adding a check for count of elements will be enough for your example
var result = objects.Where(o => o.IDs.Count == inputIDs.Count)
.Where(o => o.IDs.All(id => inputIDs.Contains(id)));
You can do some optiomization by using HashSet
var inputs =new HashSet<int>(inputsIDs);
var result = objects.Where(o => o.IDs.Count == inputIDs.Count)
.Where(o => o.IDs.All(id => inputs.Contains(id)));

Linq SelectMany project to index

I have 2 different collections.
pseudo code:
// index string by int : Dictionary<int, string>
index = { 0, "a" }, { 1, "b" }, { 2, "c" }
// data : Dictionary<string, List<Data>>
data = {"a", { "data00", "data01"..}},
{"b", {"data20", "data21", "data22"...}},
{"c", {"data4",...}}...
I want project int index to data string value and
var result = data.SelectMany ... new { IntIndex, DataValue }
I need to flatten lists into one sequence and pair the Data values with int index using string index.
I have slightly update types and values (your Dictionary contains duplicated keys and index wasn't specified) but it shouldn't be a problem. You may modify function easily for your data types.
var index = new List<Tuple<int, string>> {Tuple.Create(0, "a"), Tuple.Create(1, "b")};
var data = new Dictionary<string, IEnumerable<string>>()
{
{"a", new[] {"data00", "data01"}},
{"b", new[] {"data20", "data21", "data22"}},
{"c", new[] {"data4"}}
};
var result = index
.Join(data, x => x.Item2, y => y.Key, (x,y) => new KeyValuePair<int, IEnumerable<string>>(x.Item1, y.Value))
.SelectMany(x => x.Value, (x, y) => new KeyValuePair<int, string>(x.Key, y));
Assuming your duplication of keys was accidental, you could try this
Dictionary<int, List<Data>> intData = new Dictionary<int, List<Data>>();
foreach (var iVal in index)
{
List<Data> tmpList = new List<Data>();
if (data.TryGetValue(iVal.Value, out tmpList))
{
intData.Add(iVal.Key, tmpList);
}
}
If you can have duplicate keys then a dictionary is not the right structure.
var index = new List<Tuple<int, string>> {Tuple.Create(0, "a"), Tuple.Create(1, "b")};
var data = new Dictionary<string, IEnumerable<string>>()
{
{"a", new[] {"data00", "data01"}},
{"b", new[] {"data20", "data21", "data22"}},
{"c", new[] {"data4"}}
};
var res =
(from i in index
join d in data on i.Item2 equals d.Key
select new {Key = i.Item1, Value = d.Value})
.SelectMany(x => x.Value, (x, v) => new {x.Key, Value = v});

"Grouping" dictionary by value

I have a dictionary: Dictionary<int,int>. I want to get new dictionary where keys of original dictionary represent as List<int>. This is what I mean:
var prices = new Dictionary<int,int>();
The prices contain the following data:
1 100
2 200
3 100
4 300
I want to get the IList<Dictionary<int,List<int>>>:
int List<int>
100 1,3
200 2
300 4
How can I do this?
var prices = new Dictionary<int, int>();
prices.Add(1, 100);
prices.Add(2, 200);
prices.Add(3, 100);
prices.Add(4, 300);
Dictionary<int,List<int>> test =
prices.GroupBy(r=> r.Value)
.ToDictionary(t=> t.Key, t=> t.Select(r=> r.Key).ToList());
You can use GroupBy.
Dictionary<int,List<int>> groups =
prices.GroupBy(x => x.Value)
.ToDictionary(x => x.Key, x => x.Select(i => i.Key).ToList());
Here is my reply. When the dictionaries get large, you will likely find the GroupBy() extension methods less efficient than you would like, as they provide many guarantees that you don't need, such as retaining order.
public static class DictionaryExtensions
{
public static IDictionary<TValue,List<TKey>> Reverse<TKey,TValue>(this IDictionary<TKey,TValue> src)
{
var result = new Dictionary<TValue,List<TKey>>();
foreach (var pair in src)
{
List<TKey> keyList;
if (!result.TryGetValue(pair.Value, out keyList))
{
keyList = new List<TKey>();
result[pair.Value] = keyList;
}
keyList.Add(pair.Key);
}
return result;
}
}
And an example to use in LinqPad:
void Main()
{
var prices = new Dictionary<int, int>();
prices.Add(1, 100);
prices.Add(2, 200);
prices.Add(3, 100);
prices.Add(4, 300);
// Dump method is provided by LinqPad.
prices.Reverse().Dump();
}
You can use GroupBy followed by the Func<TSource, TKey>, Func<TSource, TElement> overload of Enumerable.ToDictionary:
var d = prices.GroupBy(x => x.Value).ToDictionary(x => x.Key, x => x.ToList());
You can use Lookup instead.
var prices = new Dictionary<int, int> { {1, 100}, { 2, 200 }, { 3, 100 }, { 4, 300 } };
ILookup<int, int> groups = prices.ToLookup(x => x.Value, y => y.Key);
foreach (var group in groups)
{
foreach (var item in group)
{
Console.WriteLine(item);
}
}
In particular case, when we use the .NET framework 2.0, we can do as follows:
var prices = new Dictionary<int, int>();
prices.Add(1, 100);
prices.Add(2, 200);
prices.Add(3, 100);
prices.Add(4, 300);
Dictionary<int, List<int>> grouping = new Dictionary<int, List<int>>();
var enumerator = prices.GetEnumerator();
while (enumerator.MoveNext())
{
var pair = enumerator.Current;
if (!grouping.ContainsKey(pair.Value))
grouping[pair.Value] = new List<int>();
grouping[pair.Value].Add(pair.Key);
}

Sort Descending directly

I have
List<string> strs;
double[] values;
where the values array contains the value of each of the string in strs list
Say strs={"abc","def","ghi"}
and values={3,1,2}
this means "abc" has value 3 and so on.
I wish to sort strs and values ordered by values, such that it becomes
strs={"def","ghi","abc"}
values={3,2,1}
I am using
string[] strsArr = strs.ToArray();
Array.Sort(values, strsArr);//1. sort it ascendingly
strs = strsArr.ToList();
Array.Reverse(strs);//2. reverse it
Is there a way I can sort it in descending sequence directly without 2 phases?
You can use a Dictionary and Linq to solve this.
var dict = new Dictionary<string, double>()
{
{"abc",3},
{"def",1},
{"ghi",2}
};
var sorted = dict.OrderByDescending(g => g.Value)
.Select(g => g.Key)
.ToArray();
Note, unless you have a ToArray() at the end the sorting will be deferred till later enumerated and may accidentally be enumerated multiple times.
How about this:
var strs = new [] { "abc", "def", "ghi", };
var values = new [] { 3, 1, 2, };
strs =
strs
.Zip(values, (s, v) => new { s, v })
.OrderByDescending(sv => sv.v)
.Select(sv => sv.s)
.ToArray();
try use dictionary:
Dictionary<string, double> dictionary = new Dictionary<string, double>();
dictionary.Add("abc", 3);
dictionary.Add("def", 1);
dictionary.Add("ghi", 2);
var sortedDict = dictionary.OrderByDescending(x => x.Value);
double[] values = sortedDict.Select(x => x.Value).ToArray();
List<string> strs = sortedDict.Select(x => x.Key).ToList();

name of assoc-diff in C#

What do you call this method, (is it available in .net?)
var list1 = new List<int>() { 1, 2, 2, 3, 4 };
var list2 = new List<int>() { 1, 2, 3};
var results = list1.diff(list2);
results:
{ 2, 4 }
The closest thing built in is the Except LINQ operator.
Produces the set difference of two sequences.
Though with your example it will result in:
{ 4 }
I don't believe there is a direct analogue to what you want.
You actually need a multiset implementation. Although there is no multiset out of the box in BCL, there are some ideas here and in the linked question.
Or you can actually implement one by yourself, it's not so complicated:
class Multiset<K> // maybe implement IEnumerable?
{
Dictionary<K, int> arities = new Dictionary<K, int>();
...
Multiset<K> Except(Multiset<K> other)
{
foreach (var k in arities.keys)
{
int arity = arities[k];
if (other.Contains(k))
arity -= other.Arity(k);
if (arity > 0)
result.Add(k, arity);
}
return result;
}
}
This exactly return what you want, You can refactor it in a Extension Method:
var results = list1.GroupBy(p => p).Select(p => new { item = p.Key, count = p.Count() })
.Concat(list2.GroupBy(p => p).Select(p => new { item = p.Key, count = -p.Count() }))
.GroupBy(p => p.item).Select(p => new { item = p.Key, count = p.Sum(q => q.count) })
.Where(p => p.count > 0)
.SelectMany(p => Enumerable.Repeat(p.item, p.count));
Like this: (see oded's post for a linq to msdn)
int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 };
int[] numbersB = { 1, 3, 5, 7, 8 };
IEnumerable<int> aOnlyNumbers = numbersA.Except(numbersB);

Categories