İki anahtarla (Anahtar-Çift, Değer) bir HashMap nasıl oluşturulur?


118

2B bir Tamsayı dizisine sahibim. Bunların bir HashMap'e yerleştirilmesini istiyorum. Ancak HashMap'teki öğelere Array Index'e dayalı olarak erişmek istiyorum. Gibi bir şey:

A [2] [5] için, map.get(2,5)o anahtarla ilişkili bir değer döndürür. Ancak bir çift anahtarla nasıl hashMap oluşturabilirim? Veya genel olarak, birden çok anahtar: Map<((key1, key2,..,keyN), Value)öğeye get (key1, key2, ... keyN) kullanarak erişebileceğim bir şekilde.

DÜZENLEME: Soruyu gönderdikten 3 yıl sonra, ona biraz daha eklemek istiyorum

İçin başka bir yolla karşılaştım NxN matrix.

Dizi dizinleri ive aşağıdaki şekilde jtek bir keyşekilde gösterilebilir:

int key = i * N + j;
//map.put(key, a[i][j]); // queue.add(key); 

Ve endeksler şu şekilde geri çekilebilir key:

int i = key / N;
int j = key % N;

Basit bir çözüm, bir anahtarı diğer hashmap'te eşlemektir.
Mihai8

1
Lütfen sorudaki soruya cevap vermeyin. Düzenlemeniz ilginç, bu yüzden bir cevap olarak göndermekten çekinmeyin.
Ole VV

@Crocode vay! Düzenleme'deki cevabın arkasındaki matematik parıldıyor. Genel olarak herhangi iki tamsayı i ve j için çalışıp çalışmadığını merak ediyorum.
likejudo

@Crocode, N'nin katları ise i ve j arasında geçiş yapar mı?
likejudo

Yanıtlar:


190

Birkaç seçenek var:

2 boyut

Haritaların haritası

Map<Integer, Map<Integer, V>> map = //...
//...

map.get(2).get(5);

Sarmalayıcı anahtar nesnesi

public class Key {

    private final int x;
    private final int y;

    public Key(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Key)) return false;
        Key key = (Key) o;
        return x == key.x && y == key.y;
    }

    @Override
    public int hashCode() {
        int result = x;
        result = 31 * result + y;
        return result;
    }

}

Uygulama equals()ve hashCode()burada çok önemlidir. O zaman sadece şunları kullanırsınız:

Map<Key, V> map = //...

ve:

map.get(new Key(2, 5));

Table Guava dan

Table<Integer, Integer, V> table = HashBasedTable.create();
//...

table.get(2, 5);

Tablealtındaki haritaların haritasını kullanır .

N boyutları

Özel Keysınıfın n boyutlarına ölçeklenen tek yaklaşım olduğuna dikkat edin . Ayrıca şunları da düşünebilirsiniz:

Map<List<Integer>, V> map = //...

ancak bu hem performans açısından hem de okunabilirlik ve doğruluk açısından korkunç (liste boyutunu zorlamanın kolay bir yolu değil).

Belki kayıtların ve casesınıfların olduğu Scala'ya bir göz atabilirsin (tüm Keysınıfı tek satırla değiştirmek ).


3
Merhaba, diğerleri hashCode yaparken x 'veya iki değere sahiptir. Neden 31 kullanıyorsun? 32 bitlik tamsayı ile bir ilgisi olduğunu düşünmüştüm, ama düşündüğümde mantıklı değil çünkü x = 1 ve y = 0 hala x = 0 ve y = 31 ile aynı hashcode ile eşleşiyor
pete

1
@pete Joshua Bloch, Etkili Java ch 3. s 9.'da, "1. Bazı sabit sıfır olmayan değerler, diyelim ki 17, sonuç ... adlı bir int değişkeninde saklayın" önerir, bu da çarpışma açısından daha iyi sonuç verir. bir asal. Ayrıca bkz .: stackoverflow.com/questions/3613102/…
fncomp

