Easy way to GroupBySubElement on a collection in LINQ? - c#

I have a normal GroupBy operation on an enumerable:
e.GroupBy(i => i.Property)
But if i.Property is really a collection, how would I break apart the collection and use the list's elements as grouping keys?
For example let's say I have two objects (Z, Y) that each have a list:
Z: { List = { A, B, C }}
Y: { List = { B, C, D }}
Now running the GroupBySubelement(o => o.List) would not group by the list itself, but would iterate over the list and generate the following Groupings.
{A, {Z}}
{B, {Z, Y}}
{C, {Z, Y}}
{D, {Y}
Is this possible?
Thanks!

Here's some example code that achieves what you want:
//This is just temporary data. Has the similar structure to what you want.
var parts = new[]
{
new
{
Name = "X",
Property = new[] {'A', 'B', 'C'}
},
new
{
Name = "Y",
Property = new[] {'B', 'C', 'D'}
},
new
{
Name = "Z",
Property = new char[] { }
}
};
var groupedBySub = from part in parts
from sub in part.Property
group part by sub;
foreach(var group in groupedBySub)
{
Console.WriteLine("{0} - {1}", group.Key, string.Join(", ", group.Select(x => x.Name)));
}
Which outputs:
A - X
B - X, Y
C - X, Y
D - Y
You can also achieve this in the method chain fashion:
var groupedBySub = parts.SelectMany(part => part.Property, (part, sub) => new {part, sub}).GroupBy(t => t.sub, t => t.part);
If you want to capture it with the list being empty:
var groupedBySub = from part in parts
from sub in part.Property.DefaultIfEmpty()
group part by sub;
Which when substituted for the code above, gives output:
A - X
B - X, Y
C - X, Y
D - Y
- Z

This would do:
var combinations = e.SelectMany(i => i.List.Select(x => new { x, i }));
var groups = combinations.GroupBy(c => c.x, c => c.i);

Part of the problem here is that you don't have a good data structure:
var z = new List<T>(); // I'm using T here, so let's pretend this is in a generic method
var y = new List<T>();
// add a bunch of stuff
There isn't really any algorithm that can get you what you want, because the variables Z and Y are not really known to the data structure, just the comiler.
But what if you had a data structure like this:
var allOfTheLists = new Dictionary<T, List<T>>();
You could then break it out using something like this:
var explodedList = allOfTheLists.SelectMany((pair) => pair.Value.Select((item) => new { pair.Key, item}));
var grouping = explodedList.GroupBy((explodedItem) => explodedItem.item);

Related

How to get first index in list of list

How can I get the first indexes from my list of lists and get it's average value. Currently I have return value on my list:
[["1.11, 2.11, 3.11"], ["2.11, 3.11, 4.11"], ["4.11, 5.11, 6.11"]]
Here is my expected result:
var index0 = 2.44
var index1 = 3.44
var index2 = 4.44
On single list only I am using this to get the average:
var avg = myList.Select(double.Parse).Average();
Any suggestion/comments TIA.
Edit Solution because you edited.
String[][] TValue = new String[][]
{
new String[] {"1.11", "2.11", "3.11" },
new String[] {"2.11", "3.11", "4.11" },
new String[] {"4.11", "5.11", "6.11" }
};
Console.WriteLine("Avg[0] => {0:F2}", TValue.Select(x => double.Parse(x[0])).Average());
Console.WriteLine("Avg[1] => {0:F2}", TValue.Select(x => double.Parse(x[1])).Average());
Console.WriteLine("Avg[2] => {0:F2}", TValue.Select(x => double.Parse(x[2])).Average());
this is what you expected.
hope this work.
It seems that you need to get avg based on columns index instead of rows then .Zip will be one option for you,
Suppose your list of list of string is,
var myList = new List<List<string>>
{
new List<string> { "1.11, 2.11, 3.11" }, //<= Notice here single item in list with comma(,) separated
new List<string> { "2.11, 3.11, 4.11" },
new List<string> { "4.11, 5.11, 6.11" }
};
So you need to first split your string in inner list with comma(,) to get each item as separate string,
var list = myList
.SelectMany(x => x
.Select(y => y.Split(',')
.Select(z => z.Trim())
.ToList()))
.ToList();
Then you can make .Zip on all above 3 list by
var results = list[0]
.Zip(list[1], (a, b) => double.Parse(a) + double.Parse(b))
.Zip(list[2], (x, y) => (x + double.Parse(y)) / 3)
.ToList();
//------------------Print the result---------------
foreach (var item in results)
{
Console.WriteLine(Math.Round(item, 2));
}
How .Zip works?
Lets take all columns value on index 0.
a = "1.11"
b = "2.11"
Then 1st Zip result will be =>
double.Parse(a) + double.Parse(b)
= 1.11 + 2.11
= 3.22
So for 2nd .Zip the x now be the result of the above 1st .Zip that means
x = 3.22
y = "4.11"
Then 2nd Zip result will be =>
(x + double.Parse(y)) / 3
= (3.22 + 4.11) / 3
= 2.44
So for Average of your values at column index 0 => 2.44
In above,
list[0] : 1st list in your list of list of string.
list[1] : 2nd list in your list of list of string.
list[2] : 3rd list in your list of list of string.
Output: (From Console)
You can zip all three lists
using zipThree from How to combine more than two generic lists in C# Zip?
using System;
using System.Collections.Generic;
using System.Linq;
public static class MyFunkyExtensions
{
public static IEnumerable<TResult> ZipThree<T1, T2, T3, TResult>(
this IEnumerable<T1> source,
IEnumerable<T2> second,
IEnumerable<T3> third,
Func<T1, T2, T3, TResult> func)
{
using (var e1 = source.GetEnumerator())
using (var e2 = second.GetEnumerator())
using (var e3 = third.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())
yield return func(e1.Current, e2.Current, e3.Current);
}
}
}
class MainClass {
public static void Main (string[] args) {
var list = new List<List<double>> { new List<double> {1.11,2.11,3.11}, new List<double> {2.11,3.11,4.11}, new List<double> {4.11,5.11,6.11} };
var a = list[0].ZipThree(list[1], list[2], (x, y, z) => (x + y + z) / 3);
Console.WriteLine(
string.Join(",",
a.Select(s => s.ToString())));
}
}
And it returns
2.44333, 3.443333, 4.44333
I'm assuming all the inner lists have the same length, and you want to count the average of the corresponding indices (i.e. index0 is average of 0th value from each list, index1 is average of the 1st value etc.).
In such case to obtain the averages for each index you use the following code:
int listLength = myList.First().Length; // Length for an array
// Count for a List<T>
var averages = Enumerable.Range(0, listLength).Select(
index => myList.Average(innerList => double.Parse(innerList[index]))
).ToList();
You could do so using Linq.
Updated based on comment
var list = new [] { "1.11, 2.11, 3.11" , "2.11,3.11, 4.11" , "4.11,5.11,6.11" };
var collection = list.Select(x => x.Split(',').Select(c => double.Parse(c)).ToList()).ToList();
var result = collection.First()
.Select((dummy, i) =>
collection.Average(inner => inner[i]));
Output
2.44
3.44
4.44

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" }

