Get the symmetric difference from generic lists - c#

I have 2 separate List and I need to compare the two and get everything but the intersection of the two lists. How can I do this (C#)?

If you mean the set of everything but the intersection (symmetric difference) you can try:
var set = new HashSet<Type>(list1);
set.SymmetricExceptWith(list2);

You can use Except to get everything but the intersection of the two lists.
var differences = listA.Except(listB).Union(listB.Except(listA));
If you want to get everything but the union:
var allButUnion = new List<MyClass>();
(The union is everything in both lists - everything but the union is the empty set...)

Do you mean everything that's only in one list or the other? How about:
var allButIntersection = a.Union(b).Except(a.Intersect(b));
That's likely to be somewhat inefficient, but it fairly simply indicates what you mean (assuming I've interpreted you correctly, of course).

Here is a generic Extension method. Rosetta Code uses Concat, and Djeefther Souza says it's more efficient.
public static class LINQSetExtensions
{
// Made aware of the name for this from Swift
// https://stackoverflow.com/questions/1683147/get-the-symmetric-difference-from-generic-lists
// Generic implementation adapted from https://www.rosettacode.org/wiki/Symmetric_difference
public static IEnumerable<T> SymmetricDifference<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
// I've used Union in the past, but I suppose Concat works.
// No idea if they perform differently.
return first.Except(second).Concat(second.Except(first));
}
}
I haven't actually benchmarked it. I think it would depend on how Union vs. Concat are implemented. In my dreamworld, .NET uses a different algorithm depending on data type or set size, though for IEnumerable it can't determine set size in advance.
Also, you can pretty much ignore my answer - Jon Skeet says that the HashSet method "Excellent - that looks like the best way of doing it to me."

Something like this?
String[] one = new String[] { "Merry", "Metal", "Median", "Medium", "Malfunction", "Mean", "Measure", "Melt", "Merit", "Metaphysical", "Mental", "Menial", "Mend", "Find" };
String[] two = new String[] { "Merry", "Metal", "Find", "Puncture", "Revise", "Clamp", "Menial" };
List<String> tmp = one.Except(two).ToList();
tmp.AddRange(two.Except(one));
String[] result = tmp.ToArray();

var theUnion = list1.Concat(list2);
var theIntersection = list1.Intersect(list2);
var theSymmetricDifference = theUnion.Except(theIntersection);

Use Except:
List<int> l1 = new List<int>(new[] { 1, 2, 3, 4 });
List<int> l2 = new List<int>(new[] { 2, 4 });
var l3 = l1.Except(l2);

Related

Using Linq Except with two lists of int arrays

Is it possible to use except with two lists of int arrays, like so:
List<int[]> a = new List<int[]>(){ new int[]{3,4,5}, new int[]{7,8,9}, new int[]{10,11,12} };
List<int[]> b = new List<int[]>(){ new int[]{6,7,9}, new int[]{3,4,5}, new int[]{10,41,12} };
var c = a.Except(b);
and exepecting {3,4,5} to be absent of the enumerable c? Of course I tried and this one is not working. Is there a solution as efficient as Except? Or even better, faster?
In .NET, arrays are only equal to another if they are the exact same array object. So two distinct arrays which have the same contents are not considered equal:
int[] x = new int[] { 1, 2 };
int[] y = new int[] { 1, 2 };
Console.WriteLine(x == y); // false
In order to check the equality based on the contents, you can use Enumerable.SequenceEqual:
Console.WriteLine(x.SequenceEqual(y)); // true
Of course that doesn’t help you directly when trying to use Enumerable.Except, since by default that will use the default equality comparer which only checks for equality (and since every array is inequal to every other array except itself…).
So the solution would be to use the other overload, and provide a custom IEqualityComparer which compares the arrays based on their content.
public class IntArrayEqualityComparer : IEqualityComparer<int[]>
{
public bool Equals(int[] a, int[] b)
{
return a.SequenceEqual(b);
}
public int GetHashCode(int[] a)
{
return a.Sum();
}
}
Unfortunately, just delegating to SequenceEqual is not enough. We also have to provide a GetHashCode implementation for this to work. As a simple solution, we can use the sum of the numbers in the array here. Usually, we would want to provide a strong hash function, which tells a lot about the contents, but since we are only using this hash function for the Except call, we can use something simple here. (In general, we would also want to avoid creating a hash value from a mutable object)
And when using that equality comparer, we correctly filter out the duplicate arrays:
var c = a.Except(b, new IntArrayEqualityComparer());
That's because default EqualityComparer for int array returns false for to arrays with same values:
int[] a1 = { 1, 2, 3 };
int[] a2 = { 1, 2, 3 };
var ec = EqualityComparer<int[]>.Default;
Console.WriteLine(ec.Equals(a1, a2));//result is false
You can fix it by implementing your own EqualityComparer and passing its instance to Except method (see documentation).
You can also read about arrays comparison in C# here.