2
Sarmalayıcı anahtar nesnesi yerine neden Map.Entry<K, V>anahtar olarak kullanılmasın?
Roland

1
Peki ya Map<Pair<Key1, Key2>, Value>?
Joaquin Iurchuk

1
hashCode()tek bir satırla da uygulanabileceğini unutmayınObjects.hash(x,y)
xdavidliu

23

Kendi anahtar çifti nesnenizi oluşturduğunuzda, birkaç şeyle karşılaşmalısınız.

Öncelikle, uygulamanın bilmelidir hashCode()ve equals(). Bunu yapmanız gerekecek.

İkincisi, uygularken hashCode()nasıl çalıştığını anladığınızdan emin olun. Verilen kullanıcı örneği

public int hashCode() {
    return this.x ^ this.y;
}

aslında yapabileceğiniz en kötü uygulamalardan biridir. Nedeni basit: çok sayıda eşit karmaya sahipsiniz! Ve hashCode()en iyi ihtimalle nadir, benzersiz olma eğiliminde olan int değerleri döndürmelidir. Bunun gibi bir şey kullanın:

public int hashCode() {
  return (X << 16) + Y;
}

Bu hızlıdır ve -2 ^ 16 ile 2 ^ 16-1 (-65536 ile 65535) arasındaki anahtarlar için benzersiz karmalar döndürür. Bu hemen hemen her duruma uyar. Çok nadiren bu sınırların dışına çıkarsınız.

Üçüncüsü, uygularken equals()ne için kullanıldığını da bilin ve nesneler oldukları için anahtarlarınızı nasıl yarattığınızın farkında olun. Çoğunlukla gereksiz şeyler yaparsınız, eğer ifadeler neden olursa her zaman aynı sonucu alırsınız.

Bunun gibi anahtarlar oluşturursanız: anahtarlarınızın map.put(new Key(x,y),V);referanslarını asla karşılaştırmazsınız. Çünkü haritaya her erişmek istediğinizde, buna benzer bir şey yapacaksınız map.get(new Key(x,y));. Dolayısıyla sizin equals()gibi bir ifadeye ihtiyaç duymaz if (this == obj). Asla olmayacak .

Yerine if (getClass() != obj.getClass())senin içinde equals()daha iyi kullanılması if (!(obj instanceof this)). Alt sınıflar için bile geçerli olacaktır.

Yani karşılaştırmanız gereken tek şey aslında X ve Y'dir. Yani equals()bu durumda en iyi uygulama şöyle olacaktır:

public boolean equals (final Object O) {
  if (!(O instanceof Key)) return false;
  if (((Key) O).X != X) return false;
  if (((Key) O).Y != Y) return false;
  return true;
}

Sonuçta anahtar sınıfınız şu şekildedir:

public class Key {

  public final int X;
  public final int Y;

  public Key(final int X, final int Y) {
    this.X = X;
    this.Y = Y;
  }

  public boolean equals (final Object O) {
    if (!(O instanceof Key)) return false;
    if (((Key) O).X != X) return false;
    if (((Key) O).Y != Y) return false;
    return true;
  }

  public int hashCode() {
    return (X << 16) + Y;
  }

}

Nihai olmaları ve hassas bilgiler içermemeleri nedeniyle boyut endekslerinize Xve Ybir genel erişim seviyesi verebilirsiniz . Emin olsun% 100 değilim privateerişim seviyesi düzgün çalışır herhangi döküm yaparken durumda Objecta Key.

Finalleri merak ediyorsanız, değerin örneklemede belirlenen ve asla değişmeyen - ve bu nedenle bir nesne sabit olduğu - her şeyi nihai olarak beyan ederim.


7

Birden çok anahtarı olan bir karma haritanız olamaz, ancak anahtar olarak birden çok parametre alan bir nesneye sahip olabilirsiniz.

Bir x ve y değeri alan Index adında bir nesne oluşturun.

public class Index {

    private int x;
    private int y;

    public Index(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        return this.x ^ this.y;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Index other = (Index) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }
}

