Geçersiz kılmak için en iyi uygulamalar


267

isEqual:Objective-C'de uygun şekilde nasıl geçersiz kılabilirsiniz ? "Yakala", iki nesne eşitse ( isEqual:yöntem tarafından belirlendiği gibi ), aynı karma değerine sahip olmaları gerektiği şeklindedir.

Introspection bölümü Kakao Temelleri Kılavuzu geçersiz kılmak nasıl bir örnek var isEqual:adında bir sınıf için, aşağıdaki gibi kopyalanabilir MyWidget:

- (BOOL)isEqual:(id)other {
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:other];
}

- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
    if (self == aWidget)
        return YES;
    if (![(id)[self name] isEqual:[aWidget name]])
        return NO;
    if (![[self data] isEqualToData:[aWidget data]])
        return NO;
    return YES;
}

İşaretçi eşitliğini, sonra sınıf eşitliğini denetler ve son olarak isEqualToWidget:yalnızca nameve dataözelliklerini denetleyen nesnelerini karşılaştırır . Ne örnek vermez göstermek geçersiz kılmak nasıl hash.

Diyelim ki eşitliği etkilemeyen başka özellikler var age. Olmamalı hashyöntemi yalnızca böyle geçersiz kılınabilir nameve datakarma etkiler? Ve eğer öyleyse, bunu nasıl yapardın? Sadece nameve data? Örneğin:

- (NSUInteger)hash {
    NSUInteger hash = 0;
    hash += [[self name] hash];
    hash += [[self data] hash];
    return hash;
}

Bu yeterli mi? Daha iyi bir teknik var mı? Ya ilkelleriniz varsa, mesela int? NSNumberOnların karma almak için onları dönüştürmek ? Veya gibi yapılar NSRect?

