Làm thế nào để cập nhật một giá trị, được cung cấp một khóa trong hàm băm?


624

Giả sử chúng ta có một HashMap<String, Integer>Java.

Làm cách nào để cập nhật (tăng) giá trị nguyên của khóa chuỗi cho mỗi lần tồn tại của chuỗi tôi tìm thấy?

Người ta có thể loại bỏ và nhập lại cặp, nhưng chi phí sẽ là một mối quan tâm.
Một cách khác là chỉ cần đặt cặp mới và cái cũ sẽ được thay thế.

Trong trường hợp sau, điều gì xảy ra nếu có xung đột mã băm với khóa mới tôi đang cố gắng chèn? Hành vi đúng cho một hashtable sẽ là chỉ định một vị trí khác cho nó hoặc tạo một danh sách từ đó trong nhóm hiện tại.

Câu trả lời:


972
map.put(key, map.get(key) + 1);

nên ổn Nó sẽ cập nhật giá trị cho ánh xạ hiện có. Lưu ý rằng điều này sử dụng tự động đấm bốc. Với sự giúp đỡ của map.get(key)chúng tôi nhận được giá trị của khóa tương ứng, sau đó bạn có thể cập nhật với yêu cầu của bạn. Ở đây tôi đang cập nhật lên giá trị tăng thêm 1.


21
Trong thực tế đó là giải pháp doanh nghiệp mạnh mẽ và có khả năng mở rộng nhất.
Lavir the Whiolet

12
@Lavir, đây không phải là một giải pháp tồi, nhưng không thấy nó mạnh mẽ và có khả năng mở rộng nhất. Một nguyên tử thay thế có khả năng mở rộng hơn nhiều.
John Vint

13
Điều này giả sử chìa khóa tồn tại, phải không? Tôi nhận được nullPulum Exception khi nó không.
Ian

84
Với Java 8, có thể dễ dàng tránh điều này bằng cách sử dụng getOrDefault, ví dụ:map.put(key, count.getOrDefault(key, 0) + 1);
Martin

2
@Martin .. map.put (khóa, map.getOrDefault (khóa, 0) + 1)
Sathesh

112

Cách Java 8:

Bạn có thể sử dụng computeIfPresentphương thức và cung cấp cho nó một hàm ánh xạ, sẽ được gọi để tính toán một giá trị mới dựa trên giá trị hiện có.

Ví dụ,

Map<String, Integer> words = new HashMap<>();
words.put("hello", 3);
words.put("world", 4);
words.computeIfPresent("hello", (k, v) -> v + 1);
System.out.println(words.get("hello"));

Thay thế, bạn có thể sử dụng mergephương thức, trong đó 1 là giá trị mặc định và hàm tăng giá trị hiện tại lên 1:

words.merge("hello", 1, Integer::sum);

Bên cạnh đó, có một loạt các phương pháp hữu ích khác, chẳng hạn như putIfAbsent, getOrDefault, forEachvv


3
Tôi chỉ thử nghiệm giải pháp của bạn. Cái thứ hai, cái có tham chiếu phương thức, hoạt động. Cái đầu tiên, biểu thức lambda, không hoạt động ổn định khi bất kỳ giá trị nào trên bản đồ của bạn là null(giả sử words.put("hello", null);), kết quả vẫn nullkhông 1như tôi mong đợi.
Tao Zhang

4
Từ Javadoc: "Nếu giá trị của khóa được chỉ định hiện diện và không có giá trị, hãy thử tính toán ánh xạ mới". Bạn có thể sử dụng compute()thay thế, nó cũng sẽ xử lý nullcác giá trị.
damluar

Tôi muốn tăng giá trị của mình lên 1. .mergelà giải pháp của tôi với Integer::sum.
S_K

48
hashmap.put(key, hashmap.get(key) + 1);

Phương thức putnày sẽ thay thế giá trị của khóa hiện có và sẽ tạo nó nếu không tồn tại.


55
Không, nó không tạo ra, nó mang lại nullPointer Exception.
smttsp

13
Mã này là một câu trả lời chính xác cho câu hỏi đã cho, nhưng đã được đăng một năm sau khi mã chính xác được đăng trong câu trả lời được chấp nhận. Điều khác biệt câu trả lời này là nêu có thể tạo ra một mục mới, có thể, nhưng không phải trong ví dụ này. Nếu bạn đang sử dụng hashmap.get (khóa) cho khóa / giá trị không tồn tại, bạn sẽ nhận được null và khi bạn cố gắng tăng, như @smttsp nói rằng nó sẽ NPE. -1
Zach

