C # 'ta Sözlük anahtarları olarak diziler (veya diziler)


109

C # dilinde Sözlük arama tablosu yapmaya çalışıyorum. 3 demetlik değeri bir dizeye çözümlemem gerekiyor. Dizileri anahtar olarak kullanmayı denedim ama bu işe yaramadı ve başka ne yapacağımı bilmiyorum. Bu noktada bir Sözlükler Sözlüğü yapmayı düşünüyorum, ancak javascript'te böyle yapsam da buna bakmak pek hoş olmaz.

Yanıtlar:


113

.NET 4.0 üzerindeyseniz bir Tuple kullanın:

lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();

Değilse, a tanımlayabilir Tupleve bunu anahtar olarak kullanabilirsiniz. Kayıt düzeni geçersiz kılmak gerekir GetHashCode, Equalsve IEquatable:

struct Tuple<T, U, W> : IEquatable<Tuple<T,U,W>>
{
    readonly T first;
    readonly U second;
    readonly W third;

    public Tuple(T first, U second, W third)
    {
        this.first = first;
        this.second = second;
        this.third = third;
    }

    public T First { get { return first; } }
    public U Second { get { return second; } }
    public W Third { get { return third; } }

    public override int GetHashCode()
    {
        return first.GetHashCode() ^ second.GetHashCode() ^ third.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
        {
            return false;
        }
        return Equals((Tuple<T, U, W>)obj);
    }

    public bool Equals(Tuple<T, U, W> other)
    {
        return other.first.Equals(first) && other.second.Equals(second) && other.third.Equals(third);
    }
}

6
Bu yapı ayrıca IEquatable <Tuple <T, U, W >> uygulamalıdır. Bu şekilde, karma kod çarpışmaları durumunda Equals () çağrıldığında kutulamayı önleyebilirsiniz.
Dustin Campbell

16
@jerryjvl ve bunu Google tarafından benim yaptığım gibi bulan herkes, .NET 4'ün Tuple'ı bir sözlükte kullanılabilmesi için eşittir uygular .
Scott Chamberlain

30
Sizin GetHashCodeuygulaması çok iyi değil. Alanların permütasyonu altında değişmez.
CodesInChaos

2
Tuple bir yapı olmamalıdır. Çerçevede, Tuple bir referans türüdür.
Michael Graczyk

5
@Thoraot - tabii ki örneğiniz yanlış ... öyle olmalı. Neden new object()diğerine eşit olsun new object()? Sadece doğrudan referans karşılaştırması kullanmakla kalmıyor ... deneyin:bool test = new Tuple<int, string>(1, "foo").Equals(new Tuple<int, string>(1, "Foo".ToLower()));
Mike Marynowski

35

Tuple ve iç içe geçmiş sözlük tabanlı yaklaşımlar arasında, tuple tabanlı yaklaşımlar neredeyse her zaman daha iyidir.

Sürdürülebilirlik açısından ,

  • şuna benzeyen bir işlevi uygulamak çok daha kolaydır:

    var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();

    göre

    var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();

    aranan taraftan. İkinci durumda, her ekleme, arama, kaldırma vb. Birden fazla sözlükte işlem yapılmasını gerektirir.

  • Ayrıca, bileşik anahtarınız gelecekte bir daha fazla (veya daha az) alan gerektiriyorsa, daha fazla iç içe geçmiş sözlük ve sonraki kontroller eklemeniz gerektiğinden, ikinci durumda (iç içe geçmiş sözlük) kodu önemli ölçüde değiştirmeniz gerekecektir.