O zaman HashMap<Index, Value>sonucunu al. :)


4
Sen geçersiz kılmak gerekir hashCodeve equals.
Tom Hawtin - tackline

4
hashCode uygulaması (2,1) ve (1,2) arasında ayrım yapmadı
user1947415

1
Bu bir çarpışmadır. Hashcode'un her farklı nesne için farklı bir değeri garanti etmesi gerekmez. @ user1947415
Ajak6

6

MultiKeyMap ortak koleksiyonlarda uygulanmıştır


@Wilson Bağlantıyı şimdi düzelttim, akran incelemesi için bekliyorum
computingfreak

@computingfreak olumlu bir görüntü geldi gibi görünüyor. Yaşa! NB bu en iyi cevap IMHO. Uzman Apache mühendisleriyle çok yararlı (her zamanki gibi) ancak nihayetinde sıradan bir işlevsellik parçası üzerinde rekabet etmek için saatler harcamaktan hoşlanmıyorsanız.
mike kemirgen

4

İki olasılık. Ya birleşik bir anahtar kullanın:

class MyKey {
    int firstIndex;
    int secondIndex;
    // important: override hashCode() and equals()
}

Veya Harita Haritası:

Map<Integer, Map<Integer, Integer>> myMap;

2
Yalnızca burada performans veya bellek kullanımını önemsemiyorsanız (yani harita küçükse) veya aynı ilk dizine sahip çok sayıda anahtar varsa bir harita haritası kullanın - çünkü bu çözüm bir HashMap nesnesinin ek yükünü ödemek anlamına gelir. her bir benzersiz ilk dizin.
BeeOnRope

1
Bu yanıtı iyileştirmek için burada geçersiz kılma hashCodeve equalsyöntemler hakkında bilgi var .
Pshemo


1

Bileşik anahtarınızı temsil edecek bir değer sınıfı oluşturun, örneğin:

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

geçersiz kılmaya equals()ve hashCode()doğru bir şekilde yapmaya özen göstererek . Bu çok iş gibi görünüyorsa, Pairdiğerleri arasında apache commons tarafından sağlananlar gibi bazı hazır jenerik kapsayıcıları düşünebilirsiniz .

Burada, Guava Tablosunu kullanmak gibi diğer fikirlerle birlikte birçok benzer soru var, ancak anahtarlarınızın farklı türlere sahip olmasına izin vermesine rağmen, sizin durumunuzda (bellek kullanımında ve karmaşıklıkta) anahtarlarınızın her ikisinin de tamsayı olduğunu anladığım için bu aşırı olabilir.


1

Eğer iki tamsayı iseler, hızlı ve kirli bir numara deneyebilirsiniz: Map<String, ?>anahtarını olarak kullanarak i+"#"+j.

Anahtar i+"#"+j, j+"#"+itry ile aynıysa min(i,j)+"#"+max(i,j).


2
Gerçekten kötü fikir. Birincisi, çok kötü. İkinci olarak, bu teknik, farklı anahtarların aynı Stringşekilde komik sonuçlarla eşlenebileceği diğer türler için kopyalanacaktır .
Tom Hawtin - tackline

Bana öyle geliyor ki i#j = j#i, i == jöyle olduğunda min/maxhileyi kullanmak işe yaramaz.
Matthieu

1
@Matthieu arasındaki 5#5ve 5#5takas edilen arasındaki fark nedir?
enrey

@enrey Yok. Ben de bunu söylüyordum. Bu gerçekten anahtarlarınız hakkında sahip olduğunuz bilgiye bağlıdır.
Matthieu

@Matthieu aha Ne demek istediğini anlıyorum. Sanırım @arutaku'nun kastettiği, 5#3aynı hash'e sahip olmak istediğinizde 3#5, o zaman 3#5bu sırayı uygulamak için min / max kullanıyorsunuz .
enrey

1

Bunun için guava Tablosu uygulamasını da kullanabilirsiniz .

