Compare two lists to search common items - c#

List<int> one //1, 3, 4, 6, 7
List<int> second //1, 2, 4, 5
How to get all elements from one list that are present also in second list?
In this case should be: 1, 4
I talk of course about method without foreach. Rather linq query

You can use the Intersect method.
var result = one.Intersect(second);
Example:
void Main()
{
List<int> one = new List<int>() {1, 3, 4, 6, 7};
List<int> second = new List<int>() {1, 2, 4, 5};
foreach(int r in one.Intersect(second))
Console.WriteLine(r);
}
Output:
1
4

static void Main(string[] args)
{
List<int> one = new List<int>() { 1, 3, 4, 6, 7 };
List<int> second = new List<int>() { 1, 2, 4, 5 };
var result = one.Intersect(second);
if (result.Count() > 0)
result.ToList().ForEach(t => Console.WriteLine(t));
else
Console.WriteLine("No elements is common!");
Console.ReadLine();
}

Related

Sort and slice multiple arrays [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have multiple arrays of objects T -> where T : IComparable<T>.
Each array contains unique elements. An element could be present in multiple arrays but not multiple times in the same array. I need to move all elements that are contains in all arrays to the front of the arrays (in the same order) and get the count of the elements (that are moved so i can have a slice of each array). What would be the most optimal algorithm perf/memory wise?
var a = new[] {4, 5, 6, 7, 8};
var b = new[] {7, 4, 3, 1, 2};
... (up to 8 arrays)
int length = SortAsc(a, b, ...)
// a -> {4, 7, 5, 6, 8}
// b -> {4, 7, 3, 1, 2}
// length = 2
You can use the Intersect method (in System.Linq) to get all the common items. Then you can use Union to join the intersection with the original array. Because we specify intersection.Union(array) instead of array.Union(intersection), the intersection items will appear first in the result. Also, the set operation methods (Union, Intersect, Except) will automatically remove any duplicates:
var a = new[] {4, 5, 6, 7, 8};
var b = new[] {7, 4, 3, 1, 2};
// Common items, ordered by value ascending
var intersection = a.Intersect(b).OrderBy(i => i);
// Union the intersection to put the intersected items first
// (the duplicates are removed automatcially by Union)
a = intersection.Union(a).ToArray();
b = intersection.Union(b).ToArray();
To get the intersection of multiple arrays, it would be handy to add them to a list and then we can use the Aggregate method:
var a = new[] {4, 5, 6, 7, 8};
var b = new[] {7, 4, 3, 1, 2};
var c = new[] {9, 1, 7, 4, 2};
var d = new[] {3, 1, 4, 2, 7};
var e = new[] {3, 7, 4, 1, 2};
var f = new[] {7, 4, 3, 1, 9};
var g = new[] {4, 1, 7, 9, 8};
var h = new[] {3, 2, 6, 7, 4};
var arrays = new List<int[]> {a, b, c, d, e, f, g, h};
var intersection = arrays.Aggregate((accumulation, next) =>
accumulation.Intersect(next).ToArray()).OrderBy(i => i);
Note that this is not the best performing solution, just a simple one to write. :)
Oh, and you can get the count of common items using intersection.Count().
As per I understood, I made an example in a console app:
This is the program.cs
static void Main(string[] args)
{
var a = new int[5] { 4, 5, 6, 7, 8 };
var b = new int[5] { 7, 4, 3, 1, 2 };
var c = new int[5] { 4, 2, 1, 7, 9 };
var d = new int[5] { 1, 2, 6, 4, 5 };
var leng = SortAsc(a, b, c, d);
if (leng.Arrays.Count > 0)
{
Console.WriteLine($"Length: {leng.Arrays.Count}");
foreach (var item in leng.Arrays)
{
for (int i = 0; i < item.Length; i++)
{
Console.Write($"{item[i]}");
}
Console.WriteLine();
}
}
}
This is the method:
public static ArrayKeeper SortAsc(int[] a, int[] b, int[] c, int[] d)
{
//order arrays
a = a.OrderBy(o => o).ToArray();
b = b.OrderBy(o => o).ToArray();
c = c.OrderBy(o => o).ToArray();
d = d.OrderBy(o => o).ToArray();
a = a.OrderByDescending(o => b.Contains(o)).ToArray();
b = b.OrderByDescending(o => a.Contains(o)).ToArray();
c = c.OrderByDescending(o => (a.Contains(o) && b.Contains(o))).ToArray();
d = d.OrderByDescending(o => (a.Contains(o) && b.Contains(o)) && c.Contains(o)).ToArray();
var resp = new ArrayKeeper();
resp.Arrays.Add(b);
resp.Arrays.Add(a);
resp.Arrays.Add(c);
resp.Arrays.Add(d);
return resp;
}
This is a DTO to help transport data;
public class ArrayKeeper {
public ArrayKeeper()
{
Arrays = new List<int[]>();
}
public List<int[]> Arrays { get; set; }
}
This is the result:
$ dotnet run
Length: 4
47123
47568
47129
41256
Of course it's a POC and is just one way to do something like you want using LINQ, Lists and arrays;
Also I think it will need some kind of validation to be more dynamic;

