Intersect a collection of collections in LINQ - c#

I've got a list of lists which I want to intersect:
List<List<int>> input = new List<List<int>>();
input.Add(new List<int>() { 1, 2, 4, 5, 8 });
input.Add(new List<int>() { 3, 4, 5 });
input.Add(new List<int>() { 1, 4, 5, 6 });
Output should be:
{ 4, 5 }
How can this be accomplished in a terse fashion?

var result = input.Cast<IEnumerable<int>>().Aggregate((x, y) => x.Intersect(y))

Related

Frequency that elements appear in an array. Code works but looking for a better answer

I'm looking for a more efficient and easier way to count the number of elements in an array and how frequently they appear. GroupBy is the obvious answer but then I'd like to generate a result that's the same dimensions as the original array.
I'm writing this in C# and want to leverage lambda expressions. My code works but there has to be a better way to do something this simple.
var testarray = new int[10]
{ 3, 3, 4, 1, 2, 5, 3, 1, 2, 5 };
var groups = testarray.GroupBy(p => p)
.Select(group => new
{
TestKey = group.Key,
Count = group.Count()
});
var final = testarray.Join(groups,
src => src,
dest => dest.TestKey,
(src, dest) => dest.Count
).ToArray();
Input:
{ 3, 3, 4, 1, 2, 5, 3, 1, 2, 5 }
Expected and actual results:
{3, 3, 1, 2, 2, 2, 3, 2, 2, 2}
I would use GroupBy to group the items store the counts in a Dictionary, then construct the final array by looking up each element in the dictionary to retrieve its count.
var testarray = new int[10] { 3, 3, 4, 1, 2, 5, 3, 1, 2, 5 };
var dict = testarray.GroupBy(item => item).ToDictionary(item => item.Key, item => item.Count());
var final = testarray.Select(item => dict[item]).ToArray();
testarray 3, 3, 4, 1, 2, 5, 3, 1, 2, 5
final 3, 3, 1, 2, 2, 2, 3, 2, 2, 2
Executing GroupBy effectively creates an internal Dictionary (actually, a Lookup) and then you are breaking it back down in order to create the final Dictionary.
That seems wasteful to me. It may be more efficient to use ToLookup, which returns the Lookup, and then get the counts:
var counts = testarray.ToLookup(n => n);
var final = testarray.Select(t => ((ICollection<int>)counts[t]).Count).ToArray();

LINQ Union with duplicates

Let's say we have the following three lists:
{ 1, 2, 2, 3 }
{ 2, 3, 3, 4 }
{ 2, 3, 4, 5, 5, 5 }
How can we then convert the above to a list having each item repeated the maximum number of times it's found in a list.i.e.,
{1, 2, 2 (Found twice in list 1), 3, 3 (Twice in list 2), 4, 5, 5, 5 (Thrice in list 3)}
I can achieve the above through loops, however, I am looking for a LINQ method that might already be there.
The question is similar to list union with duplicates in python
Linq in one line
int[][] items = { new[]{ 1, 2, 2, 3 }, new[] { 2, 3, 3, 4 }, new[] { 2, 3, 4, 5, 5, 5 } };
var result = items.SelectMany(x => x.GroupBy(y => y)).GroupBy(x => x.Key).Select(x => x.OrderByDescending(y => y.Count()).First()).SelectMany(x => x);
https://dotnetfiddle.net/kZhseg
Here you go:
var xs = new [] { 1, 2, 2, 3 };
var ys = new [] { 2, 3, 3, 4 };
var zs = new [] { 2, 3, 4, 5, 5, 5 };
var result =
xs
.ToLookup(x => x)
.Concat(ys.ToLookup(x => x))
.Concat(zs.ToLookup(x => x))
.GroupBy(x => x.Key)
.Select(x => new { x.Key, count = x.Max(y => y.Count()) })
.SelectMany(x => Enumerable.Repeat(x.Key, x.count));
It gives the result you want.