Tablo, tek bir değere atıfta bulunmak için iki anahtarın birleşik şekilde belirtilebildiği özel bir haritayı temsil eder. Bir harita haritası oluşturmaya benzer.

//create a table
  Table<String, String, String> employeeTable = HashBasedTable.create();

  //initialize the table with employee details
  employeeTable.put("IBM", "101","Mahesh");
  employeeTable.put("IBM", "102","Ramesh");
  employeeTable.put("IBM", "103","Suresh");

  employeeTable.put("Microsoft", "111","Sohan");
  employeeTable.put("Microsoft", "112","Mohan");
  employeeTable.put("Microsoft", "113","Rohan");

  employeeTable.put("TCS", "121","Ram");
  employeeTable.put("TCS", "122","Shyam");
  employeeTable.put("TCS", "123","Sunil");

  //get Map corresponding to IBM
  Map<String,String> ibmEmployees =  employeeTable.row("IBM");

1

Anahtar nesnenizi şu şekilde oluşturabilirsiniz:

public class MapKey {

public  Object key1;
public Object key2;

public Object getKey1() {
    return key1;
}

public void setKey1(Object key1) {
    this.key1 = key1;
}

public Object getKey2() {
    return key2;
}

public void setKey2(Object key2) {
    this.key2 = key2;
}

public boolean equals(Object keyObject){

    if(keyObject==null)
        return false;

    if (keyObject.getClass()!= MapKey.class)
        return false;

    MapKey key = (MapKey)keyObject;

    if(key.key1!=null && this.key1==null)
        return false;

    if(key.key2 !=null && this.key2==null)
        return false;

    if(this.key1==null && key.key1 !=null)
        return false;

    if(this.key2==null && key.key2 !=null)
        return false;

    if(this.key1==null && key.key1==null && this.key2 !=null && key.key2 !=null)
        return this.key2.equals(key.key2);

    if(this.key2==null && key.key2==null && this.key1 !=null && key.key1 !=null)
        return this.key1.equals(key.key1);

    return (this.key1.equals(key.key1) && this.key2.equals(key2));
}

public int hashCode(){
    int key1HashCode=key1.hashCode();
    int key2HashCode=key2.hashCode();
    return key1HashCode >> 3 + key2HashCode << 5;
}

}

Bunun avantajı şudur: Her zaman Eşittir senaryolarını da kapsadığınızdan emin olursunuz.

NOT : Anahtar1 ve anahtar2'niz değişmez olmalıdır. Ancak o zaman kararlı bir anahtar Nesne oluşturabilirsiniz.


1

birden fazla anahtar veya değer iletmek için bir sınıf oluşturabiliriz ve bu sınıfın nesnesi haritada parametre olarak kullanılabilir.

import java.io.BufferedReader; 
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

 public class key1 {
    String b;
    String a;
    key1(String a,String b)
    {
        this.a=a;
        this.b=b;
    }
  }

public class read2 {

private static final String FILENAME = "E:/studies/JAVA/ReadFile_Project/nn.txt";

public static void main(String[] args) {

    BufferedReader br = null;
    FileReader fr = null;
    Map<key1,String> map=new HashMap<key1,String>();
    try {

        fr = new FileReader(FILENAME);
        br = new BufferedReader(fr);

        String sCurrentLine;

        br = new BufferedReader(new FileReader(FILENAME));

        while ((sCurrentLine = br.readLine()) != null) {
            String[] s1 = sCurrentLine.split(",");
            key1 k1 = new key1(s1[0],s1[2]);
            map.put(k1,s1[2]);
        }
        for(Map.Entry<key1,String> m:map.entrySet()){  
            key1 key = m.getKey();
            String s3 = m.getValue();
               System.out.println(key.a+","+key.b+" : "+s3);  
              }  
  //            }   
        } catch (IOException e) {

        e.printStackTrace();

    } finally {

        try {

            if (br != null)
                br.close();

            if (fr != null)
                fr.close();

        } catch (IOException ex) {

            ex.printStackTrace();

        }

    }

    }

 }

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.