c# - 複数 - linq 重複 抽出




ラムダとのDistinct()? (12)

そうですね、私は列挙可能であり、そこから別個の価値を得ることを望みます。

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をインライン(embarass me)に指定する方法はありますか?

更新

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

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

それは意味があると思う..


LambdaEqualityComparerを使用することができます:

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;
    }

Microsoft System.Interactiveパッケージには、キーセレクタlambdaを使用するDistinctのバージョンがあります。 これはJon Skeetのソリューションと事実上同じですが、人々が知り、ライブラリの残りの部分をチェックアウトするのに役立ちます。


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()); 

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://.com/a/5969691/206730 : https://.com/a/5969691/206730
Union用にIEqualityComparerを使用する
明示的な型コンパレータをインラインで指定できますか?


あなたがMoreLINQとのDistinctByを望むように見えます。 あなたは次のように書くことができます:

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

以下は、 DistinctByカットダウンバージョンです(ヌルチェックなし、独自のキー比較機能を指定するオプションはありません)。

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;
        }
    }
}

あなたがそれを行う方法は次のとおりです:

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番目のパラメータとして条件を指定することもできます:

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

注意 :例えば、 .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")
                    );

NBここで、式は簡単ですが、 .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"}
};

ここに私が必要とするものを行う簡単な拡張メソッドがあります...

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);
    }
}

彼らはフレームワークにこれのような別個の方法を焼き付けなかったのは残念ですが、ちょっとしたことです。


これはあなたの望むことをしますが、パフォーマンスについてはわかりません:

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

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


別の方法を取る:

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

シーケンスは個別の要素を返すことで、それらを '_myCaustomerProperty'プロパティで比較します。


私がここで見たすべての解決策は、すでに比較可能なフィールドを選択することに依存しています。 しかし、別の方法で比較する必要がある場合は、 このソリューションは一般的には次のようなものになります。

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

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

私はあなたが自己結合でこれを達成できると信じていますvar distinctResults = myListのc1からmyListのc2をjoin on


簡略解

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




extension-methods