Find common items in multiple lists in C# linq

I searched, but I found only answers which related to two lists. But what about when they are more than two?
List 1 = 1,2,3,4,5
List 2 = 6,7,8,9,1
List 3 = 3,6,9,2,0,1
List 4 = 1,2,9,0,5
List 5 = 1,7,8,6,5,4
List 6 = 1
List 7 =
How to get the common items? as you can see one of them is empty, so the common will be empty, but I need to skip empty lists.
var data = new List<List<int>> {
new List<int> {1, 2, 3, 4, 5},
new List<int> {6, 7, 2, 8, 9, 1},
new List<int> {3, 6, 9, 2, 0, 1},
new List<int> {1, 2, 9, 0, 5},
new List<int> {1, 7, 8, 6, 2, 5, 4},
new List<int> {1, 7, 2}
};
List<int> res = data
.Aggregate<IEnumerable<int>>((a, b) => a.Intersect(b))
.ToList();
The type of Aggregate is explicitly given, otherwise aggregation of two Lists would have to be List too. It can be easily adapted to run in parallel:
List<int> res = data
.AsParallel<IEnumerable<int>>()
.Aggregate((a, b) => a.Intersect(b))
.ToList();
EDIT
Except... it does not run in parallel. The problem is operations on IEnumerable are deferred, so even if they are logically merged in parallel context, the actual merging occurs in the ToList(), which is single threaded. For parallel execution it would be better to leave IEnumerable and return to the Lists:
List<int> res = data
.AsParallel()
.Aggregate((a, b) => a.Intersect(b).ToList());
You can chain Intersect:
List<int> List1 = new List<int> {1, 2, 3, 4, 5};
List<int> List2 = new List<int> { 6, 7, 8, 9, 1 };
List<int> List3 = new List<int> { 3, 6, 9, 2, 0, 1 };
List<int> List4 = new List<int> { 1, 2, 9, 0, 5 };
List<int> List5 = new List<int> { 1, 7, 8, 6, 5, 4 };
List<int> List6 = new List<int> { 1 };
List<int> common = List1
.Intersect(List2)
.Intersect(List3)
.Intersect(List4)
.Intersect(List5)
.Intersect(List6)
.ToList();
var data = new [] {
new List<int> {1, 2, 3, 4, 5},
new List<int> {6, 7, 8, 9, 1},
new List<int> {3, 6, 9, 2, 0, 1},
new List<int> {1, 2, 9, 0, 5},
new List<int> {1, 7, 8, 6, 5, 4},
new List<int> {1},
new List<int> {},
null
};
IEnumerable<int> temp = null;
foreach (var arr in data)
if (arr != null && arr.Count != 0)
temp = temp == null ? arr : arr.Intersect(temp);
One way is to use a HashSet. You can put the items of the first collection in the hash, then iterate each collection after the first and create an new hash that you add items from the current collection to if it's in the hash. At the end you assign that common hash set to the overall one and break if it's every empty. At the end you just return the overall hash set.
public IEnumerable<T> CommonItems<T>(IEnumerable<IEnumerable<T>> collections)
{
if(collections == null)
throw new ArgumentNullException(nameof(collections));
using(var enumerator = collections.GetEnumerator())
{
if(!enumerator.MoveNext())
return Enumerable<T>.Empty();
var overall = new HashSet<T>(enumerator.Current);
while(enumerator.MoveNext())
{
var common = new HashSet<T>();
foreach(var item in enumerator.Current)
{
if(hash.Contains(item))
common.Add(item);
}
overall = common;
if(overall.Count == 0)
break;
}
return overall;
}
}

Sort 2D array based on user provided indices

