How to count list items grouped by an embedded list - c#

I have a class like
class MyClass
{
public DateTime Date { get; set; }
public List<int> IdList { get; set; }
public MyClass(DateTime initDate)
{
Date = initDate;
IdList = new List<int>();
}
}
and need to count the number of entries in a List<MyClass>, grouped by each int in IdList.
I have experimented with various Linq constructs, but I cannot get anything to work. Here is what I have so far:
List<MyClass> myc = new List<MyClass>();
myc.Add(new MyClass(new DateTime(2016, 1, 1)) { IdList = new List<int> { 1, 2 } });
myc.Add(new MyClass(new DateTime(2016, 1, 2)) { IdList = new List<int> { 1, 3 } });
myc.Add(new MyClass(new DateTime(2016, 1, 3)) { IdList = new List<int> { 1, 4 } });
myc.Add(new MyClass(new DateTime(2016, 1, 4)) { IdList = new List<int> { 5, 6 } });
myc.Add(new MyClass(new DateTime(2016, 1, 5)) { IdList = new List<int> { 2, 3 } });
var grouped = from p in myc
group p by p.IdList into g
select new { Id = g.Key, Count = g.Count() };
foreach (var x in grouped)
{
Console.WriteLine("ID: {0}, Count: {1}", x.Id, x.Count);
}
// Expecting output like:
// ID: 1, Count: 3
// ID: 2, Count : 2
// etc.
If there was a single int Id property in MyClass, it would be straightforward, but I cannot work out how to use the List<int>. Is there any alternative to writing nested loops and populating a Dictionary? Thanks for any help.

You can use SelectMany
var grouped = myc.SelectMany(x => x.IdList).GroupBy(x => x);
foreach (var i in g)
{
Console.WriteLine(string.Format("Id: {0}, Count: {1}", i.Key,i.Count()));
}
This should give you the output you're looking for.

I don't know if I've understand your requeriment correctly. But try this and let me know:
var groupedIds = myc.SelectMany(x => x.IdList.Select(i => i))
.GroupBy(x => x)
.ToList();
The full fiddle here
And here SelectMany documentation so you know what this code means.
Hope this helps!

Related

Add index position Value of an array of array using Linq

We can do sum using arr.Sum() function. But if it is an array of arrays. How will we add all values.
suppose data is
Array/List is [[1,2,3],[3,4,5],[5,4,3]]
how will you get s1 , sum of all first index value, s2 , sum of second index value and so on using LINQ.
If you want to sum up columns' values with a help of Linq:
int[][] source = new int[][] {
new int[] { 1, 2, 3},
new int[] { 3, 4, 5},
new int[] { 5, 4, 3},
};
int maxCol = source.Max(item => item.Length);
var colsSum = Enumerable
.Range(0, maxCol)
.Select(index => source.Sum(item => item.Length > index ? item[index] : 0))
.ToArray(); // let's meaterialize into an array
Test:
Console.Write(string.Join(", ", colsSum));
Outcome:
9, 10, 11
Summing up lines' values is easier:
// [6, 12, 12]
var linesSum = source
.Select(item => item.Sum())
.ToArray();
If you want total sum:
// 30
var total = source
.Select(item => item.Sum())
.Sum();
or
// 30
var total = source
.SelectMany(item => item)
.Sum();
Use combination of Aggregate and Zip
var arrays = new[]
{
new[] { 1, 2, 3 },
new[] { 3, 4, 5 },
new[] { 5, 4, 3 }
};
var result =
arrays.Aggregate(Enumerable.Repeat(0, 3),
(total, array) => total.Zip(array, (sum, current) => sum + current));
// result = { 9, 10, 11 }
Enumerable<T>.Zip executes provided function with items of same index.
A possible LINQ based approach (which will handle variable number of columns in each row):
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public class Program
{
private static IEnumerable<int> GetTotalsPerColumn(int[][] inputData)
{
var data = inputData.SelectMany(z =>
{
return z.Select((item, index) => new { item, index });
})
.GroupBy(z => z.index)
.OrderBy(z => z.Key)
.Select(y => y.Select(z => z.item).Sum()
);
return data;
}
static void Main(string[] args)
{
var inputData = new[] {
new[] { 1, 2, 3, 5},
new[] { 3, 4, 5, 6},
new[] { 5, 4, 3},
};
var values = GetTotalsPerColumn(inputData);
foreach (var value in values)
{
Console.WriteLine(value);
}
Console.ReadLine();
}
}
}
If you are happy to avoid LINQ, this is another approach you could consider.
GetTotalsPerColumn populates a Dictionary where the key is the column number, and the value is the sum.
using System;
using System.Collections.Generic;
namespace Test
{
public class Program
{
static void Main(string[] args)
{
var inputData = new[] {
new[] { 1, 2, 3, 5},
new[] { 3, 4, 5, 6},
new[] { 5, 4, 3},
};
var values = GetTotalsPerColumn(inputData);
foreach (var value in values)
{
Console.WriteLine(value.Key + " - " + value.Value);
}
Console.ReadLine();
}
private static Dictionary<int, int> GetTotalsPerColumn(int[][] inputData)
{
var values = new Dictionary<int, int>();
foreach (var line in inputData)
{
for (int i = 0; i < line.Length; i++)
{
int tempValue;
values.TryGetValue(i, out tempValue);
tempValue += line[i];
values[i] = tempValue;
}
}
return values;
}
}
}

