Dizeler için İyi Karma İşlevi


160

Dizeler için iyi bir karma işlevi düşünmeye çalışıyorum. Ve dizede ilk beş karakter için unicode değerlerini özetlemek iyi bir fikir olabileceğini düşünüyordum (beş olduğu varsayılarak, aksi takdirde bittiği yerde dur). Bu iyi bir fikir mi yoksa kötü bir fikir mi?

Bunu Java'da yapıyorum, ancak bunun çok fazla fark yaratacağını hayal edemezdim.


4
İyi karma fonksiyonları büyük ölçüde karma girdiye ve algoritmanın gereksinimlerine bağlıdır. Örneğin tüm dizeleriniz aynı beş karakterle başlıyorsa böyle bir karma çok iyi olmayacaktır. Ayrıca normal bir dağılım ile sonuçlanma eğilimi gösterir.
WhirlWind


14
Neden kullanamazsınız Stringkendi s' hashCode()?
Bart Kiers

@WhirlWind, true, dizelerin ne olacağından emin değilim, bunun dışında muhtemelen İngilizce metin olacak.
Leif Andersen

@Barl, esas olarak profesörüm bize kendi karma fonksiyonumuzu uygulamamızı söylediğinden ve Java kullanmak istemediğimin nedeni jenerik olduğundan ve daha spesifik bir karma fonksiyonun daha iyi olacağını hayal edebileceğimden kaynaklanıyordu.
Leif Andersen

Yanıtlar:


161

Genellikle toplamları yapmazdı sağlamalarının, aksi stopve potsaynı karma sahip olacaktır.

ve bunu ilk n karakterle sınırlamazsınız çünkü aksi takdirde ev ve evler aynı hash'a sahip olurdu.

Genellikle hash değerleri alır ve bir asal sayı ile çarpar (benzersiz hash üretme olasılığını artırır) Böylece şöyle bir şey yapabilirsiniz:

int hash = 7;
for (int i = 0; i < strlen; i++) {
    hash = hash*31 + charAt(i);
}

@jonathanasdf Size her zaman benzersiz bir hash anahtarı verdiğini nasıl söyleyebilirsiniz? Matematiksel kanıt var mı? Bence daha büyük bir asal sayı ile hash modunu almalıyız, aksi takdirde taşma problemi meydana gelir.
devsda

17
@devsda Her zaman benzersiz demedi, benzersiz olma olasılığının daha yüksek olduğunu söyledi. Nedeniyle ilgili olarak, Google'da yapılan hızlı bir arama bu makaleyi ortaya koymaktadır: computinglife.wordpress.com/2008/11/20/… 31'in Java string karma için neden kullanıldığını açıklamaktadır. Matematiksel bir kanıt yoktur, ancak asalların neden daha iyi çalıştığına dair genel kavramı açıklar.
Pharap

2
Daha iyi karma yapma fikrini açıkladığınız için çok teşekkürler. Sadece iki kez kontrol etmek için - hashCode () dönüş değeri, nesneyi kaydetmeden önce bazı tablo dizinleriyle eşlemek için Java tarafından kullanılacaktır. Bu nedenle, hashCode () m değerini döndürürse, k boyutundaki tablonun bir dizinini almak için (m mod k) gibi bir şey yapar. Bu doğru mu?
whitehat

1
"hash = hash * 31 + charAt (i);" spot, tops, stop, ops ve tencere için aynı hash üretir.
Jack Straub

1
@maq Doğru olduğuna inanıyorum. Ne düşündüğümü bilmiyorum.
Jack Straub

139

Bu bir güvenlik şeyiyse, Java kripto kullanabilirsiniz:

import java.security.MessageDigest;

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(stringToEncrypt.getBytes());
String encryptedString = new String(messageDigest.digest());