Performans açısından , ulaşabileceğiniz en iyi sonuç, onu kendiniz ölçmektir. Ancak önceden değerlendirebileceğiniz birkaç teorik sınırlama vardır:

  • İç içe geçmiş sözlük durumunda, her anahtar için (dış ve iç) ek bir sözlüğe sahip olmak, bir miktar bellek ek yüküne sahip olacaktır (bir demet oluşturmanın sahip olacağından daha fazla).

  • İç içe geçmiş sözlük durumunda, toplama, güncelleme, arama, kaldırma vb. Gibi her temel işlemin iki sözlükte gerçekleştirilmesi gerekir. Şimdi, iç içe geçmiş sözlük yaklaşımının daha hızlı olabileceği bir durum var, yani, aranan veri olmadığında, çünkü ara sözlükler tam karma kod hesaplamasını ve karşılaştırmasını atlayabilir, ancak yine de emin olmak için zamanlaması gerekir. Verilerin varlığında, aramaların iki kez (veya yuvalamaya bağlı olarak üç kez) yapılması gerektiğinden daha yavaş olmalıdır.

  • Tanımlama grubu yaklaşımı ile ilgili olarak, .NET tuples onların beri setlerinde tuşları olarak kullanılacak geldiğini gösteren etkili ve verimli değildir Equalsve GetHashCodeuygulama nedenleri değer türleri için boks .

Tuple tabanlı sözlükle giderdim, ancak daha fazla performans istersem, daha iyi bir uygulama ile kendi demetimi kullanırdım.


Bir yan not olarak, az sayıda kozmetik sözlüğü havalı hale getirebilir:

  1. Dizin oluşturucu tarzı aramalar çok daha temiz ve sezgisel olabilir. Örneğin,

    string foo = dict[a, b, c]; //lookup
    dict[a, b, c] = ""; //update/insertion

    Bu nedenle, sözlük sınıfınızda, eklemeleri ve aramaları dahili olarak işleyen gerekli dizinleyicileri gösterin.

  2. Ayrıca, uygun bir IEnumerablearayüz uygulayın ve Add(TypeA, TypeB, TypeC, string)size aşağıdaki gibi koleksiyon başlatıcı sözdizimi verecek bir yöntem sağlayın :

    new MultiKeyDictionary<TypeA, TypeB, TypeC, string> 
    { 
        { a, b, c, null }, 
        ...
    };

İç içe sözlüklerde durumunda, dizin daha böyle olmasını sözdizimini olmaz: string foo = dict[a][b][c]?
Steven Rands

@StevenRands evet olacak.
nawfal

1
@nawfal Tuple sözlüğünde yalnızca bir anahtarım varken arama yapabilir miyim? veya bu dikteyi [a, b] sonra dikte edebilir miyim [a, c]?
Khan Engineer

@KhanEngineer Bunların çoğu, sözlüğün amaçlanan amacına veya onu nasıl kullanmak istediğinize bağlıdır. Örneğin, anahtarın bir parçasıyla değeri geri almak istiyorsunuz a. Normal bir koleksiyon gibi herhangi bir sözlüğü yineleyebilir ve eğer öyleyse anahtar özelliğini kontrol edebilirsiniz a. Maddeyi her zaman ilk özelliğe göre dikte almak istiyorsanız, sözlüğü cevabımda ve sorguda gösterildiği gibi sözlükler sözlüğü olarak daha iyi tasarlayabilirsiniz dict[a], bu size başka bir sözlük verir.
nawfal

"Tek tuşla ara" derken, sahip olduğunuz herhangi bir tuşla değeri geri almak istiyorsanız, sözlüğünüzü bir tür "herhangi bir anahtar sözlük" olarak yeniden tasarlamanız daha iyi olur. Eg için size değerini almak istiyorsanız 4, her iki anahtarlar için ave bdaha sonra bunu bir standart sözlük yapmak ve benzeri değerler eklemek olabilir dict[a] = 4ve dict[b] = 4. Mantıksal olarak sizin ave btek bir birim olması gerekiyorsa mantıklı olmayabilir . Böyle bir durumda IEqualityComparer, özelliklerinden herhangi biri eşitse, iki anahtar örneğini eşit olarak eşitleyen bir özel tanımlayabilirsiniz . Bütün bunlar genel olarak refelction ile yapılabilir.
nawfal

30

C # 7 kullanıyorsanız, bileşik anahtarınız olarak değer demetlerini kullanmayı düşünmelisiniz. Değer demetleri tipik olarak geleneksel referans demetlerinden ( Tuple<T1, …>) daha iyi performans sunar çünkü değer demetleri referans türleri değil, değer türleri (yapılar) olduğundan bellek ayırma ve atık toplama maliyetlerinden kaçınırlar. Ayrıca, kısaltılmış ve daha sezgisel sözdizimi sunarlar ve dilerseniz alanlarının adlandırılmasına izin verirler. IEquatable<T>Sözlük için gerekli olan arayüzü de uygularlar.