LINQ: Separating single list to multiple lists

I have a single array with these entries:
{1, 1, 2, 2, 3,3,3, 4}
and i want to transform them to ( 3 lists in this case ):
{1,2,3,4}
{1,2,3}
{3}
Is there any way to do this with LINQ or SQL? I guess there's a mathematical term for this operation, which I don't know unfortunately...
Or do I have to do it with loops?
=======
EDIT: I can't really describe the logic, so here are more examples.. It more or less loops multiple times over the array and takes every number once ( but every number only once per round ) until there are no numbers left
{1, 1, 2, 2, 3,3,3, 4, 5}
would be
{1,2,3,4,5}
{1,2,3}
{3}
or
{1, 1, 2, 2,2, 3,3,3, 4, 5}
would be
{1,2,3,4,5}
{1,2,3}
{2,3}
private IEnumerable<List<int>> FooSplit(IEnumerable<int> items)
{
List<int> source = new List<int>(items);
while (source.Any())
{
var result = source.Distinct().ToList();
yield return result;
result.ForEach(item => source.Remove(item));
}
}
Usage:
int[] items = { 1, 1, 2, 2, 3, 3, 3, 4 };
foreach(var subList in FooSplit(items))
{
// here you have your three sublists
}
Here is another solution, which is less readable but it will have better performance:
private IEnumerable<IEnumerable<int>> FooSplit(IEnumerable<int> items)
{
var groups = items.GroupBy(i => i).Select(g => g.ToList()).ToList();
while (groups.Count > 0)
{
yield return groups.Select( g =>
{ var i = g[0]; g.RemoveAt(g.Count - 1); return i; });
groups.RemoveAll(g => g.Count == 0);
}
}
this does the job:
static void Main(string[] args)
{
int[] numbers = {1, 1, 2, 2, 3, 3, 3, 3, 4, 5, 5};
List<int> nums = new List<int>(numbers.Length);
nums.AddRange(numbers);
while (nums.Count > 0)
{
int[] n = nums.Distinct().ToArray();
for (int i = 0; i < n.Count(); i++)
{
Console.Write("{0}\t", n[i]);
nums.Remove(n[i]);
}
Console.WriteLine();
}
Console.Read();
}
Here's an alternative console app:
class Program
{
class Freq
{
public int Num { get; set; }
public int Count { get; set; }
}
static void Main(string[] args)
{
var nums = new[] { 1, 1, 2, 2, 3, 3, 3, 4 };
var groups = nums.GroupBy(i => i).Select(g => new Freq { Num = g.Key, Count = g.Count() }).ToList();
while (groups.Any(g => g.Count > 0))
{
var list = groups.Where(g => g.Count > 0).Select(g => g.Num).ToList();
list.ForEach(li => groups.First(g => g.Num == li).Count--);
Console.WriteLine(String.Join(",", list));
}
Console.ReadKey();
}
}

Is there a more concise way to express this foreach loop?

I have 2 lists that I need to consolidate. List 1 has only the dates, and List 2 may have the time element as well:
var List1 = new[] {
new ListType{ val = new DateTime(2012, 1, 1)},
new ListType{ val = new DateTime(2012, 1, 2)}
};
List2 = new[] { new ListType{ val = new DateTime(2012, 1, 1, 5, 0, 0)} };
FinalList = new[] {
new ListType{ val = new DateTime(2012, 1, 1, 5, 0, 0)},
new ListType{ val = new DateTime(2012, 1, 2)}
};
The way I'm going about this is:
foreach (var l in List1) {
var match = List2.FirstOrDefault(q => q.val.Date == l.val);
if (match == null) continue;
l.val = match.val;
}
Is there a better way than iterating through List1, using FirstOrDefault and then reassigning the val? It works, so this is just more a curiosity if Linq has a more elegant way (i.e. I am missing something obvious).
Thanks
You can use Enumerable.Union with a custom IEqualityComparer<ListType>:
class ListType
{
public DateTime val { get; set; }
public class DateComparer : IEqualityComparer<ListType>
{
public bool Equals(ListType x, ListType y)
{
if (ReferenceEquals(x, y))
return true;
else if (x == null || y == null)
return false;
return x.val.Date == y.val.Date;
}
public int GetHashCode(ListType obj)
{
return obj.val.Date.GetHashCode();
}
}
}
and then ...
var finalList = List2.Union(List1, new ListType.DateComparer());
I wouldn't get rid of the loop, but for efficiency I'd build up a dictionary mapping dates to the first matchnig time:
var dateToTime = List2
.GroupBy(d => d.Date)
.ToDictionary(g => g.Key, g => g.First());
foreach (var l in List1)
{
DateTime match;
if (dateToTime.TryGetValue(l.val, out match))
l.val = match.val;
}
LINQ is made for querying items rather than updating items - if you need to update items, use something non-LINQ like a foreach loop. That said, if you want to generate a new list from the items in the first list, the following is the equivalent of your code:
var newList = List1.Select(l => new ListType { val =
dateToTime.ContainsKey(l.val) ? dateToTime[l.val] : l.val }).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);