93
Güzel. Büyük bir ceset üzerinde istatistiksel NLP yapan bir makine öğrenme uygulamam var. Metindeki orijinal kelimeler üzerinde birkaç ilk morfolojik normalizasyon geçişinden sonra, dize değerlerini atıyorum ve bunun yerine karma kodları kullanıyorum. Bütün korpusumda yaklaşık 600.000 benzersiz kelime var ve varsayılan java hashcode işlevini kullanarak yaklaşık% 3.5 çarpışma yaşıyordum. Ama ben dize değerini SHA-256 ve sonra sindirilmiş dize bir hashcode oluşturmak, çarpışma oranı% 0.0001 daha azdır. Teşekkürler!
benjismith

3
Çarpışmalar ve kelimelerin sayısı hakkında bilgi verdiğiniz için teşekkür ederiz. Çok yararlı.
philipp

19
@benjismith Milyonda bir kişi çok büyük ... "% 0.0001'den az", "tam 0" demenin eğik bir yolu mu? Gerçekten bir SHA-256 çarpışması gördüğünüzden şüpheliyim, çünkü bu hiçbir zaman, hiçbir yerde, hiç gözlemlenmedi; 160 bit SHA-1 için bile değil. Aynı SHA-256'yı üreten iki dizeniz varsa, güvenlik topluluğu onları görmek ister; dünyaca ünlü olacaksın ... çok belirsiz bir şekilde. Bkz . SHA İşlevlerinin Karşılaştırması
Tim Sylvester

7
@TimSylvester, yanlış anladın. SHA-256 çarpışmalarını bulamadım. SHA-256'yı hesapladım ve daha sonra 32 bitlik bir karta ihtiyaç duyduğum için sonuçtaki bayt dizilerini tipik bir Java "hashCode" işlevine besledim. Çarpışmaları burada buldum. Dikkat çekici bir şey yok :)
benjismith

1
'Karma' ve 'şifreleme' arasında bir fark yok mu? MessageDigest'in tek yönlü bir karma işlevi olduğunu anlıyorum, değil mi? Ayrıca, işlevi kullandığımda, dosyayı LibreOffice'de açtığımda karma dizeyi bir sürü önemsiz UTF karakteri olarak aldım. Karma dizeyi önemsiz UTF karakterleri yerine rastgele bir grup alfasayısal karakter olarak almak mümkün müdür?
Nav

38

Muhtemelen String.hashCode () kullanmalısınız .

Gerçekten hashCode'u kendiniz uygulamak istiyorsanız:

Performansı artırmak için bir nesnenin önemli bölümlerini karma kod hesaplamasının dışında bırakmaya çalışmayın - Joshua Bloch, Etkili Java

Yalnızca ilk beş karakteri kullanmak kötü bir fikirdir . URL'ler gibi hiyerarşik adları düşünün: hepsinin aynı karma kodu olacaktır (çünkü hepsi "http: //" ile başlar, bu da korkunç bir performans gösteren karma bir haritada aynı grup altında depolandıkları anlamına gelir.

İşte " Effective Java " dan String hashCode üzerinde açıklanmış bir savaş hikayesi :

1.2'den önceki tüm sürümlerde uygulanan String sağlama işlevi, ilk karakterle başlayarak dize boyunca eşit aralıklarla en fazla on altı karakteri inceledi. URL'ler gibi büyük hiyerarşik ad koleksiyonları için bu karma işlevi korkunç davranışlar gösterdi.


1
Bir çift karma toplama kullanıyorsa, ilk karma gerçekten hızlı ve kirli olması faydalı olabilir. Birinin bin uzun bir dizesi varsa, bunların yarısı a'nın bir işlevle belirli bir değere eşlenmiş ve yarısı farklı değerlere eşlenmişse, tek karma tablodaki performans kötü olur, ancak çift ikinci karmaın tüm dizeyi incelediği karma tablo tekli karma tablonun neredeyse iki katı olabilir (çünkü dizelerin yarısının tam karma olması gerekmez). Yine de standart Java koleksiyonlarının hiçbiri iki kez karma yapmaz.
supercat

Etkili Java bağlantı @Frederik bozuldu
kg'lık

17