List to Dictionary with incremental keys in one line LINQ statement

I have this list:
var items = new List<string>() { "Hello", "I am a value", "Bye" };
I want it to convert it to a dictionary with the following structure:
var dic = new Dictionary<int, string>()
{
{ 1, "Hello" },
{ 2, "I am a value" },
{ 3, "Bye" }
};
As you can see, the dictionary keys are just incremental values, but they should also reflect the positions of each element in the list.
I am looking for a one-line LINQ statement. Something like this:
var dic = items.ToDictionary(i => **Specify incremental key or get element index**, i => i);
You can do that by using the overload of Enumerable.Select which passes the index of the element:
var dic = items.Select((val, index) => new { Index = index, Value = val})
.ToDictionary(i => i.Index, i => i.Value);
static void Main(string[] args)
{
var items = new List<string>() { "Hello", "I am a value", "Bye" };
int i = 1;
var dict = items.ToDictionary(A => i++, A => A);
foreach (var v in dict)
{
Console.WriteLine(v.Key + " " + v.Value);
}
Console.ReadLine();
}
Output
1 Hello
2 I am a value
3 Bye
EDIT: Out of curosity i did a performance test with a list of 3 million strings.
1st Place: Simple For loop to add items to a dictionary using the loop count as the key value. (Time: 00:00:00.2494029)
2nd Place: This answer using a integer variable outside of LINQ. Time(00:00:00.2931745)
3rd Place: Yuval Itzchakov's Answer doing it all on a single line. Time (00:00:00.7308006)
var items = new List<string>() { "Hello", "I am a value", "Bye" };
solution #1:
var dic2 = items.Select((item, index) => new { index, item })
.ToDictionary(x => x.item, x => x.index);
solution #2:
int counter = 0;
var dic = items.ToDictionary(x => x, z => counter++);