C# - How to get complement of two lists without identifiers

I have two lists of the same type. That type does not have an identifier or any other guaranteed way to programatically distinguish.
List A: {1, 2, 2, 3, 5, 8, 8, 8}
List B: {1, 3, 5, 8}
I want the items from A that are not in B.
Desired Result: {2, 2, 8, 8}
If the types had identifiers, I could use a statement like the following...
var result = listA
.Where(a => listB.Where(b => b.Id == a.Id).Count() == 0)
.ToList();
So far, the only way I can do this is with a loop where I add each item the number of times it doesn't appear in the original list.
foreach (var val in listB.Select(b => b.val).Distinct())
{
var countA = listA.Where(a => a.val == val).Count();
var countB = listB.Where(b => b.val == val).Count();
var item = listA.Where(a => a.val == val).FirstOrDefault();
for (int i=0; i<countA-countB; i++)
result.Add(item);
}
Is there a cleaner way to achieve this?
EDIT:
Here is a simplified version of the object in the lists. It's coming from a Web service that's hitting another system.
public class myObject
{
public DateTime SomeDate { get; set; }
public decimal SomeNumber; { get; set; }
public bool IsSomething { get; set; }
public string SomeString { get; set; }
}
The data I am receiving has the same values for SomeDate/SomeString and repeated values for SomeNumber and IsSomething. Two objects might have equal properties, but I need to treat them as distinct objects.
try this:
var listA = new List<Int32> {1, 2, 2, 3, 5, 8, 8, 8};
var listB = new List<Int32> {1, 3, 5, 8};
var listResult = new List<Int32>(listA);
foreach(var itemB in listB)
{
listResult.Remove(itemB);
}
What am I missing?
class Program
{
static void Main(string[] args)
{
List<int> a = new List<int>();
a.Add(1);
a.Add(2);
a.Add(2);
a.Add(3);
a.Add(5);
a.Add(8);
a.Add(8);
a.Add(8);
List<int> b = new List<int>();
b.Add(1);
b.Add(3);
b.Add(5);
b.Add(8);
foreach (int x in b)
a.Remove(x);
foreach (int x in a)
Console.WriteLine(x);
Console.ReadKey(false);
}
}
Are the objects same instances in both lists? If so you can use .Where(a => listB.Where(b => b == a).Count() == 0)
Or
.Where(a => !listB.Any(b => b == a))
You could sort both lists and then iterate through them both at the same time.
public IEnumerable<int> GetComplement(IEnumerable<int> a, IEnumerable<int> b)
{
var listA = a.ToList();
listA.Sort();
var listB = b.ToList();
listB.Sort();
int i=0,j=0;
while( i < listA.Count && j < listB.Count )
{
if(listA[i] > listB[j]) {yield return listB[j];j++;}
else if (listA[i] < listB[j]) {yield return listA[i]; i++; }
else {i++;j++;}
}
while(i < listA.Count)
{
yield return listA[i];
i++;
}
while(j < listB.Count)
{
yield return listB[j];
j++;
}
}
I don't know if this is "cleaner", but it should be more performant on large sets of data.
This is a bit nasty but it does what you want. Not sure about performance though.
var a = new List<int> { 1, 2, 2, 3, 5, 8, 8, 8 };
var b = new List<int> { 1, 3, 5, 8 };
var c = from x in a.Distinct()
let a_count = a.Count(el => el == x)
let b_count = b.Count(el => el == x)
from val in Enumerable.Repeat (x, a_count - b_count)
select val;
Why don't you implement your own equality comparer for your myObject:
public class YourTypeEqualityComparer : IEqualityComparer<myObject>
{
public bool Equals(myObject x, myObject y)
public int GetHashCode(myObject obj)
}
and then use it like this:
var list1 = new List<myObj>();
var list2 = new List<myObj>()
list1.RemoveAll(i =>
list2.Contains(list1),
new YourTypeEqualityComparer()
);
now list1 contains result.

Categories