Partition lists of integers based on unique sets of numbers they contain

I have a list of integer list like -
List<List<int>> dataList = new List<List<int>> {
new List<int>{ 0, 2, 4, 7 },
new List<int>{ 1, 6, 3 },
new List<int>{ 2, 0, 7, 9 },
new List<int>{ 3, 1, 6 },
new List<int>{ 4, 0, 2 },
new List<int>{ 5, 2, 7 },
};
I want to merge all the list those have duplicates and generate a list of integer list where no values should be common in any list.
The output should be like--
0, 2, 4, 5, 7, 9
1, 3, 6
If you want one single list, then you can do this:
// flatten your list:
var newList = new List<int>();
foreach (var list in output) {
newList.AddRange(list);
}
// make sure every number is only once in that list:
newList.Distinct() // here is linq!
var output = new List<List<int>>();
output.Add(newList);

How to find first specific item and then take 3 following?

I am trying to find position in a List based on where LINQ statement and get that item and next (x) amount. Example code:
List<int> numbers = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
numbers = numbers.Where(elt => elt == 6).Take(3).ToList();
I am trying to get back a filtered list of 6,7,8. However this is not working. Am I approaching this wrong?
Thanks in advance!
You almost got it. You just need to change the Where to a SkipWhile:
numbers = numbers.SkipWhile(elt => elt != 6).Take(3).ToList();
You have to use Where() overload that takes index of item as well and then use with indexOf():
List<int> numbers = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
var result = numbers.Where((x, i) => i >= numbers.IndexOf(6)).Take(3);
Here's another approach which comes into play when it's possible that the number is not unique and you want all occurences including the two next followers:
List<int> numbers = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 6, 7, 8, 9, 6 });
numbers = Enumerable.Range(0, numbers.Count)
.Where(index => numbers[index] == 6)
.SelectMany(index => numbers.Skip(index).Take(3))
.ToList(); // 6,7,8,6,7,8,6

How to Get the longest match found in number of sets, order is important