8
Câu trả lời này là sai. NullPulumException cho các khóa không tồn tại
Eleanore

@smttp NullpointterException chỉ khi bạn không khởi tạo giá trị (như bạn biết bạn không thể tăng null)
Mehdi

Sao chép và giải thích không chính xác ... và sẽ tạo ra nó nếu không tồn tại Bạn không thể làm null + 1điều này vì điều này sẽ cố gắng bỏ hộp nullsố nguyên thành một số nguyên để thực hiện gia số.
AxelH

43

Cách 8 được đơn giản hóa của Java :

map.put(key, map.getOrDefault(key, 0) + 1);

Điều này sử dụng phương thức HashMap lấy giá trị cho một khóa, nhưng nếu không thể lấy được khóa, nó sẽ trả về giá trị mặc định đã chỉ định (trong trường hợp này là '0').

Điều này được hỗ trợ trong lõi Java: HashMap <K, V> getOrDefault (Khóa đối tượng, V defaultValue)


3
Đây là một trong những tốt hơn trong trường hợp bạn đang ở trên java 1.8
Hemant Nagpal

30

Thay thế Integerbằng AtomicIntegervà gọi một trong các incrementAndGet/ getAndIncrementphương thức trên nó.

Một cách khác là bọc một lớp intriêng của bạn MutableIntegercó một increment()phương thức, bạn chỉ có một mối quan tâm về an toàn luồng để giải quyết.


37
AtomicInteger là một Integer Integer nhưng được tích hợp sẵn. Tôi thực sự nghi ngờ việc viết MutableInteger của riêng bạn là một ý tưởng tốt hơn.
Peter Lawrey

Tùy chỉnh MutableIntegerlà tốt hơn, như AtomicIntegersử dụng volatile, có chi phí. Tôi sẽ sử dụng int[1]thay vì MutableInteger.
Oliv

@Oliv: không đồng thời.
BalusC

@BalusC nhưng vẫn, viết dễ bay hơi thì đắt hơn. Nó vô hiệu hóa bộ nhớ cache. Nếu nó không có sự khác biệt, tất cả các biến sẽ biến động.
Oliv

@Oliv: câu hỏi đề cập rõ ràng về xung đột mã băm, vì vậy đồng thời rất quan trọng đối với OP.
BalusC

19

Giải pháp một dòng:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);

4
Điều đó không thêm bất cứ điều gì mới vào các câu trả lời hiện có, phải không?
Robert

1
Vâng, nó có. Nó trả lời đúng được đánh dấu sẽ ném NullPulumException nếu khóa không tồn tại. Giải pháp này sẽ hoạt động tốt.
Hemant Nagpal

18

Giải pháp của @ Matthew là đơn giản nhất và sẽ thực hiện đủ tốt trong hầu hết các trường hợp.

Nếu bạn cần hiệu suất cao, AtomicInteger là giải pháp tốt hơn ala @BalusC.

Tuy nhiên, một giải pháp nhanh hơn (cung cấp an toàn luồng không phải là vấn đề) là sử dụng TObjectIntHashMap cung cấp phương thức tăng (khóa) và sử dụng các nguyên hàm và ít đối tượng hơn so với tạo AtomicIntegers. ví dụ

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");

13

Bạn có thể tăng như bên dưới nhưng bạn cần kiểm tra sự tồn tại để không bị ném NullPulumException

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}

9

Liệu hàm băm có tồn tại (với 0 là giá trị) hay nó được "đặt" vào bản đồ trên bước tăng đầu tiên? Nếu nó được "đặt" vào gia số đầu tiên, mã sẽ có dạng:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}

7

Nó có thể hơi muộn nhưng đây là hai xu của tôi.

Nếu bạn đang sử dụng Java 8 thì bạn có thể sử dụng phương thức compute IfPftime . Nếu giá trị của khóa được chỉ định hiện diện và không có giá trị thì nó sẽ cố gắng tính toán một ánh xạ mới được cung cấp cho khóa và giá trị được ánh xạ hiện tại của nó.

final Map<String,Integer> map1 = new HashMap<>();
map1.put("A",0);
map1.put("B",0);
map1.computeIfPresent("B",(k,v)->v+1);  //[A=0, B=1]

Chúng ta cũng có thể sử dụng một phương thức khác đặt IfAbsent để đặt khóa. Nếu khóa được chỉ định chưa được liên kết với một giá trị (hoặc được ánh xạ thành null) thì phương thức này liên kết nó với giá trị đã cho và trả về null, nếu không thì trả về giá trị hiện tại.

Trong trường hợp bản đồ được chia sẻ trên các luồng thì chúng ta có thể sử dụng ConcurrentHashMapAtomicInteger . Từ tài liệu:

An AtomicIntegerlà một giá trị int có thể được cập nhật nguyên tử. Một AtomicInteger được sử dụng trong các ứng dụng như bộ đếm tăng nguyên tử và không thể được sử dụng để thay thế cho Integer. Tuy nhiên, lớp này mở rộng Số để cho phép truy cập thống nhất bằng các công cụ và tiện ích liên quan đến các lớp dựa trên số.

Chúng ta có thể sử dụng chúng như được hiển thị:

final Map<String,AtomicInteger> map2 = new ConcurrentHashMap<>();
map2.putIfAbsent("A",new AtomicInteger(0));
map2.putIfAbsent("B",new AtomicInteger(0)); //[A=0, B=0]
map2.get("B").incrementAndGet();    //[A=0, B=1]

Một điểm cần quan sát là chúng ta đang gọi getđể lấy giá trị cho khóa Bvà sau đó gọi incrementAndGet()giá trị của nó là điều tất nhiên AtomicInteger. Chúng ta có thể tối ưu hóa nó khi phương thức putIfAbsenttrả về giá trị cho khóa nếu đã có:

map2.putIfAbsent("B",new AtomicInteger(0)).incrementAndGet();//[A=0, B=2]

Một lưu ý nữa là nếu chúng tôi dự định sử dụng AtomicLong thì theo tài liệu dưới sự tranh chấp cao, thông lượng dự kiến ​​của LongAdder sẽ cao hơn đáng kể, với chi phí tiêu thụ không gian cao hơn. Cũng kiểm tra câu hỏi này .


5

Giải pháp sạch hơn mà không có NullPulumException là:

map.replace(key, map.get(key) + 1);

5
nếu khóa không tồn tại thì map.get (khóa) sẽ ném NPE
Navi

Đúng vậy
Serge Dirin

2

Vì tôi không thể nhận xét một vài câu trả lời do ít tiếng tăm hơn, tôi sẽ đăng một giải pháp mà tôi đã áp dụng.

for(String key : someArray)
{
   if(hashMap.containsKey(key)//will check if a particular key exist or not 
   {
      hashMap.put(hashMap.get(key),value+1);// increment the value by 1 to an already existing key
   }
   else
   {
      hashMap.put(key,value);// make a new entry into the hashmap
   }
}

1

Sử dụng một forvòng lặp để tăng chỉ số:

for (int i =0; i<5; i++){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("beer", 100);

    int beer = map.get("beer")+i;
    System.out.println("beer " + beer);
    System.out ....

}

3
Điều đó sẽ ghi đè lên Bản đồ trên mỗi lần lặp. Xem câu trả lời của Matthew cho cách tiếp cận chính xác.
Leigh


1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

hoặc là

Integer i = map.get(key);
map.put(key, i == null ? newValue : i + 1);

Số nguyên là kiểu dữ liệu Nguyên thủy http://cs.fit.edu/~ryan/java/lingu/java-data.html , vì vậy bạn cần lấy nó ra, thực hiện một số quy trình, sau đó đặt lại. nếu bạn có một giá trị không phải là kiểu dữ liệu Nguyên thủy, bạn chỉ cần lấy nó ra, xử lý nó, không cần đưa nó trở lại vào hashmap.


1
Cảm ơn bạn vì đoạn mã này, có thể cung cấp một số trợ giúp ngay lập tức. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị giáo dục của nó bằng cách chỉ ra lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ giúp nó hữu ích hơn cho những độc giả tương lai với những câu hỏi tương tự, nhưng không giống nhau. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng.
Toby Speight

0

Thử:

HashMap hm=new HashMap<String ,Double >();

GHI CHÚ:

String->give the new value; //THIS IS THE KEY
else
Double->pass new value; //THIS IS THE VALUE

Bạn có thể thay đổi khóa hoặc giá trị trong hashmap của mình, nhưng bạn không thể thay đổi cả hai cùng một lúc.


0

Sử dụng Java8 được xây dựng trong phần kết hợp 'compute IfPftime'

Thí dụ:

public class ExampleToUpdateMapValue {

    public static void main(String[] args) {
        Map<String,String> bookAuthors = new TreeMap<>();
        bookAuthors.put("Genesis","Moses");
        bookAuthors.put("Joshua","Joshua");
        bookAuthors.put("Judges","Samuel");

        System.out.println("---------------------Before----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
        // To update the existing value using Java 8
        bookAuthors.computeIfPresent("Judges", (k,v) -> v = "Samuel/Nathan/Gad");

        System.out.println("---------------------After----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.