In python there is a functionality (numpy.take) to sort arrays within an array, for example if I have an array (3x3):
a = [[1, 2, 3],[7,9,10],[3, 5,6]]
and I have an array of set indices
indices = [2, 0, 1]
the result shall be
array([[ 3, 5, 6], [ 1, 2, 3], [ 7, 9, 10]]).
Are there any direct approach methods/ functions as these in C# where I can pass in a jagged array and produce the same output?
Not directly, but you can achieve the same thing with Linq
var a = new[] { new[] { 1, 2, 3 }, new[] { 7, 9, 10 }, new[] { 3, 5, 6 } };
var indices = new [] { 2, 0, 1 };
var sorted = indices.Select(i => a[i]).ToArray();
foreach(var s in sorted) Console.WriteLine(string.Join(", ", s));
Note this does not check that your indices are all in range.
You can do it easily with LINQ:
var a = new[] { new[] { 1, 2, 3 }, new[] { 7, 9, 10 }, new[] { 3, 5, 6 } };
var indices = new[] { 2, 0, 1};
var result = indices
.Select(i => a[i])
.ToArray();
Or .ToList() if you prefer lists.
There is also the Array.Sort(keys, values) - MSDN
var a = new[]
{
new[] {1, 2, 3},
new[] {7, 9, 10},
new[] {3, 5, 6}
};
var indices = new[] {2, 0, 1};
var sortedArray = a.SortEx(indices);
Where SortEx is
public static class Extensions
{
public static T[][] SortEx<T>(this T[][] source, int[] indices)
{
return indices.Select(index => source[index]).ToArray();
}
}
This assumes that the all the indices in the indices array are not out of bound in a.

Add item to a Jagged array

I have already looked at some array topics but I am still stumped.
I wish to add a new line to my jagged array - and am strugigng to get the syntax right..
int[][] intJaggedArray = new int[7][];
intJaggedArray[0] = new int[3] { 1, 1, 1 };
intJaggedArray[1] = new int[3] { 2, 2, 2 };
intJaggedArray[2] = new int[3] { 3, 3, 3 };
intJaggedArray[3] = new int[3] { 4, 4, 4 };
intJaggedArray[4] = new int[3] { 5, 5, 5 };
intJaggedArray[5] = new int[3] { 6, 6, 6 };
intJaggedArray[6] = new int[3] { 7, 7, 7 };
So now if i want to add
intJaggedArray[0] = new int[3] { 1, 1, 2 };
so the array ends up being as shown below how do I acheive it - thanks in advance - A noob from England. (And a big thanks in advance)
intJaggedArray[0] = new int[3] { 1, 1, 1 };
intJaggedArray[0] = new int[3] { 1, 1, 2 };
intJaggedArray[1] = new int[3] { 2, 2, 2 };
intJaggedArray[2] = new int[3] { 3, 3, 3 };
intJaggedArray[3] = new int[3] { 4, 4, 4 };
intJaggedArray[4] = new int[3] { 5, 5, 5 };
intJaggedArray[5] = new int[3] { 6, 6, 6 };
intJaggedArray[6] = new int[3] { 7, 7, 7 };
What do you want to do? Insert a line between 0 and 1? Or replace the existing line 0?
Your line :
intJaggedArray[0] = new int[3] { 1, 1, 2 };
simply replaces the existing line 0.
You can't insert a line in an array. To do so, use a list instead:
List<int[]> myList = new List<int[]>();
myList.Add(new int[] {...});
myList.Add(new int[] {...});
myList.Add(new int[] {...});
...
myList.Insert(1, new int[] {...});
Or if you want to replace the existing line, then simply:
You might want to create a collection or a List<int[]>
Then you could insert an item into it at a certain index.
List<int[]> x = new List<int[]>();
x.Insert(3, new int[3] { 1, 2, 3 });
If you want the initial list to be of a variable length, you cannot use an array. Use a List instead.
This should work:
List<int[]> intJaggedList = new List<int[]>();
intJaggedList.Add( new int[3] { 1, 1, 1 } );
intJAggedList.Add( new int[3] { 2, 2, 2 } );
...
Then to insert your new array:
intJaggedList.Insert( 1, new int[3] { 1, 1, 2 } );

Categories