Bunu Java ile yapıyorsanız neden yapıyorsunuz? Sadece .hashCode()dizeyi arayın


2
Ben sınıfın bir parçası olarak yapıyorum ve ödev bir kısmı birkaç farklı karma fonksiyonları yazmaktır. Profesör bize 'daha iyi' olanlar için dışarıdan yardım almamızı söyledi.
Leif Andersen

20
JVM sürümlerinde ve uygulamalarında tutarlı olmanız gerekiyorsa, güvenmemelisiniz .hashCode(). Aksine, bilinen bir algoritma kullanın.
Stephen Ostermiller

7
Algoritması String::hashCodeJDK'da belirtildiğinden, sınıfın varlığı kadar taşınabilir java.lang.String.
yshavit


8

Nick tarafından sağlanan bu işlev iyidir, ancak String'e dönüşüm yapmak için yeni String (bayt [] bayt) kullanırsanız, başarısız oldu. Bunu yapmak için bu işlevi kullanabilirsiniz.

private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public static String byteArray2Hex(byte[] bytes) {
    StringBuffer sb = new StringBuffer(bytes.length * 2);
    for(final byte b : bytes) {
        sb.append(hex[(b & 0xF0) >> 4]);
        sb.append(hex[b & 0x0F]);
    }
    return sb.toString();
}

public static String getStringFromSHA256(String stringToEncrypt) throws NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    messageDigest.update(stringToEncrypt.getBytes());
    return byteArray2Hex(messageDigest.digest());
}

Belki bu birine yardım edebilir


Bayt dizisini messageDigest.update () öğesine iletebilirsiniz.
szgal

byteArray2Hex () - aradığım şey tam olarak bu! Çok teşekkürler :)
Krzysiek


5

FNV-1'in dizeler için iyi bir sağlama işlevi olduğu söylenir.

Uzun dizeler için (örneğin, yaklaşık 200 karakterden daha uzun), MD4 karma işlevinden iyi performans elde edebilirsiniz . Kriptografik bir işlev olarak, yaklaşık 15 yıl önce kırıldı, ancak kriptografik olmayan amaçlar için hala çok iyi ve şaşırtıcı derecede hızlı. Java bağlamında, 16 bit chardeğerleri 32 bit kelimelere dönüştürmeniz gerekir , örneğin bu değerleri çiftler halinde gruplandırarak. Java'da MD4'ün hızlı bir şekilde uygulanması sphlib'de bulunabilir . Muhtemelen bir sınıf ödevi bağlamında aşırıya kaçmak, ama aksi halde denemeye değer.


Bu karma işlevi, java ile gelen işlevden çok daha iyidir.
clankill3r


1

İşte birçok farklı hash fonksiyonunu açıklayan bir bağlantı , şimdilik sizin özel probleminiz için ELF hash fonksiyonunu tercih ediyorum. Girdi olarak gelişigüzel uzunlukta bir dize alır.


1

sdbm: bu algoritma sdbm (ndbm'nin ortak etki alanı yeniden uygulaması) veritabanı kitaplığı için oluşturuldu

static unsigned long sdbm(unsigned char *str)
{   
    unsigned long hash = 0;
    int c;
    while (c = *str++)
            hash = c + (hash << 6) + (hash << 16) - hash;

    return hash;
}

0
         public String hashString(String s) throws NoSuchAlgorithmException {
    byte[] hash = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        hash = md.digest(s.getBytes());

    } catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < hash.length; ++i) {
        String hex = Integer.toHexString(hash[i]);
        if (hex.length() == 1) {
            sb.append(0);
            sb.append(hex.charAt(hex.length() - 1));
        } else {
            sb.append(hex.substring(hex.length() - 2));
        }
    }
    return sb.toString();
}

-1

Dize için iyi bir hast işlevi geliştirmeye çalışırken tek sayı ile çalışmak iyi bir fikirdir. bu işlev bir dize alır ve bir dizin değeri döndürür, şimdiye kadar çalışması oldukça iyi. ve daha az çarpışma var. endeks 0 - 300 arasında değişebilir belki daha da fazla, ama ben şimdiye kadar "elektromekanik mühendisliği" gibi uzun kelimelerle daha da yükselmedim

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += 7*n%31;
    }
    return u%139;
}