How avoid duplication in a list

I'm trying to get a distinct list to my view.I need to select records from a list randomly and put it in to another list.The following code works..But it contain duplication records..How can I overcome this problem?
Note: the variable "budget" is a parameter passed in to the controller and "model1" is a List of PlanObjectsViewModel
int count = 0;
foreach (var item in model1) { count++; }
List<PlanObjectsViewModel> result = new List<PlanObjectsViewModel>();
Random rand = new Random();
double? temp=0;
while(budget>temp)
{
int randi = rand.Next(0, count);
var nthItem = model1.OrderBy(p => p.Id).Skip(randi).First();
temp += nthItem.Price;
if (!result.Contains(nthItem)) // Think this is the wrong point
{
result.Add(nthItem);
}
}
Use a HashSet<PlanObjectsViewModel>
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
// Input array that contains three duplicate strings.
string[] array1 = { "cat", "dog", "cat", "leopard", "tiger", "cat" };
// Display the array.
Console.WriteLine(string.Join(",", array1));
// Use HashSet constructor to ensure unique strings.
var hash = new HashSet<string>(array1);
// Convert to array of strings again.
string[] array2 = hash.ToArray();
// Display the resulting array.
Console.WriteLine(string.Join(",", array2));
}
}
Output:
cat,dog,cat,leopard,tiger,cat
cat,dog,leopard,tiger
there are two ways to do this, use a hashset instead of list for your result, or use Distinct()
HashSet<PlanObjectsViewModel> result
or
return result.Distinct();
You will have have to implement the Equals() method for this to work with objects, a which point your current code should work too.
Actually you have made it the correct way. For me it looks like you didnt implemented Equals and GetHashCode which are used by List.Contains to compare objects. Well basically GetHashCode is not mandatory but its a good design if you implemented the one to implement the other one.
But ofcourse you can use HashSet as pointed in the other answeres.

Sort one List<> based on another