var dict = new Dictionary<(int PersonId, int LocationId, int SubjectId), string>();
dict.Add((3, 6, 9), "ABC");
dict.Add((PersonId: 4, LocationId: 9, SubjectId: 10), "XYZ");
var personIds = dict.Keys.Select(k => k.PersonId).Distinct().ToList();

13

İyi, temiz, hızlı, kolay ve okunaklı yollar:

  • geçerli tür için eşitlik üyeleri (Equals () ve GetHashCode ()) yöntemi oluşturur. ReSharper gibi araçlar sadece yöntemleri oluşturmakla kalmaz, aynı zamanda bir eşitlik kontrolü ve / veya karma kodu hesaplamak için gerekli kodu da üretir. Üretilen kod, Tuple gerçekleştirmeden daha optimal olacaktır.
  • sadece bir demetten türetilmiş basit bir anahtar sınıfı yapın .

buna benzer bir şey ekleyin:

public sealed class myKey : Tuple<TypeA, TypeB, TypeC>
{
    public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { }

    public TypeA DataA => Item1; 

    public TypeB DataB => Item2;

    public TypeC DataC => Item3;
}

Yani sözlük ile kullanabilirsiniz:

var myDictinaryData = new Dictionary<myKey, string>()
{
    {new myKey(1, 2, 3), "data123"},
    {new myKey(4, 5, 6), "data456"},
    {new myKey(7, 8, 9), "data789"}
};
  • Sözleşmelerde de kullanabilirsiniz
  • linq'te katılmak veya gruplandırmalar için bir anahtar olarak
  • bu şekilde giderseniz, Öğe1, Öğe2, Öğe3 sırasını asla yanlış yazmazsınız ...
  • bir şeyi almak için nereye gideceğinizi anlamak için hatırlamanıza veya kodlara bakmanıza gerek yok
  • IStructuralEquatable, IStructuralComparable, IComparable, ITuple bunların hepsini burada geçersiz kılmaya gerek yok

1
Artık ifade gövdeli üyeleri kullanabilirsiniz, daha da temiz örn.public TypeA DataA => Item1;
andrewtatham

7

Herhangi bir nedenle gerçekten kendi Tuple sınıfınızı oluşturmaktan veya .NET 4.0'da yerleşik kullanmaktan kaçınmak istiyorsanız, olası başka bir yaklaşım daha vardır; üç anahtar değeri birlikte tek bir değerde birleştirebilirsiniz.

Örneğin, üç değer birlikte 64 bitten fazlasını almayan tam sayı türleriyse, bunları bir ulong.

En kötü ihtimalle, içindeki üç bileşenin anahtar bileşenlerinde bulunmayan bir karakter veya diziyle, örneğin deneyebileceğiniz üç sayı ile sınırlandırıldığından emin olduğunuz sürece her zaman bir dize kullanabilirsiniz:

string.Format("{0}#{1}#{2}", key1, key2, key3)

Açıkçası, bu yaklaşımda bazı kompozisyon ek yükleri vardır, ancak ne kullandığınıza bağlı olarak, bununla ilgilenmeyecek kadar önemsiz olabilir.


6
Yine de büyük ölçüde bağlama bağlı olduğunu söyleyebilirim; Birleştirecek üç tam sayı türünüz olsaydı ve performans kritik değilse, bu, minimum hata yapma şansı ile mükemmel bir şekilde çalışıyor. Elbette, Microsoft bize kutudan çıktığı gibi (muhtemelen doğru!) Tuple türlerini sağlayacağından, bunların tümü .NET 4 itibariyle tamamen gereksizdir.
jerryjvl

Bu yöntemi, sizin JavaScriptSerializeriçin bir dizi ve / veya tamsayı türlerini birleştirmek için a ile kombinasyon halinde bile kullanabilirsiniz . Bu şekilde, kendiniz bir sınırlayıcı karakter bulmanız gerekmez.
binki