Yapabileceğiniz başka bir şey de "ayı" (0 * b) + (1 * e) + (2 * a) + (3 * r) kelimesi gibi arttıkça her karakter int ayrıştırma dizini ile çarpmaktır. oynamak için bir int değeri. yukarıdaki ilk hash fonksiyonu "burada" ve "duymak" ile çarpışır, ancak yine de bazı eşsiz değerler verir. Aşağıdaki karakter "burada" ve "duymak" ile çarpışmaz, çünkü her karakteri arttıkça indeksle çarparım.

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += i*n%31;
    }
    return u%139;
}

-1

İşte oluşturduğum bir karma tablo için kullandığım basit bir karma işlevi. Temelde bir metin dosyası almak ve her kelimeyi alfabetik sırayı temsil eden bir dizinde saklamak içindir.

int generatehashkey(const char *name)
{
        int x = tolower(name[0])- 97;
        if (x < 0 || x > 25)
           x = 26;
        return x;
}

Bunun temel olarak yaptığı şey, ilk harflerine göre sözcüklerin özetlenmesidir. Yani, 'a' ile başlayan kelime 0 hash anahtarını alır, 'b' 1 ve benzerlerini alır ve 'z' 25 olur. Sayılar ve semboller 26 hash anahtarına sahip olur. ; Belirli bir kelimenin hash tablosunda, alfabetik sıraya göre dizine ekleneceği yeri kolayca ve hızlı bir şekilde hesaplayabilirsiniz, şuna benzer: Kod burada bulunabilir: https://github.com/abhijitcpatil/general

Aşağıdaki metni girdi olarak vermek: Atticus bir gün Jem'e, “Arka bahçedeki teneke kutulara ateş etmeyi tercih ederim, ama kuşların peşinden gideceğini biliyorum. Onlara vurabilirsen, istediğin tüm mavi alakargaları vur ama alaycı kuşu öldürmenin günah olduğunu unutma. ” Atticus'un bir şeyler yapmanın günah olduğunu söylediğini duyduğum tek zamandı ve Bayan Maudie'den bunu sordum. “Baban haklı,” dedi. “Alaycı kuşlar müzik dinlemekten başka bir şey yapmıyorlar. İnsanların bahçelerini yemiyorlar, mısır beşiklerinde yuva yapmıyorlar, tek bir şey yapmıyorlar ama kalplerini bizim için söylüyorlar. Bu yüzden alaycı kuşu öldürmek günahtır.

Bu çıktı:

0 --> a a about asked and a Atticus a a all after at Atticus
1 --> but but blue birds. but backyard
2 --> cribs corn can cans
3 --> do dont dont dont do dont do day
4 --> eat enjoy. except ever
5 --> for for fathers
6 --> gardens go
7 --> hearts heard hit
8 --> its in it. I it I its if I in
9 --> jays Jem
10 --> kill kill know
11 --> 
12 --> mockingbird. music make Maudie Miss mockingbird.”
13 --> nest
14 --> out one one only one
15 --> peoples
16 --> 17 --> right remember rather
18 --> sin sing said. she something sin say sin Shoot shot said
19 --> to Thats their thing they They to thing to time the That to the the tin to
20 --> us. up us
21 --> 
22 --> why was was want
23 --> 
24 --> you you youll you
25 --> 
26 --> Mockingbirds  Your em Id

2
İyi bir karma işlevi değerleri kovalara eşit olarak dağıtır.
Jonathan Peterson

-1

Bu herhangi bir çarpışmayı önleyecek ve biz de hesaplamalarda kaymayı kullanana kadar hızlı olacaktır.

 int k = key.length();
    int sum = 0;
    for(int i = 0 ; i < k-1 ; i++){
        sum += key.charAt(i)<<(5*i);
    }
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.