Say I have
List<int> ages = new List<int>() { 8, 5, 3, 9, 2, 1, 7 };
List<int> marks = new List<int>() { 12, 17, 08, 15, 19, 02, 11 };
I can sort my marks by ages like this:
while (true)
{
bool swapped = false;
for (int i = 0; i < ages.Count - 1; i++)
if (ages[i] > ages[i + 1])
{
int tmp = ages[i];
ages[i] = ages[i + 1];
ages[i + 1] = tmp;
tmp = marks[i];
marks[i] = marks[i + 1];
marks[i + 1] = tmp;
swapped = true;
}
if (!swapped)
break;
}
Now I want to put this into a function that accepts any two lists. The first parameter will be the reference list, the numerical or comparable list. The second parameter will be the list containing the data.
For example:
public static void Sort<T>(List<T> RefList, List<T> DataList)
{
// sorting logic here...
}
There are a few problems:
First of all, T is almost certainly not the same type in RefList and DataList. RefList might be dates, integers, or doubles; whereas DataList is free to be absolutely anything. I need to be able to receive two, arbitrary generic types.
Secondly, I cannot seem to use the > operator with the T in this line:
if (ages[i] > ages[i + 1])
Perhaps my whole approach is wrong.
By the way, I have read responses to similar questions that suggest that the two lists should be combined into a single list of a compound data type. This isn't practical at all for my application. All I want to do is write a static function that somehow sorts one list based on the elements of another.
To sort one list the way you want you actually need to somehow keep references from items in first list to they weight/keys in the second list. No existing methods do that as you can't easily associate metadata with arbitrary values (i.e. if first list is list of int as in your case there is nothing to map to keys in second list). Your only reasonable option is to sort 2 lists at the same time and make association by index - again no existing classes to help.
It may be much easier to use solution that you reject. I.e. simply Zip and OrderBy, than recreate first list:
ages = ages
.Zip(marks, (a,m)=> new {age = a; mark = m;})
.OrderBy(v => v.mark)
.Select(v=>v.age)
.ToList();
Note (courtesy of phoog): if you need to do this type of sorting with Array there is Array.Sort that allows exactly this operatiion (see phoog's answer for details).
There's no framework method to do this with List<T>, but if you don't mind putting the data into two arrays, you can use one of the Array.Sort() overloads that takes two arrays as arguments. The first array is the keys, and the second is the values, so your code might look like this (leaving aside the step of getting arrays from the lists):
Array.Sort(ages, marks);
The specifics of getting the values into arrays and then back into lists would depend, among other things, on whether you need to end up with the same list sorted appropriately or whether it's okay to return a new list with the data in the desired order.
Use:
public static void Sort<TR, TD>(IList<TR> refList, IList<TD> dataList)
where TR : System.IComparable<TR>
where TD : System.IComparable<TD>
{
...
}
and then use:
refList[i].CompareTo(refList[i+1])
instead of the operators.
.Net numbers already implement IComparable, and you can use overloads that allow you to specify a different IComparable.
If I understand "I can sort my marks by ages like this:" properly,
I would like to suggest the below to eliminate much confusion.
struct Student{
int age;
int marks;
};
List<Student> students = {{8,12}, ...};
Now you can sort according to age and marks is accordingly sorted automatically.
If it is not possible, you need to fix the code as below.
First of all, T is almost certainly not the same type in RefList and DataList.
Then you need 2 parameters T1, T2. Just T implies the types are the same.
public static void Sort<RefType, DataType>(List<RefType> RefList, List<DataType> DataList)
{
You can also zip the two lists together as suggested by Mechanical Snail and explained in Looping through 2 Lists at once

Removing one items from an array and moving the others backwards

I'll just go straight to the point. I want to move the items in an array in a uniform difference, let's say I have this.
string[] fruits = { "Banana", "Apple", "Watermelon", "Pear", "Mango" };
For example, let's say I want to remove the "Apple" so I'll do this.
fruits[1] = "";
Now all that left are:
{ "Banana", "", "Watermelon", "Pear", "Mango" }
How do I really remove the Apple part and get only:
{ "Banana", "Watermelon", "Pear", "Mango" }
Note that the index of all the items from "Watermelon" until the end of the array moves 1 backward. Any ideas?
The List class is the right one for you. It provides a method Remove which automatically moves the following elements backwards.
If you really want to use Arrays, you can use Linq to filter your list and convert to array:
string[] fruits = { "Banana", "Apple", "Watermelon", "Pear", "Mango" };
fruits = fruits.Where(f => f != "Apple").ToArray();
If you're not required to use an array, look at the List class. A list allows items to be added and removed.
Similar to Wouter's answer, if you want to remove by item index rather than item value, you could do:
fruits = fruits.Where((s, i) => i != 1).ToArray();
You can do something like this:
for( int i = 1; i + 1 < fruits.Length; i++ )
fruits[i] = fruits[i + 1];
fruits = System.Array.Resize( fruits, fruits.Length - 1 );
If you do not care about the order of the fruit in the array, a smarter way to do it is as follows:
fruits[1] = fruits[fruits.Length - 1];
fruits = System.Array.Resize( fruits, fruits.Length - 1 );
I think one of the most useful things a new programmer can do is study and understand the various collection types.
While I think the List option that others have mentioned is probably what you are looking for, it's worth looking at a LinkedList class if you are doing a lot of insertions and deletions and not a lot of looking up by index.
This is an example of how I used lists and arrays to remove an item from an array. Note I also show you how to use linq to search an array full of bad names to remove. Hope this helps someone.
public static void CheckBadNames(ref string[] parts)
{
string[] BadName = new string[] {"LIFE", "ESTATE" ,"(",")","-","*","AN","LIFETIME","INTREST","MARRIED",
"UNMARRIED","MARRIED/UNMARRIED","SINGLE","W/","/W","THE","ET",
"ALS","AS", "TENANT" };
List<string> list = new List<string>(BadName); //convert array to list
foreach(string part in list)
{
if (BadName.Any(s => part.ToUpper().Contains(s)))
{
list.Remove(part);
}
}
parts = list.ToArray(); // convert list back to array
}
As a beginner 3 years ago, I started making the software that I'm still working on today. I used an array for 'PartyMembers' of the game, and I'm basically today regretting it and having to spend a ton of time converting all this hard coded $#!t into a list.
Case in point, just use Lists if you can, arrays a nightmare in comparison.

Concatenating two lists of different types with LINQ

Is it possible to concat two list that are of different types?
string[] left = { "A", "B", "C" };
int[] right = { 1, 2, 3 };
var result = left.Concat(right);
The code above obciously has a type error. It works if the types match (eg. both are ints or strings).
pom
You can box it.
var result = left.Cast<object>().Concat(right.Cast<object>());
result will be IEnumerable<object>.
Then to unbox it, you can use OfType<T>().
var myStrings = result.OfType<string>();
var myInts = result.OfType<int>();
You could cast both to a common base type (in this case object), or you could convert the types of one of the lists so you can combine them, either:
right.Select(i = i.ToString())
or
left.Select(s => int.Parse(s)) // Careful of exceptions here!
Only way I know to make this work would be:
var result = left.Concat(right.Select(i = i.ToString()));

Categories

Resources