Given collection of strings, count number of times each word appears in List<T>

Input 1: List<string>, e.g:
"hello", "world", "stack", "overflow".
Input 2: List<Foo> (two properties, string a, string b), e.g:
Foo 1:
a: "Hello there!"
b: string.Empty
Foo 2:
a: "I love Stack Overflow"
b: "It's the best site ever!"
So i want to end up with a Dictionary<string,int>. The word, and the number of times it appears in the List<Foo>, either in the a or the b field.
Current first-pass/top of my head code, which is far too slow:
var occurences = new Dictionary<string, int>();
foreach (var word in uniqueWords /* input1 */)
{
var aOccurances = foos.Count(x => !string.IsNullOrEmpty(x.a) && x.a.Contains(word));
var bOccurances = foos.Count(x => !string.IsNullOrEmpty(x.b) && x.b.Contains(word));
occurences.Add(word, aOccurances + bOccurances);
}
Roughly:
Build a dictionary (occurrences) from the first input, optionally with a case-insensitive comparer.
For each Foo in the second input, use RegEx to split a and b into words.
For each word, check if the key exists in occurrences. If it exists, increment and update the value in the dictionary.
You could try concating the two strings a + b. Then doing a regex to pull out all the words into a collection. Then finally indexing that using a group by query.
For example
void Main()
{
var a = "Hello there!";
var b = "It's the best site ever!";
var ab = a + " " + b;
var matches = Regex.Matches(ab, "[A-Za-z]+");
var occurences = from x in matches.OfType<System.Text.RegularExpressions.Match>()
let word = x.Value.ToLowerInvariant()
group word by word into g
select new { Word = g.Key, Count = g.Count() };
var result = occurences.ToDictionary(x => x.Word, x => x.Count);
Console.WriteLine(result);
}
Example with some changes suggested...
Edit. Just reread the requirement....kinda strange but hey...
void Main()
{
var counts = GetCount(new [] {
"Hello there!",
"It's the best site ever!"
});
Console.WriteLine(counts);
}
public IDictionary<string, int> GetCount(IEnumerable<Foo> inputs)
{
var allWords = from input in inputs
let matchesA = Regex.Matches(input.A, "[A-Za-z']+").OfType<System.Text.RegularExpressions.Match>()
let matchesB = Regex.Matches(input.B, "[A-Za-z']+").OfType<System.Text.RegularExpressions.Match>()
from x in matchesA.Concat(matchesB)
select x.Value;
var occurences = allWords.GroupBy(x => x, (x, y) => new{Key = x, Count = y.Count()}, StringComparer.OrdinalIgnoreCase);
var result = occurences.ToDictionary(x => x.Key, x => x.Count, StringComparer.OrdinalIgnoreCase);
return result;
}

Categories

Resources