( Beyin osuruğu : Başlangıçta onları "bitsel VEYA" yazdı |=.


2
if (![other isKindOfClass:[self class]])- Bu teknik olarak eşitliğin Değişmeli olmayacağı anlamına gelir. Yani A = B, B = A anlamına gelmez (örneğin, biri diğerinin alt sınıfı ise)
Robert

Dokümantasyon bağlantısı öldü, şimdi Introspection'a
jedwidz

Yanıtlar:


111

İle başla

 NSUInteger prime = 31;
 NSUInteger result = 1;

Sonra yaptığınız her ilkel için

 result = prime * result + var

Nesneler için nil için 0, aksi takdirde bunların hashcode'larını kullanırsınız.

 result = prime * result + [var hash];

Booleans için iki farklı değer kullanırsınız

 result = prime * result + ((var)?1231:1237);

Açıklama ve Atıf

Bu tcurdt'ın işi değil ve yorumlar daha fazla açıklama istiyordu, bu yüzden atıf için bir düzenlemenin adil olduğuna inanıyorum.

Bu algoritma "Etkili Java" kitabında popüler hale getirildi ve ilgili bölüm şu anda çevrimiçi olarak burada bulunabilir . Bu kitap, şimdi bir dizi Java uygulamasında (Eclipse dahil) varsayılan olan algoritmayı popülerleştirdi. Bununla birlikte, Dan Bernstein veya Chris Torek'e çeşitli şekillerde atfedilen daha eski bir uygulamadan türemiştir. Bu eski algoritma aslında Usenet'te dolaşıyordu ve bazı atıflar zor. Örneğin, bu Apache kodunda orijinal adıyla ilgili bazı ilginç yorumlar var (isimlerini arayın).

Alt satır, bu çok eski, basit bir karma algoritmasıdır. En yüksek performans değil ve matematiksel olarak "iyi" bir algoritma olduğu bile kanıtlanmadı. Ancak bu basit ve birçok insan uzun zamandır iyi sonuçlarla kullandı, bu yüzden çok fazla tarihsel desteğe sahip.


9
1231: 1237 nereden geldi? Ben de Java'nın Boolean.hashCode () bakın. Büyülü mü?
David Leonard

17
Çarpışmalar olacak bir karma algoritma doğasıdır. Demek istediğini anlamıyorum, Paul.
tcurdt

85
Bence bu cevap asıl soruya yanıt vermiyor (NSObject hashını geçersiz kılmak için en iyi uygulamalar). Sadece belirli bir hash algoritması sağlar. Bunun da ötesinde, açıklamanın azlığı, konuyla ilgili derin bir bilgi olmadan anlaşılmasını zorlaştırır ve insanların ne yaptığını bilmeden kullanmasına neden olabilir. Bu sorunun neden bu kadar çok oyu olduğunu anlamıyorum.
Ricardo Sanchez-Saez

6
1. sorun - (int) küçük ve taşması kolaydır, NSUInteger kullanın. 2. sorun - Sonucu her değişken karma ile çarpmaya devam ederseniz sonucunuz taşacaktır. Örneğin. [NSString hash] büyük değerler oluşturur. 5+ değişkeniniz varsa, bu algoritma ile taşması kolaydır. Her şeyin aynı karma ile eşleştirilmesi ile sonuçlanır, bu da kötüdür.
Cevabımı

10
@PaulSolt - Bir hash oluşturmada taşma sorunu değil, çarpışma. Ancak taşma, çarpışmayı daha olası hale getirmez ve taşma hakkındaki her şeyin aynı karma ile eşleşmesine neden olan ifadeniz yanlıştır.
DougW

81

Ben sadece Objective-C'yi kendim alıyorum, bu yüzden o dil için özel olarak konuşamam, ama diğer dillerde kullandığım iki örnek "Eşit" ise aynı hash döndürmelidir - aksi takdirde her şeye sahip olacaksınız bir hashtable (veya sözlük türü koleksiyonlarda) anahtar olarak kullanmaya çalışırken bir çok problem.

Öte yandan, 2 örnek eşit değilse, aynı karmaya sahip olabilirler veya olmayabilirler - eğer yapmazlarsa en iyisidir. Bu, bir karma tabloda O (1) araması ile O (N) araması arasındaki farktır - eğer tüm karmalarınız çarpışırsa, tablonuzu aramanın bir liste aramaktan daha iyi olmadığını görebilirsiniz.

En iyi uygulamalar açısından, karma değeriniz girdisi için rastgele bir değer dağılımı döndürmelidir. Bu, örneğin, bir çiftiniz varsa, ancak değerlerinizin çoğunluğu 0 ile 100 arasında kümelenme eğilimi gösteriyorsa, bu değerlerin döndürdüğü karmaların olası tüm karma değerleri aralığına eşit olarak dağıtıldığından emin olmanız gerekir. . Bu, performansınızı önemli ölçüde artıracaktır.

Burada sıralananlar da dahil olmak üzere bir dizi karma algoritma vardır. Büyük performans sonuçları olabileceğinden yeni karma algoritmalar oluşturmaktan kaçınmaya çalışıyorum, bu nedenle mevcut karma yöntemlerini kullanmak ve örneğinizde yaptığınız gibi bir çeşit bitsel kombinasyon yapmak bunu önlemenin iyi bir yoludur.


4
+1 Mükemmel cevap, özellikle "en iyi uygulamalar" ve iyi (benzersiz) bir karmanın neden önemli olduğu teorisinden bahsettiği için daha fazla oyu hak ediyor.
Quinn Taylor

30

Kritik özelliklerin hash değerleri üzerinden basit bir XOR,% 99 oranında yeterlidir.

Örneğin:

- (NSUInteger)hash
{
    return [self.name hash] ^ [self.data hash];
}

Mattt Thompson tarafından http://nshipster.com/equality/ adresinde bulunan çözüm (bu soruyu yazısında da belirtti!)


1
Bu cevaptaki sorun, ilkel değerleri hiç dikkate almamasıdır. Ayrıca ilkel değerler hash için de önemli olabilir.
Vive

@Vive Bu sorunların çoğu Swift'te çözülür, ancak bu türler ilkel olduklarından genellikle kendi karmalarını temsil eder.
Yariv Nissim

1
Swift için haklısın, yine de objc ile yazılmış birçok proje var. Cevabınız objc için adanmış olduğu için en azından söz etmeye değer.
Vive

XORing hash değerleri birlikte kötü bir tavsiye, birçok hash çarpışmasına yol açar. Bunun yerine, asal bir sayıyla çarpın ve sonra diğer yanıtlar durumu olarak ekleyin.
fishinMar

27

Bu iş parçacığı benim almak için gereken her şeyi isEqual:ve hashyöntemleri bir catch ile uygulanan son derece yararlı buldum . isEqual:Örnek koddaki nesne örneği değişkenlerini test ederken şunları kullanır:

if (![(id)[self name] isEqual:[aWidget name]])
    return NO;

Nesnelerin birim testimde aynı olduğunu bildiğimde , bu tekrar tekrar başarısız oldu ( yani , NO döndü ) ve hata olmadan . Bunun nedeni, örnek değişkenlerden birinin nil olmasıydı, bu nedenle yukarıdaki ifade:NSString

if (![nil isEqual: nil])
    return NO;

ve nil herhangi bir yönteme cevap vereceğinden, bu tamamen yasaldır ancak

[nil isEqual: nil]

döner nil olan hiçbir nesne ve bir hem vardı, test edilen, bu nedenle zaman sıfır bunlar (eşit değildir düşünülebilir nesne , yani , isEqual:dönmek NO ).

Bu basit düzeltme, if ifadesini şu şekilde değiştirmekti:

if ([self name] != [aWidget name] && ![(id)[self name] isEqual:[aWidget name]])
    return NO;

Bu şekilde, adresleri aynı ise, hem nil hem de her ikisi de aynı nesneyi işaret etseler de yöntem çağrısını atlar, ancak ya nil değilse ya da farklı nesnelere işaret ederse karşılaştırıcı uygun şekilde çağrılır.

Umarım bu birileri kafa çizilmesinden birkaç dakika kurtarır.


20

Karma işlevi, başka bir nesnenin karma değerini çarpışma veya eşleşme olasılığı olmayan yarı benzersiz bir değer oluşturmalıdır.

İşte sınıf örneği değişkenlerinize uyarlanabilen tam sağlama işlevi. 64 / 32bit uygulamalarda uyumluluk için int yerine NSUInteger kullanır.

Eğer sonuç farklı nesneler için 0 olursa, karmaları çarpma riskiyle karşı karşıya kalırsınız. Karma çarpışması, karma işlevine bağlı bazı koleksiyon sınıflarıyla çalışırken beklenmeyen program davranışlarına neden olabilir. Kullanmadan önce hash fonksiyonunuzu test ettiğinizden emin olun.

-(NSUInteger)hash {
    NSUInteger result = 1;
    NSUInteger prime = 31;
    NSUInteger yesPrime = 1231;
    NSUInteger noPrime = 1237;

    // Add any object that already has a hash function (NSString)
    result = prime * result + [self.myObject hash];

    // Add primitive variables (int)
    result = prime * result + self.primitiveVariable; 

    // Boolean values (BOOL)
    result = prime * result + (self.isSelected?yesPrime:noPrime);

    return result;
}

3
Burada bir tane var: Nokta sözdiziminden kaçınmayı tercih ederim, bu yüzden BOOL ifadenizi (örneğin) dönüştürdüm result = prime * result + [self isSelected] ? yesPrime : noPrime;. Sonra bu result(örneğin) ayar olarak bulundu 1231, ben ?operatör öncelikli nedeniyle varsayıyorum . Parantez ekleyerek sorunu çözdüm:result = prime * result + ([self isSelected] ? yesPrime : noPrime);
Ashley

12

Kolay ama verimsiz yol, -hashher örnek için aynı değeri döndürmektir . Aksi takdirde, evet, karma değerini yalnızca eşitliği etkileyen nesnelere dayalı olarak uygulamalısınız. İçinde lax karşılaştırmaları kullanıyorsanız -isEqual:(örneğin büyük / küçük harf duyarsız dize karşılaştırmaları) bu zordur . Ints için, NSNumbers ile karşılaştırma yapmadığınız sürece genellikle int'in kendisini kullanabilirsiniz.

Yine de | = kullanmayın, doygun olacaktır. Bunun yerine ^ = kullanın.

Rastgele eğlenceli gerçek:, [[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]ama [[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]. (rdar: // 4538282, 05-Mayıs-2006'dan beri açık)


1
| = Tam olarak haklısın. Gerçekten öyle demek istemedim. :) + = ve ^ = oldukça eşdeğerdir. Double ve float gibi tamsayı olmayan ilkelleri nasıl ele alırsınız?
Dave Dribin

Rastgele eğlenceli gerçek: Snow Leopard'da test et ... ;-)
Quinn Taylor

Alanları bir karma haline getirmek için OR yerine XOR kullanma konusunda haklıdır. Bununla birlikte, her nesne için aynı -hash değerini döndürme tavsiyesini kullanmayın - kolay olmasına rağmen, nesnenin karma değerini kullanan herhangi bir şeyin performansını ciddi şekilde düşürebilir . Karma, eşit olmayan nesneler için ayrı olmak zorunda değildir, ancak bunu başarabilirseniz, bunun gibi bir şey yoktur.
Quinn Taylor

Açık radar hata raporu kapatıldı. openradar.me/4538282 Bu ne anlama geliyor?
JJD

JJD, hatanın Quinn'in işaret ettiği gibi Mac OS X 10.6'da düzeltildi. (Yorumun iki yaşında olduğunu unutmayın.)
Jens Ayton

9

Yalnızca isEqualdoğru olduğunda karma sağlamanız gerektiğini unutmayın . isEqualYanlış olduğu zaman , muhtemelen olduğu gibi, karma eşit değildir. Dolayısıyla:

Karma basit tutun. En belirgin olan bir üye (veya birkaç üye) değişkeni seçin.

Örneğin, CLPlacemark için yalnızca ad yeterlidir. Evet, aynı ada sahip 2 veya 3 farklı CLPlacemark var ama bunlar nadirdir. Bu karmayı kullanın.

@interface CLPlacemark (equal)
- (BOOL)isEqual:(CLPlacemark*)other;
@end

@implementation CLPlacemark (equal)

...

-(NSUInteger) hash
{
    return self.name.hash;
}


@end

Uyarı Şehri, ülkeyi vb. Belirtmeyi zahmet etmiyorum. Adı yeterlidir. Belki de isim ve KOLEKSİYON.

Hash eşit olarak dağıtılmalıdır. Böylece ^ (xveya işareti) işaretini kullanarak birkaç üye değişkenini birleştirebilirsiniz.

Yani böyle bir şey

hash = self.member1.hash ^ self.member2.hash ^ self.member3.hash

Bu şekilde karma eşit olarak dağıtılacaktır.

Hash must be O(1), and not O(n)

Peki dizide ne yapmalı?

Yine basit. Dizinin tüm üyelerini karma yapmak zorunda değilsiniz. İlk elementi, son elementi, sayımı, belki de bazı orta elementleri hash etmek için yeterli ve hepsi bu.


XORing karma değerleri eşit dağılım sağlamaz.
fishinMar

7

Bekle, elbette bunu yapmanın çok daha kolay bir yolu, önce - (NSString )descriptionnesne durumunuzu geçersiz kılmak ve dize temsilini sağlamaktır (bu dizede nesnenizin tüm durumunu temsil etmelisiniz).

Ardından, aşağıdaki uygulamaları sağlamanız yeterlidir hash:

- (NSUInteger)hash {
    return [[self description] hash];
}

Bu, "iki dize nesnesi eşitse (isEqualToString: yöntemiyle belirlendiği gibi), aynı karma değerine sahip olmaları gerektiği ilkesine dayanır.

Kaynak: NSString Sınıfı Başvurusu


1
Bu, açıklama yönteminin benzersiz olacağını varsayar. Açıklamanın karma değerini kullanmak, açıkça görülmeyen bir bağımlılık ve daha yüksek çarpışma riski yaratır.
Paul Solt

1
+1 Oy verildi. Bu harika bir fikir. Tanımların çarpışmalara neden olduğundan korkuyorsanız, geçersiz kılabilirsiniz.
user4951

Teşekkürler Jim, bunun bir hack olduğunu inkar etmeyeceğim, ama her durumda düşünebileceğim - ve dediğim gibi, geçersiz kılmanızı sağlayarak description, bunun neden daha düşük olduğunu görmüyorum daha yüksek oy alan çözümlerden herhangi biri. Matematiksel olarak en zarif çözüm olmayabilir, ancak hile yapmalıdır. Brian B. belirttiği gibi (bu noktada en çok oylanan cevap): "Ben yeni karma algoritmaları oluşturmaktan kaçının" - kabul etti! - Sadece ! hashNSString
Jonathan Ellis

Güzel bir fikir olduğu için oylandı. Yine de kullanmayacağım çünkü ek NSString tahsislerinden korkuyorum.
karwag

1
Sınıfların çoğu descriptioniçin işaretçi adresini içerdiğinden bu genel bir çözüm değildir . Böylece bu, aynı sınıfın farklı karmaya eşit iki farklı örneğini oluşturur; bu, iki eşit nesnenin aynı karmaya sahip olduğu temel varsayımını ihlal eder!
Diogo T

5

Eşit ve karma sözleşmeler Java dünyasında iyi tanımlanmış ve kapsamlı bir şekilde araştırılmıştır (bakınız @ mipardi'nin cevabı), ancak aynı düşünceler Objective-C için de geçerli olmalıdır.

Eclipse, Java'da bu yöntemleri oluşturmak için güvenilir bir iş çıkarır, bu nedenle Objective-C'ye elle taşınan bir Eclipse örneği:

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if ([self class] != [object class])
        return false;
    MyWidget *other = (MyWidget *)object;
    if (_name == nil) {
        if (other->_name != nil)
            return false;
    }
    else if (![_name isEqual:other->_name])
        return false;
    if (_data == nil) {
        if (other->_data != nil)
            return false;
    }
    else if (![_data isEqual:other->_data])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = 1;
    result = prime * result + [_name hash];
    result = prime * result + [_data hash];
    return result;
}

Ve YourWidgetbir özellik ekleyen bir alt sınıf için serialNo:

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if (![super isEqual:object])
        return false;
    if ([self class] != [object class])
        return false;
    YourWidget *other = (YourWidget *)object;
    if (_serialNo == nil) {
        if (other->_serialNo != nil)
            return false;
    }
    else if (![_serialNo isEqual:other->_serialNo])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = [super hash];
    result = prime * result + [_serialNo hash];
    return result;
}

Bu uygulama isEqual:, Apple'daki örnekteki bazı alt sınıf tuzaklarından kaçınır :

  • Apple'ın sınıf testi other isKindOfClass:[self class] , iki farklı alt sınıfı için asimetriktir MyWidget. Eşitliğin simetrik olması gerekir: a = b ve yalnızca b = a ise. Bu, testi olarak değiştirerek kolayca düzeltilebilir other isKindOfClass:[MyWidget class], daha sonra tüm MyWidgetalt sınıflar karşılıklı olarak karşılaştırılabilir.
  • Bir isKindOfClass:alt sınıf testi kullanmak, alt sınıfların isEqual:rafine bir eşitlik testi ile geçersiz kılınmasını önler . Bunun nedeni eşitliğin geçişli olması gerektiğidir: a = b ve a = c ise b = c ise. Eğer birMyWidget örnek ikiye eşitseYourWidget örneğe , bu YourWidgetörnekler serialNofarklı olsa bile birbirleriyle eşit karşılaştırmalıdır .

İkinci konu, sadece aynı sınıfa ait olmaları durumunda nesnelerin eşit olduğu düşünülerek düzeltilebilir. [self class] != [object class] burada test yapılır. Tipik uygulama sınıfları için için bu en iyi yaklaşım gibi görünmektedir.

Bununla birlikte, isKindOfClass:testin tercih edilebilir olduğu durumlar kesinlikle vardır . Bu, çerçeve sınıflarına uygulama sınıflarından daha tipiktir . Örneğin, herhangi biri NSString, NSStringaltta yatan karakter sırasına bakılmaksızın, aynı temel karakter dizisine sahip diğerleriyle karşılaştırılmalıdır .NSString / NSMutableStringayrımına hangi özel sınıflardan bağımsız olarak .NSString sınıf kümesindeki dahil .

Bu gibi durumlarda, isEqual:iyi tanımlanmış, iyi belgelenmiş bir davranışa sahip olmalı ve alt sınıfların bunu geçersiz kılamayacağı açıkça belirtilmelidir. Java'da, 'geçersiz kılma yok' kısıtlaması, eşittir ve hashcode yöntemleri olarak işaretlenerek uygulanabilir final, ancak Objective-C'nin eşdeğeri yoktur.


@adubr Bu son iki paragrafımda kapsanıyor. Bu fokal değil MyWidgetSınıf küme olduğu için anlaşılır.
jedwidz

5

Bu doğrudan sorunuza cevap vermiyor (ama daha önce karma üretmek için MurmurHash'ı kullandım: murmurhash

Sanırım nedenini açıklamalıyım: üfürüm hızlı kanlı ...


2
Rastgele sayı (ve ayrıca Objective-C nesneleriyle ilgili değildir) kullanan bir void * anahtarı için benzersiz karelere odaklanan bir C ++ kütüphanesi gerçekten yararlı bir öneri değildir. -Hash yöntemi her seferinde tutarlı bir değer döndürmelidir, yoksa tamamen yararsız olacaktır. Nesne -hash öğesini çağıran ve her seferinde yeni bir değer döndüren bir koleksiyona eklenirse, yinelenmeler hiçbir zaman algılanmaz ve nesneyi de koleksiyondan alamazsınız. Bu durumda, "karma" terimi güvenlik / kriptografideki anlamdan farklıdır.
Quinn Taylor

3
murmurhash bir şifreleme karma işlevi değildir. Lütfen yanlış bilgi göndermeden önce gerçeklerinizi kontrol edin. Murmurhash , özel obj -c sınıflarının (özellikle de çok sayıda NSDtatasınız varsa) hash etmek için yararlı olabilir , çünkü son derece hızlıdır. Bununla birlikte, size "sadece objektif-c'yi almak" için en iyi tavsiyenin olmadığını belki de öneriyorum, ancak lütfen soruya orijinal cevabımdaki öneki not edin.
schwa


4

Ben de bir Objective C acemi, ama burada Objective C kimlik ile eşitlik hakkında mükemmel bir makale buldum . Okuduğumdan, varsayılan karma işlevini (benzersiz bir kimlik sağlamalıdır) koruyabilir ve veri değerlerini karşılaştırmak için isEqual yöntemini uygulayabilirsiniz.


Ben bir Kakao / Objective C acemi, ve bu cevap ve bağlantı gerçekten tüm daha gelişmiş şeyler alt satırda kesmek yardımcı oldu - karma hakkında endişelenmenize gerek yok - sadece isEqual: yöntemini uygulamak. Teşekkürler!
John Gallagher

@ Ceperry'nin bağlantısını kaçırmayın. Equality vs IdentityKarl Kraft'ın makalesi gerçekten iyi.
JJD

6
@John: Sanırım makaleyi tekrar okumalısın. “Eşit olan örneklerin eşit karma değerlere sahip olması gerektiğini” çok açık bir şekilde söyler. Geçersiz kılarsanız isEqual:, geçersiz kılmanız gerekir hash.
Steve Madsen

3

Quinn, burada üfürüm karması referansının işe yaramaz olması yanlıştır. Quinn, karmaşanın arkasındaki teoriyi anlamak istediğinizde haklıdır. Üfürüm bu teorinin çoğunu bir uygulamaya damıtır. Bu uygulamanın bu özel uygulamaya nasıl uygulanacağının araştırılması, araştırmaya değer.

Buradaki kilit noktalardan bazıları:

Tcurdt'ın örnek işlevi, '31'in iyi bir çarpan olduğunu, çünkü asal olduğunu göstermektedir. Başbakan olmanın gerekli ve yeterli bir koşul olduğunu göstermek gerekir. Aslında 31 (ve 7) muhtemelen iyi primer değildir çünkü 31 == -1% 32. Ayarlanan bitlerin yaklaşık yarısı ve açık bitlerin yarısı açık olan tek bir çarpan muhtemelen daha iyi olacaktır. (Üfürüm karma çarpma sabiti bu özelliğe sahiptir.)

Çarpma işleminden sonra sonuç değeri bir kaydırma ve xor ile ayarlanırsa, bu tür sağlama işlevi büyük olasılıkla daha güçlü olacaktır. Çarpma, yazmaçın üst ucunda çok sayıda bit etkileşimi ve yazmaçın alt ucunda düşük etkileşim sonuçları üretme eğilimindedir. Kayma ve xor, kaydın alt ucundaki etkileşimleri arttırır.

İlk sonucun, bitlerin yaklaşık yarısının sıfır ve bitlerin yaklaşık yarısının bir değere ayarlanması da faydalı olma eğilimindedir.

Elemanların birleştirilme sırasına dikkat etmek faydalı olabilir. Muhtemelen ilk önce booleans ve değerlerin güçlü bir şekilde dağıtılmadığı diğer unsurları işlemelidir.

Hesaplamanın sonuna birkaç ekstra bit karıştırma aşaması eklemek yararlı olabilir.

Bu uygulama için üfürüm karma aslında hızlı olup olmadığı açık bir sorudur. Üfürüm hash'ı, her bir giriş kelimesinin bitlerini önceden karıştırır. Birden çok giriş kelimesi paralel olarak işlenebilir, bu da çok sayıdaki boru hattı cpus'a yardımcı olur.


3

@ Tcurdt'ın cevabını @ oscar-gomez'in mülk adlarını alma cevabı ile birleştirerek , hem isEqual hem de hash için kolay bir bırakma çözümü oluşturabiliriz:

NSArray *PropertyNamesFromObject(id object)
{
    unsigned int propertyCount = 0;
    objc_property_t * properties = class_copyPropertyList([object class], &propertyCount);
    NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity:propertyCount];

    for (unsigned int i = 0; i < propertyCount; ++i) {
        objc_property_t property = properties[i];
        const char * name = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:name];
        [propertyNames addObject:propertyName];
    }
    free(properties);
    return propertyNames;
}

BOOL IsEqualObjects(id object1, id object2)
{
    if (object1 == object2)
        return YES;
    if (!object1 || ![object2 isKindOfClass:[object1 class]])
        return NO;

    NSArray *propertyNames = PropertyNamesFromObject(object1);
    for (NSString *propertyName in propertyNames) {
        if (([object1 valueForKey:propertyName] != [object2 valueForKey:propertyName])
            && (![[object1 valueForKey:propertyName] isEqual:[object2 valueForKey:propertyName]])) return NO;
    }

    return YES;
}

NSUInteger MagicHash(id object)
{
    NSUInteger prime = 31;
    NSUInteger result = 1;

    NSArray *propertyNames = PropertyNamesFromObject(object);

    for (NSString *propertyName in propertyNames) {
        id value = [object valueForKey:propertyName];
        result = prime * result + [value hash];
    }

    return result;
}

Artık özel sınıfınızda kolayca uygulayabilir isEqual:ve hash:

- (NSUInteger)hash
{
    return MagicHash(self);
}

- (BOOL)isEqual:(id)other
{
    return IsEqualObjects(self, other);
}

2

Oluşturma işleminden sonra değiştirilebilen bir nesne oluşturuyorsanız, nesne bir koleksiyona eklenirse karma değerinin değişmemesi gerektiğini unutmayın . Pratik olarak, bu, karma değerinin ilk nesne oluşturma noktasından sabitlenmesi gerektiği anlamına gelir. Daha fazla bilgi için Apple'ın NSObject protokolünün -hash yöntemiyle ilgili belgelerine bakın :

Nesnenin koleksiyondaki konumunu belirlemek için karma değerleri kullanan bir koleksiyona değiştirilebilir bir nesne eklenirse, nesne koleksiyondayken nesnenin karma yöntemiyle döndürülen değer değişmemelidir. Bu nedenle, ya karma yöntemi nesnenin dahili durum bilgilerine dayanmamalı ya da nesne koleksiyondayken nesnenin dahili durum bilgilerinin değişmediğinden emin olmalısınız. Böylece, örneğin, değişken bir sözlük bir karma tabloya konabilir, ancak oradayken değiştirmemelisiniz. (Belirli bir nesnenin bir koleksiyonda olup olmadığını bilmek zor olabilir.)

Potansiyel olarak hash aramalarını çok daha az verimli hale getirdiğinden bu benim için tam bir balina avcısı gibi geliyor, ancak sanırım dikkatli olun ve belgelerin söylediklerini takip edin.


1
Hash belgelerini yanlış okuyorsunuz - bu aslında "ya" ya da "bir durum. Nesne değişirse, karma genellikle de değişir. Bu gerçekten programcı için bir uyarıdır, eğer karma bir nesneyi mutasyona uğratmanın sonucu olarak değişirse, nesneyi karma kullanan bir koleksiyonda bulunurken beklenmedik davranışlara neden olur. Nesne böyle bir durumda "güvenli bir şekilde değişebilir" ise, karmayı değişebilir durumla alakasız hale getirmekten başka seçeneğiniz yoktur. Bu özel durum benim için garip geliyor, ancak geçerli olduğu durumlarda nadiren var.
Quinn Taylor

1

Burada tam bir boffin çalma riskini göze alırsam özür dilerim ama ... ... kimse 'en iyi uygulamaları' takip etmek için kesinlikle, hedef nesnenizin sahip olduğu tüm verileri dikkate almayacak eşit bir yöntem belirtmemeniz gerektiğinden bahsetmez. eşittir uygulanırken veriler nesnenize göre birleştirilir, bunun bir ortağı göz önünde bulundurulmalıdır. Bir karşılaştırmada dikkate almak istemiyorsanız, 'yaş' deyin, o zaman bir karşılaştırıcı yazmalı ve bunu karşılaştırmaları isEqual: yerine yapmak için kullanmalısınız.

Eşitlik karşılaştırmasını keyfi olarak gerçekleştiren bir isEqual: yöntemi tanımlarsanız, eşittir yorumunuzdaki 'bükümü' unuttuğunuzda, bu yöntemin başka bir geliştirici veya hatta kendiniz tarafından kötüye kullanılması riskine maruz kalırsınız.

Ergo, bu karma hakkında büyük bir soru-cevap olmasına rağmen, normalde karma yöntemini yeniden tanımlamanız gerekmez, bunun yerine muhtemelen geçici bir karşılaştırıcı tanımlamanız gerekir.

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.