web-dev-qa-db-ja.com

ラムダと区別()?

そう、私は列挙型を持っていて、それから異なる値を得たいと思っています。

System.Linqを使用して、もちろんDistinctと呼ばれる拡張メソッドがあります。単純な場合には、次のようにパラメータなしで使用できます。

var distinctValues = myStringList.Distinct();

うまくいきますが、同等性を指定する必要のある列挙可能なオブジェクトがある場合、唯一利用可能なオーバーロードは次のとおりです。

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

等価比較引数はIEqualityComparer<T>のインスタンスでなければなりません。もちろんこれは可能ですが、やや冗長で、おかしなことです。

私が期待していたことはラムダを取るだろうオーバーロードです、Func <T、T、bool>を言う:

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

誰かがそのような拡張機能が存在するかどうか、あるいはそれと同等の回避策があるかどうか知っていますか?それとも私は何かが足りないのですか?

あるいは、IEqualityComparerをインラインで指定する方法はありますか?

更新

私はこの件に関するMSDNフォーラムで post へのAnders Hejlsbergの返事を見つけました。彼は言い​​ます:

遭遇する問題は、2つのオブジェクトが等しい場合、それらは同じGetHashCode戻り値を持つ必要があるということです(そうでなければ、Distinctによって内部的に使用されるハッシュテーブルは正しく機能しません)。 IEqualityComparerを使用するのは、EqualsとGetHashCodeの互換性のある実装を単一のインターフェイスにパッケージ化しているためです。

私はそれが理にかなっていると思います。

680
Tor Haugen
IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());
946
Carlo Bos

DistinctBy from MoreLINQ が欲しいようです。あなたはそれから書くことができます:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

これはDistinctByの簡易版です(nullチェックや独自のキー比較を指定するオプションはありません)。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
     (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> knownKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (knownKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}
456
Jon Skeet

ものをまとめるには 。私のようにここに来た人たちの大部分は 最も簡単 可能な解決策 ライブラリを使わずに そして可能な限り最高の パフォーマンス を望んでいると思います。

(私の方法で承認されたグループは、パフォーマンスの面ではやり過ぎだと思います。)

これは IEqualityComparer インターフェースを使った簡単な拡張メソッドで、null値に対しても機能します。

使用法:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

拡張メソッドコード

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}
28

いいえこのような拡張メソッドのオーバーロードはありません。私は過去にこれをイライラさせていることに気付きました。そのため、通常はこの問題に対処するためのヘルパークラスを作成します。目標はFunc<T,T,bool>IEqualityComparer<T,T>に変換することです。

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

これにより、次のように書くことができます。

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
19
JaredPar

速記ソリューション

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
16
Arasu RRK

これはあなたが望むことをするでしょうが、私はパフォーマンスについて知りません:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

少なくとも冗長ではありません。

12
Gordon Freeman

これが私が必要とすることをする簡単な拡張方法です...

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

このような明確な方法をフレームワークに組み込まなかったのは残念ですが、やあ。

10
David Kirkland

私が使ったことのあるものが私にとってはうまくいった。

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}
4
Kleinux

私がここで見たすべての解決策はすでに比較可能な分野を選ぶことに頼っています。しかし、別の方法で比較する必要がある場合は、 この解決策は のように一般的に機能します。

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
3

別の方法をとる:

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

シーケンスが返す個別の要素は、プロパティ '_myCaustomerProperty'によってそれらを比較します。

3
Bob

InlineComparerを使用できます

public class InlineComparer<T> : IEqualityComparer<T>
{
    //private readonly Func<T, T, bool> equalsMethod;
    //private readonly Func<T, int> getHashCodeMethod;
    public Func<T, T, bool> EqualsMethod { get; private set; }
    public Func<T, int> GetHashCodeMethod { get; private set; }

    public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = hashCode;
    }

    public bool Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    public int GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null) return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

使用例

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
  var peticionesEV = listaLogs.Distinct(comparer).ToList();
  Assert.IsNotNull(peticionesEV);
  Assert.AreNotEqual(0, peticionesEV.Count);

ソース: https://stackoverflow.com/a/5969691/206730
連合のためのIEqualityComparerの使用
明示的な型比較子をインラインで指定できますか?

2
Kiquenet

あなたはLambda iEqualityComparerを使うことができます:

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
    {
        public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
        {
            _equalsFunction = equalsFunction;
        }

        public bool Equals(T x, T y)
        {
            return _equalsFunction(x, y);
        }

        public int GetHashCode(T obj)
        {
            return obj.GetHashCode();
        }

        private readonly Func<T, T, bool> _equalsFunction;
    }

これを行うための巧妙な方法は、辞書としてアキュムレータとして key-property valuesをキーとして使用して、Aggregate()拡張子を使用することです。

var customers = new List<Customer>();

var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

そして GroupBy-style の解決法はToLookup()を使っています:

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
1
Arturo Menchaca

これがあなたのやり方です。

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

このメソッドは.MyDistinct(d => d.Name)のように1つのパラメータを指定することによってそれを使用することを可能にします、しかしそれはあなたがそのように2番目のパラメータとしてhaving条件を指定することを可能にします:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

N.B. これにより、例えば.LastOrDefault(...)のような他の関数を指定することもできます。


もし条件だけを公開したいのであれば、次のように実装することでさらに簡単にすることができます。

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

この場合、クエリは次のようになります。

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

N.B. ここでは、式はより単純ですが、.MyDistinct2.FirstOrDefault(...)を暗黙的に使用しています。


注: 上記の例では、次のデモクラスを使用しています。

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};
0
Matt

Microsoft System.Interactiveパッケージ にはキーセレクターラムダをとるDistinctのバージョンがあります。これはJon Skeetの解決策と実質的に同じですが、人々が知っていてライブラリの残りの部分をチェックするのに役立つかもしれません。

0

IEnumerableラムダ拡張子:

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

使用法:

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

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 
0
Shantanu

Distinct()が独自の結果をもたらさない場合は、これを試してください。

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);
0
Andy Singh

私はあなたがIEnumerableを持っていると仮定します、そしてあなたの例のデリゲートで、あなたはc1とc2がこのリストの2つの要素を参照することを望みますか?

これは、myListのc1からmyListのc2に参加するという、var distinctResults =からの自己結合で実現できると思います。

0
MattH