Сравните два объекта List <T> для равенства, игнорируя порядок

Еще один вопрос, сравнивающий список.

List<MyType> list1;
List<MyType> list2;

Мне нужно проверить, что оба они имеют одинаковые элементы, независимо от их положения в списке. Каждый объект MyType может отображаться несколько раз в списке. Есть ли встроенная функция, которая проверяет это? Что делать, если я гарантирую, что каждый элемент появляется только один раз в списке?

EDIT: Ребята благодарны за ответы, но я забыл что-то добавить, количество вхождений каждого элемента должно быть одинаковым в обоих списках.

Ответ 1

Если вы хотите, чтобы они были действительно равными (т.е. одни и те же элементы и одинаковое количество каждого элемента), я считаю, что самым простым решением является сортировка перед сравнением:

Enumerable.SequenceEqual(list1.OrderBy(t => t), list2.OrderBy(t => t))

Edit:

Вот решение, которое работает немного лучше (примерно в десять раз быстрее) и требует только IEquatable, а не IComparable:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2) {
  var cnt = new Dictionary<T, int>();
  foreach (T s in list1) {
    if (cnt.ContainsKey(s)) {
      cnt[s]++;
    } else {
      cnt.Add(s, 1);
    }
  }
  foreach (T s in list2) {
    if (cnt.ContainsKey(s)) {
      cnt[s]--;
    } else {
      return false;
    }
  }
  return cnt.Values.All(c => c == 0);
}

Изменить 2:

Чтобы обрабатывать любой тип данных в качестве ключа (например, типы с нулевым значением, как указал Фрэнк Цаннабити), вы можете сделать версию, которая принимает comparer для словаря:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer) {
  var cnt = new Dictionary<T, int>(comparer);
  ...

Ответ 2

Как написано, этот вопрос неоднозначен. Утверждение:

... оба они имеют одинаковые элементы, независимо от их положения в списке. Каждый объект MyType может появляться несколько раз в списке.

не указывает, хотите ли вы убедиться, что два списка имеют один и тот же набор объектов или один и тот же отдельный набор.

Если вы хотите, чтобы коллекции имели точно такой же набор элементов независимо от порядка, вы можете использовать:

// lists should have same count of items, and set difference must be empty
var areEquivalent = (list1.Count == list2.Count) && !list1.Except(list2).Any();

Если вы хотите, чтобы две коллекции имели одинаковый набор элементов (где дубликаты игнорируются), вы можете использовать:

// check that [(A-B) Union (B-A)] is empty
var areEquivalent = !list1.Except(list2).Union( list2.Except(list1) ).Any();

Использование заданных операций (Intersect, Union, Except) более эффективно, чем использование методов типа Contains. На мой взгляд, он также лучше выражает ожидания вашего запроса.

EDIT: Теперь, когда вы уточнили свой вопрос, я могу сказать, что вы хотите использовать первую форму - поскольку дубликаты имеют значение. Вот простой пример, демонстрирующий, что вы получаете нужный результат:

var a = new[] {1, 2, 3, 4, 4, 3, 1, 1, 2};
var b = new[] { 4, 3, 2, 3, 1, 1, 1, 4, 2 };

// result below should be true, since the two sets are equivalent...
var areEquivalent = (a.Count() == b.Count()) && !a.Except(b).Any(); 

Ответ 3

Если вы не заботитесь о количестве вхождений, я бы подошел к нему так. Использование хеш-наборов даст вам лучшую производительность, чем простая итерация.

var set1 = new HashSet<MyType>(list1);
var set2 = new HashSet<MyType>(list2);
return set1.SetEquals(set2);

Это потребует, чтобы вы переопределили .GetHashCode() и внедрили IEquatable<MyType> на MyType.

Ответ 4

В дополнение к ответу Гуффа вы можете использовать этот вариант, чтобы иметь более короткую нотацию.

public static bool ScrambledEquals<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
{
  var deletedItems = list1.Except(list2).Any();
  var newItems = list2.Except(list1).Any();
  return !newItems && !deletedItems;          
}

Ответ 5

Думая, что это должно делать то, что вы хотите:

list1.All(item => list2.Contains(item)) &&
list2.All(item => list1.Contains(item));

если вы хотите, чтобы он отличался, вы можете изменить его на:

list1.All(item => list2.Contains(item)) &&
list1.Distinct().Count() == list1.Count &&
list1.Count == list2.Count

Ответ 6

Это немного сложная проблема, которая, по-моему, сводится к: "Проверьте, являются ли два списка перестановками друг друга".

Я считаю, что решения, предоставленные другими, указывают только на то, содержат ли 2 списка одни и те же уникальные элементы. Это необходимый, но недостаточный тест, например {1, 1, 2, 3} не является перестановкой {3, 3, 1, 2} хотя их подсчеты равны и содержат одни и те же отдельные элементы.

Я считаю, что это должно сработать, хотя оно не является наиболее эффективным:

static bool ArePermutations<T>(IList<T> list1, IList<T> list2)
{
   if(list1.Count != list2.Count)
         return false;

   var l1 = list1.ToLookup(t => t);
   var l2 = list2.ToLookup(t => t);

   return l1.Count == l2.Count 
       && l1.All(group => l2.Contains(group.Key) && l2[group.Key].Count() == group.Count()); 
}

Ответ 7

Это сработало для меня:
Если вы сравниваете два списка объектов, зависит от одного объекта, такого как ID, и вам нужен третий список, который соответствует этому условию, то вы можете сделать следующее:

list3=List1.Where(n => !List2.select(n1 => n1.Id).Contains.(n.Id));

Обратитесь: MSDN - С# Сравнить Два списка объектов

Ответ 8

Я использую этот метод)

public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);

public static bool CompareTwoArrays<T1, T2>(this IEnumerable<T1> array1, IEnumerable<T2> array2, CompareValue<T1, T2> compareValue)
{
    return array1.Select(item1 => array2.Any(item2 => compareValue(item1, item2))).All(search => search)
            && array2.Select(item2 => array1.Any(item1 => compareValue(item1, item2))).All(search => search);
}

Ответ 9

попробуйте это!!!

используя следующий код, вы можете сравнить одно или несколько полей для создания списка результатов в соответствии с вашими потребностями. список результатов будет содержать только измененные элементы.

// veriables been used
List<T> diffList = new List<T>();
List<T> gotResultList = new List<T>();



// compare First field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1))).ToList();
// Generate result list
diffList.AddRange(gotResultList);

// compare Second field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2))).ToList();
// Generate result list
diffList.AddRange(gotResultList);


MessageBox.Show(diffList.Count.ToString);

Ответ 10

Ответ

Объедините Count, Except и Any следующим образом. Как упоминал Биух, он быстрее Count.

public static bool AreTheSameIgnoringOrder(List<string> x, List<string> y)
{
    return x.Count() == y.Count() 
        && !x.Except(y).Any()
        && !y.Except(x).Any(); // re: Servy comment.
}

Demo

Вот скрипка, чтобы продемонстрировать.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var x = new List<string>() { "a", "b", "c"};

        var result1 = AreTheSameIgnoringOrder(x, new List<string>() { "c", "b", "a"});
        Console.WriteLine(result1);

        var result2 = AreTheSameIgnoringOrder(x, new List<string>() { "c", "b", "a", "b" });
        Console.WriteLine(result2);
    }

    public static bool AreTheSameIgnoringOrder(List<string> x, List<string> y)
    {
        return x.Count() == y.Count() 
            && !x.Except(y).Any()
            && !y.Except(x).Any();
    }
}