web-dev-qa-db-ja.com

LINQ to Objectsを使用して、あるコレクション内で別のコレクションと一致しないアイテムを検索する

別のコレクションと一致しない1つのコレクション内のすべてのアイテムを検索したい。ただし、コレクションは同じタイプではありません。等式を指定するラムダ式を記述したいと思います。

LINQPad 私がやろうとしていることの例:

void Main()
{
    var employees = new[]
    {
        new Employee { Id = 20, Name = "Bob" },
        new Employee { Id = 10, Name = "Bill" },
        new Employee { Id = 30, Name = "Frank" }
    };

    var managers = new[]
    {
        new Manager { EmployeeId = 20 },
        new Manager { EmployeeId = 30 }
    };

    var nonManagers =
    from employee in employees
    where !(managers.Any(x => x.EmployeeId == employee.Id))
    select employee;

    nonManagers.Dump();

    // Based on cdonner's answer:

    var nonManagers2 =
    from employee in employees
    join manager in managers
        on employee.Id equals manager.EmployeeId
    into tempManagers
    from manager in tempManagers.DefaultIfEmpty()
    where manager == null
    select employee;

    nonManagers2.Dump();

    // Based on Richard Hein's answer:

    var nonManagers3 =
    employees.Except(
        from employee in employees
        join manager in managers
            on employee.Id equals manager.EmployeeId
        select employee);

    nonManagers3.Dump();
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Manager
{
    public int EmployeeId { get; set; }
}

上記は機能し、Employee Bill(#10)を返します。ただし、洗練されているようには見えず、コレクションが大きい場合は効率が悪い場合があります。 SQLでは、おそらくLEFT JOINを実行して、2番目のIDがNULLであるアイテムを見つけます。 LINQでこれを行うためのベストプラクティスは何ですか?

編集:インデックスと等しいIDに依存するソリューションを防ぐために更新されました。

編集:cdonnerのソリューションを追加しました-誰かがもっと簡単なものを持っていますか?

編集:リチャード・ハインの答えに変種を追加しました、私の現在のお気に入りです。すばらしい回答をありがとうございました。

24
TrueWill

これは他のいくつかの例とほとんど同じですが、コードが少なくなっています。

employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e));

ただし、employees.Where(e =>!managers.Any(m => m.EmployeeId == e.Id))または元の構文よりも単純ではありません。

31
 var nonManagers =(従業員のe1から
はe1を選択)します。 EmployeeId == e2.Id 
 select e2); 
5
    /// <summary>
    /// This method returns items in a set that are not in 
    /// another set of a different type
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TOther"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="items"></param>
    /// <param name="other"></param>
    /// <param name="getItemKey"></param>
    /// <param name="getOtherKey"></param>
    /// <returns></returns>
    public static IEnumerable<T> Except<T, TOther, TKey>(
                                           this IEnumerable<T> items,
                                           IEnumerable<TOther> other,
                                           Func<T, TKey> getItemKey,
                                           Func<TOther, TKey> getOtherKey)
    {
        return from item in items
               join otherItem in other on getItemKey(item)
               equals getOtherKey(otherItem) into tempItems
               from temp in tempItems.DefaultIfEmpty()
               where ReferenceEquals(null, temp) || temp.Equals(default(TOther))
               select item;
    }

この方法をどこで見つけたか覚えていません。

5
cdonner

少し遅れます(わかっています)。

私は同じ問題を見ていましたが、その方向にさまざまなパフォーマンスのヒントがあるため、HashSetを検討していました。 @Skeetの IEnumerable.Intersect()を使用した複数のリストの交差 -そして私のオフィスの周りで尋ねられ、コンセンサスはHashSetがより高速で読みやすくなることでした:

HashSet<int> managerIds = new HashSet<int>(managers.Select(x => x.EmployeeId));
nonManagers4 = employees.Where(x => !managerIds.Contains(x.Id)).ToList();

次に、ネイティブ配列を使用してビットマスク風のタイプのソリューションを作成するさらに高速なソリューションが提供されました(ただし、ネイティブ配列クエリの構文は、極端なパフォーマンス上の理由を除いて、それらを使用することをやめさせます)。

ひどい長い時間の後にこの答えに少し信用を与えるために、私はあなたのlinqpadプログラムとデータをタイミングで拡張して、現在6つのオプションを比較できるようにしました:

void Main()
{
    var employees = new[]
    {
        new Employee { Id = 20, Name = "Bob" },
        new Employee { Id = 10, Name = "Kirk NM" },
        new Employee { Id = 48, Name = "Rick NM" },
        new Employee { Id = 42, Name = "Dick" },
        new Employee { Id = 43, Name = "Harry" },
        new Employee { Id = 44, Name = "Joe" },
        new Employee { Id = 45, Name = "Steve NM" },
        new Employee { Id = 46, Name = "Jim NM" },
        new Employee { Id = 30, Name = "Frank"},
        new Employee { Id = 47, Name = "Dave NM" },
        new Employee { Id = 49, Name = "Alex NM" },
        new Employee { Id = 50, Name = "Phil NM" },
        new Employee { Id = 51, Name = "Ed NM" },
        new Employee { Id = 52, Name = "Ollie NM" },
        new Employee { Id = 41, Name = "Bill" },
        new Employee { Id = 53, Name = "John NM" },
        new Employee { Id = 54, Name = "Simon NM" }
    };

    var managers = new[]
    {
        new Manager { EmployeeId = 20 },
        new Manager { EmployeeId = 30 },
        new Manager { EmployeeId = 41 },
        new Manager { EmployeeId = 42 },
        new Manager { EmployeeId = 43 },
        new Manager { EmployeeId = 44 }
    };

    System.Diagnostics.Stopwatch watch1 = new System.Diagnostics.Stopwatch();

    int max = 1000000;

    watch1.Start();
    List<Employee> nonManagers1 = new List<Employee>();
    foreach (var item in Enumerable.Range(1,max))
    {
        nonManagers1 = (from employee in employees where !(managers.Any(x => x.EmployeeId == employee.Id)) select employee).ToList();

    }
    nonManagers1.Dump();
    watch1.Stop();
    Console.WriteLine("Any: " + watch1.ElapsedMilliseconds);

    watch1.Restart();       
    List<Employee> nonManagers2 = new List<Employee>();
    foreach (var item in Enumerable.Range(1,max))
    {
        nonManagers2 =
        (from employee in employees
        join manager in managers
            on employee.Id equals manager.EmployeeId
        into tempManagers
        from manager in tempManagers.DefaultIfEmpty()
        where manager == null
        select employee).ToList();
    }
    nonManagers2.Dump();
    watch1.Stop();
    Console.WriteLine("temp table: " + watch1.ElapsedMilliseconds);

    watch1.Restart();       
    List<Employee> nonManagers3 = new List<Employee>();
    foreach (var item in Enumerable.Range(1,max))
    {
        nonManagers3 = employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e)).ToList();
    }
    nonManagers3.Dump();
    watch1.Stop();
    Console.WriteLine("Except: " + watch1.ElapsedMilliseconds);

    watch1.Restart();       
    List<Employee> nonManagers4 = new List<Employee>();
    foreach (var item in Enumerable.Range(1,max))
    {
        HashSet<int> managerIds = new HashSet<int>(managers.Select(x => x.EmployeeId));
        nonManagers4 = employees.Where(x => !managerIds.Contains(x.Id)).ToList();

    }
    nonManagers4.Dump();
    watch1.Stop();
    Console.WriteLine("HashSet: " + watch1.ElapsedMilliseconds);

      watch1.Restart();
      List<Employee> nonManagers5 = new List<Employee>();
      foreach (var item in Enumerable.Range(1, max))
      {
                   bool[] test = new bool[managers.Max(x => x.EmployeeId) + 1];
                   foreach (var manager in managers)
                   {
                        test[manager.EmployeeId] = true;
                   }

                   nonManagers5 = employees.Where(x => x.Id > test.Length - 1 || !test[x.Id]).ToList();


      }
      nonManagers5.Dump();
      watch1.Stop();
      Console.WriteLine("Native array call: " + watch1.ElapsedMilliseconds);

      watch1.Restart();
      List<Employee> nonManagers6 = new List<Employee>();
      foreach (var item in Enumerable.Range(1, max))
      {
                   bool[] test = new bool[managers.Max(x => x.EmployeeId) + 1];
                   foreach (var manager in managers)
                   {
                        test[manager.EmployeeId] = true;
                   }

                   nonManagers6 = employees.Where(x => x.Id > test.Length - 1 || !test[x.Id]).ToList();
      }
      nonManagers6.Dump();
      watch1.Stop();
      Console.WriteLine("Native array call 2: " + watch1.ElapsedMilliseconds);
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Manager
{
    public int EmployeeId { get; set; }
}
4
amelvin
var nonmanagers = employees.Select(e => e.Id)
    .Except(managers.Select(m => m.EmployeeId))
    .Select(id => employees.Single(e => e.Id == id));
3
G-Wiz

Except()LINQ関数を見てください。それはまさにあなたが必要とするものを行います。

2
nitzmahone

アイテムを結合したままにしておき、null条件でフィルタリングすることをお勧めします

var finalcertificates = (from globCert in resultCertificate
                                         join toExcludeCert in certificatesToExclude
                                             on globCert.CertificateId equals toExcludeCert.CertificateId into certs
                                         from toExcludeCert in certs.DefaultIfEmpty()
                                         where toExcludeCert == null
                                         select globCert).Union(currentCertificate).Distinct().OrderBy(cert => cert.CertificateName);
1
Mahendra

マネージャーも社員!したがって、ManagerクラスはEmployeeクラスからサブクラス化する必要があります(または、それが望ましくない場合は、両方とも親クラスからサブクラス化するか、NonManagerを作成する必要がありますクラス)。

次に、問題はIEquatableスーパークラスにEmployeeインターフェースを実装し(GetHashCodeは単にEmployeeIDを返す)、次のコードを使用するだけです。

var nonManagerEmployees = employeeList.Except(managerList);
0
ErikE