I need to find a way to return the longest match found in number of sets/lists (values returns only once) when the order of items is important.
the list is not cyclic.
A match is a sequence of values that exists in all the lists and maintains the same order of elements in all the lists.
e.g. 1:
List<int> list1 = new List<int> { 1, 2, 3, 4, 7, 9 };
List<int> list2 = new List<int> { 1, 2, 5, 6, 3, 4, 7, 9 };
List<int> list3 = new List<int> { 1, 2, 3, 6, 8, 9 };
List<int> list4 = new List<int> { 1, 2, 5, 6, 8, 9 };
result { 1, 2 }
e.g. 2:
List<int> list1 = new List<int> { 2, 3, 6, 8, 1, 18 };
List<int> list2 = new List<int> { 2, 3, 4, 6, 8, 1, 18, 19, 17, 14 };
List<int> list3 = new List<int> { 2, 5, 6, 8, 1, 18, 16, 13, 14 };
List<int> list4 = new List<int> { 2, 6, 8, 1, 18, 19, 17, 14 };
result { 6, 8, 1, 18 }
The match doesn't have to be found at the beginning or at the end and can be on any part of any list.
I hope that I explained my problem good enough :)
Thanks!
You can build a map from pairs of ints to a count of how many of the lists they appear adjacent in.
Pseudo-code:
For each list L {
For each adjacent pair (x, y) in L {
Counts[x, y] += 1
}
}
Now you can iterate through the first list (or the shortest list), and find the longest run such that each adjacent pair (x, y) in the run with Counts[x, y] showing that the pair appears in every list.
Pseudo-code:
run = []
best_run = []
For x in L[0] {
if len(run) is zero or Counts[run[len(run)-1], x] == number of lists {
run = run + x
} else {
run = [x]
}
if run is longer than best_run {
best_run = run
}
}
This works given the assumption in the question that no integer appears twice in the same list.
This algorithm runs in O(N) time, where N is the sum of the lengths of all the lists.
Here's my approach.
First I need a way to compare lists:
public class ListCompare<T> : IEqualityComparer<List<T>>
{
public bool Equals(List<T> left, List<T> right)
{
return left.SequenceEqual(right);
}
public int GetHashCode(List<T> list)
{
return list.Aggregate(0, (a, t) => a ^ t.GetHashCode());
}
}
Next a method to produce all subsequences of a source list:
Func<List<int>, IEnumerable<List<int>>> subsequences = xs =>
from s in Enumerable.Range(0, xs.Count)
from t in Enumerable.Range(1, xs.Count - s)
select xs.Skip(s).Take(t).ToList();
Now I can create a list of lists:
var lists = new [] { list1, list2, list3, list4, };
Finally a query that pulls it all together:
var answer =
lists
.Skip(1)
.Aggregate(
subsequences(lists.First()),
(a, l) => a.Intersect(subsequences(l), new ListCompare<int>()))
.OrderByDescending(x => x.Count)
.FirstOrDefault();
Given the sample data provided in the question this produces the expected results.
First generate an ordered combination of int from the shortest list
Compare the lists other than shortest list with the combination. For easy comparison of lists I just convert to string and use string.Contains()
Return immediately if find the match as the items left are next order or the shorter one.
public static List<int> GetLongestMatch(params List<int>[] all)
{
var shortest = all.Where(i => i.Count == all.Select(j => j.Count).Min()).First();
var permutations = (from length in Enumerable.Range(1, shortest.Count)
orderby length descending
from count in Enumerable.Range(1, shortest.Count - length + 1)
select shortest.Skip(count - 1).Take(length).ToList())
.ToList();
Func<List<int>, string> stringfy = (list) => { return string.Join(",", list.Select(i => i.ToString()).ToArray()); };
foreach (var item in permutations)
{
Debug.WriteLine(string.Join(", ", item.Select(i => i.ToString()).ToArray()));
if (all.All(list => stringfy(list).Contains(stringfy(item))))
{
Debug.WriteLine("Matched, skip process and return");
return item;
}
}
return new List<int>();
}
Usage
var result = GetLongestMatch(list1, list2, list3, list4);
Result
2, 3, 6, 8, 1, 18
2, 3, 6, 8, 1
3, 6, 8, 1, 18
2, 3, 6, 8
3, 6, 8, 1
6, 8, 1, 18
Matched, skip process and return

Linq query that reduces a subset of duplicates to a single value within a larger set?

Is there a linq command that will filter out duplicates that appear in a sequence?
Example with '4':
Original { 1 2 3 4 4 4 5 6 7 4 4 4 8 9 4 4 4 }
Filtered { 1 2 3 4 5 6 7 4 8 9 4 }
Thanks.
Not really. I'd write this:
public static IEnumerable<T> RemoveDuplicates(this IEnumerable<T> sequence)
{
bool init = false;
T current = default(T);
foreach (var x in sequence)
{
if (!init || !object.Equals(current, x))
yield return x;
current = x;
init = true;
}
}
Yes there is! One-line code and one loop of the array.
int[] source = new int[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
var result = source.Where((item, index) => index + 1 == source.Length
|| item != source[index + 1]);
And according to #Hogan's advice, it can be better:
var result = source.Where((item, index) => index == 0
|| item != source[index - 1]);
More readable now i think. It means "choose the first element, and those which isn't equal to the previous one".
Similar to svick's answer, except with side effects to avoid the cons and reverse:
int[] source = new int[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
List<int> result = new List<int> { source.First() };
source.Aggregate((acc, c) =>
{
if (acc != c)
result.Add(c);
return c;
});
Edit: No longer needs the source.First() as per mquander's concern:
int[] source = new int[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
List<int> result = new List<int>();
result.Add(
source.Aggregate((acc, c) =>
{
if (acc != c)
result.Add(acc);
return c;
})
);
I think I still like Danny's solution the most.
You can use Aggregate() (although I'm not sure whether it's better than the non-LINQ solution):
var ints = new[] { 1, 2, 3, 4, 4, 4, 5, 6, 7, 4, 4, 4, 8, 9, 4, 4, 4 };
var result = ints.Aggregate(
Enumerable.Empty<int>(),
(list, i) =>
list.Any() && list.First() == i
? list
: new[] { i }.Concat(list)).Reverse();
I think it's O(n), but I'm not completely sure.
If you're using .NET 4 then you can do this using the built-in Zip method, although I'd probably prefer to use a custom extension method like the one shown in mquander's answer.
// replace "new int[1]" below with "new T[1]" depending on the type of element
var filtered = original.Zip(new int[1].Concat(original),
(l, r) => new { L = l, R = r })
.Where((x, i) => (i == 0) || !object.Equals(x.L, x.R))
.Select(x => x.L);

Categories

Resources