3
Tuşlarından (herhangi Bu gerçek pis bir alabilir key1, key2, key3) deliminator (içeren dizeleri idi "#")
Greg

4

Tuple'ınızı uygun bir GetHashCode ile geçersiz kılar ve sadece anahtar olarak kullanırdım.

Doğru yöntemleri aşırı yüklediğiniz sürece, iyi performans görmelisiniz.


1
IComparable'ın, anahtarların bir Dictionary <TKey, TValue> içinde nasıl saklandığı veya konumlandırıldığı üzerinde etkisi yoktur. Hepsi GetHashCode () ve bir IEqualityComparer <T> aracılığıyla yapılır. IEquatable <T> uygulaması, Equals (nesne) işlevine geri dönen varsayılan EqualityComparer'ın neden olduğu kutulamayı hafiflettiği için daha iyi performans elde edecektir.
Dustin Campbell

GetHashCode'dan bahsedecektim, ancak HashCode'ların Özdeş olması durumunda Dictionary'nin IComparable kullandığını düşündüm ... sanırım yanılmışım.
John Gietzen

3

İşte referans için .NET demeti:

[Serializable] 
public class Tuple<T1, T2, T3> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple {

    private readonly T1 m_Item1; 
    private readonly T2 m_Item2;
    private readonly T3 m_Item3; 

    public T1 Item1 { get { return m_Item1; } }
    public T2 Item2 { get { return m_Item2; } }
    public T3 Item3 { get { return m_Item3; } } 

    public Tuple(T1 item1, T2 item2, T3 item3) { 
        m_Item1 = item1; 
        m_Item2 = item2;
        m_Item3 = item3; 
    }

    public override Boolean Equals(Object obj) {
        return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);; 
    }

    Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) { 
        if (other == null) return false;

        Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;

        if (objTuple == null) {
            return false; 
        }

        return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3); 
    }

    Int32 IComparable.CompareTo(Object obj) {
        return ((IStructuralComparable) this).CompareTo(obj, Comparer<Object>.Default);
    }

    Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer) {
        if (other == null) return 1; 

        Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;

        if (objTuple == null) {
            throw new ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other");
        }

        int c = 0;

        c = comparer.Compare(m_Item1, objTuple.m_Item1); 

        if (c != 0) return c; 

        c = comparer.Compare(m_Item2, objTuple.m_Item2);

        if (c != 0) return c; 

        return comparer.Compare(m_Item3, objTuple.m_Item3); 
    } 

    public override int GetHashCode() { 
        return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
    }

    Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) { 
        return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3));
    } 

    Int32 ITuple.GetHashCode(IEqualityComparer comparer) {
        return ((IStructuralEquatable) this).GetHashCode(comparer); 
    }
    public override string ToString() {
        StringBuilder sb = new StringBuilder();
        sb.Append("("); 
        return ((ITuple)this).ToString(sb);
    } 

    string ITuple.ToString(StringBuilder sb) {
        sb.Append(m_Item1); 
        sb.Append(", ");
        sb.Append(m_Item2);
        sb.Append(", ");
        sb.Append(m_Item3); 
        sb.Append(")");
        return sb.ToString(); 
    } 

    int ITuple.Size { 
        get {
            return 3;
        }
    } 
}

3
Karma kodu al ((öğe1 ^ öğe2) * 33) ^ öğe3
Michael Graczyk

2

Tüketen kodunuz Sözlük yerine bir IDictionary <> arayüzü ile yetinebilirse, benim içgüdülerim özel bir dizi karşılaştırıcı ile SortedDictionary <> kullanmak olurdu, yani:

class ArrayComparer<T> : IComparer<IList<T>>
    where T : IComparable<T>
{
    public int Compare(IList<T> x, IList<T> y)
    {
        int compare = 0;
        for (int n = 0; n < x.Count && n < y.Count; ++n)
        {
            compare = x[n].CompareTo(y[n]);
        }
        return compare;
    }
}

Ve şöyle oluşturun (int [] kullanarak somut bir örnek uğruna):

var dictionary = new SortedDictionary<int[], string>(new ArrayComparer<int>());
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.