Tại sao HashMap nên được sử dụng (trong các hàm) để xác định giá trị nào sẽ trả về (cho khóa) khi một cấu trúc khác có thể thực hiện công việc trong thời gian tốt hơn?


9

Trong khi gần đây tôi làm việc tại một công ty lớn, tôi nhận thấy rằng các lập trình viên ở đó theo phong cách mã hóa này:

Giả sử tôi có hàm trả về 12 nếu đầu vào là A, 21 nếu đầu vào là B và 45 nếu đầu vào là C.

Vì vậy, tôi có thể viết chữ ký hàm là:

int foo(String s){
    if(s.equals("A"))      return 12;
    else if(s.equals("B")) return 21;
    else if(s.equals("C")) return 45;
    else throw new RuntimeException("Invalid input to function foo");
}

Nhưng khi xem lại mã, tôi được yêu cầu thay đổi chức năng như sau:

int foo(String s){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 12);
    map.put("B", 21);
    map.put("C", 45);
    return map.get(s);
}

Tôi không thể thuyết phục bản thân tại sao mã thứ hai tốt hơn mã thứ nhất. Mã thứ hai chắc chắn sẽ mất nhiều thời gian hơn để chạy.

Lý do duy nhất để sử dụng mã thứ hai có thể là nó cung cấp khả năng đọc tốt hơn. Nhưng nếu chức năng này được gọi nhiều lần thì chức năng thứ hai có làm chậm thời gian chạy của tiện ích gọi nó không?

Bạn nghĩ gì về điều này?


4
Đối với ba giá trị, Bản đồ có vẻ như quá mức cần thiết ( switchcó vẻ phù hợp hơn if-else). Nhưng đến một lúc nào đó, nó trở nên có vấn đề. Ưu điểm chính của việc sử dụng Bản đồ là bạn có thể tải nó từ tệp hoặc bảng, v.v. Nếu bạn khó mã hóa đầu vào vào bản đồ, tôi không thấy nhiều giá trị qua một công tắc.
JimmyJames

Câu trả lời:


16

Vấn đề là di chuyển việc tạo ra hashmap bên ngoài hàm và thực hiện nó một lần (hoặc chỉ ít lần hơn so với cách khác).

private static final Map<String, Integer> map;
static{
    Map<String, Integer> temp = new HashMap<String, Integer>();
    temp.put("A", 12);
    temp.put("B", 21);
    temp.put("C", 45);
    map = Collections.unmodifiableMap(temp);//make immutable
}

int foo(String s){
    if(!map.containsKey(s))
        throw new RuntimeException("Invalid input to function foo");

    return map.get(s);
}

Tuy nhiên java kể từ khi java7 có thể có các chuỗi (cuối cùng) trong các khóa:

int foo(String s){
    switch(s){
    case "A":
        return 12;
    case "B": 
        return 21;
    case "C": 
        return 45;
    default: throw new RuntimeException("Invalid input to function foo");
}

1
Tôi không thấy cách này trả lời câu hỏi OP, vì vậy -1 ở đó. Nhưng +1 cho đề xuất chuyển đổi.
dùng949300

Nó cho thấy cách thực hiện đúng phong cách mã hóa để thực sự có ý nghĩa và có thể cải thiện hiệu suất. Nó vẫn không có ý nghĩa cho 3 lựa chọn, nhưng mã ban đầu có thể dài hơn nhiều.
Florian F

12

Trong ví dụ thứ hai của bạn, Mapnên là một thành viên tĩnh riêng để tránh chi phí khởi tạo dự phòng.

Đối với số lượng lớn các giá trị, bản đồ sẽ hoạt động tốt hơn. Sử dụng hashtable, người ta có thể tra cứu câu trả lời trong thời gian không đổi. Cấu trúc nhiều-if phải so sánh đầu vào với từng khả năng cho đến khi tìm thấy câu trả lời đúng.

Nói cách khác, tra cứu bản đồ là O (1) trong khi ifs là O (n) trong đó n là số lượng đầu vào có thể.

Tạo bản đồ là O (n), nhưng chỉ được thực hiện một lần nếu nó ở trạng thái không đổi tĩnh. Đối với việc tra cứu được thực hiện thường xuyên, bản đồ sẽ vượt trội hơn các ifcâu lệnh trong thời gian dài, với chi phí hơi nhiều thời gian hơn khi chương trình khởi động (hoặc lớp được tải, tùy thuộc vào ngôn ngữ).

Điều đó đang được nói, bản đồ không phải lúc nào cũng là công cụ phù hợp cho công việc này. Thật tuyệt khi có nhiều giá trị hoặc các giá trị phải được cấu hình thông qua tệp văn bản, đầu vào của người dùng hoặc cơ sở dữ liệu (trong trường hợp đó bản đồ đóng vai trò là bộ đệm).


Có, với số lượng lớn các giá trị, bản đồ sẽ hoạt động tốt hơn. Nhưng số lượng giá trị là cố định và đó là ba.
RemcoGerlich

việc tạo bản đồ là O (N) chỉ tìm kiếm trong đó là O (1).
Pieter B

Điểm tốt, tôi làm rõ câu trả lời của tôi.

Bản đồ cũng yêu cầu tự động hủy hộp ảnh hưởng đến hiệu suất rất nhẹ.
dùng949300

@ user949300 trong Java, vâng, và mã trong câu hỏi dường như là Java. Tuy nhiên, nó không được gắn thẻ với bất kỳ ngôn ngữ nào và cách tiếp cận này hoạt động trên nhiều ngôn ngữ (bao gồm C # và C ++, cả hai ngôn ngữ này đều không yêu cầu quyền anh).

3

Có hai tốc độ trong phần mềm: thời gian cần để viết / đọc / gỡ lỗi mã; và thời gian cần thiết để thực thi mã.

Nếu bạn có thể thuyết phục tôi (và những người đánh giá mã của bạn) rằng hàm hashmap thực sự chậm hơn if / then / other (sau khi tái cấu trúc để tạo một hashmap tĩnh) VÀ bạn có thể thuyết phục tôi / người đánh giá rằng nó được gọi là đủ thời gian để thực hiện khác biệt, sau đó tiếp tục và thay thế hashmap bằng if / other.

Mặt khác, mã hashmap có thể đọc được; và (có lẽ) không có lỗi; bạn có thể xác định điều đó một cách nhanh chóng chỉ bằng cách nhìn vào nó. Bạn thực sự không thể nói điều tương tự về if / other mà không thực sự nghiên cứu nó; sự khác biệt thậm chí còn phóng đại hơn khi có hàng trăm lựa chọn.


3
Chà, sự phản đối đó sụp đổ khi người ta so sánh với một công tắc thay vào đó ...
Ded repeatator

Cũng sẽ sụp đổ nếu bạn viết các câu lệnh if trong một dòng duy nhất.
gnasher729

2
Bằng cách đặt việc tạo hashmap ở một nơi khác, bạn sẽ khó khăn hơn để tìm hiểu điều gì thực sự xảy ra. Bạn cần xem các khóa và giá trị đó để biết tác dụng thực sự của hàm là gì.
RemcoGerlich

2

Tôi rất thích câu trả lời kiểu HashMap.

Có một số liệu cho điều này

Có một số liệu chất lượng mã được gọi là Độ phức tạp Cyclomatic . Số liệu này về cơ bản đếm số lượng đường dẫn khác nhau thông qua mã ( cách tính Độ phức tạp theo chu kỳ ).

Đối với mọi đường dẫn thực thi có thể, một phương thức trở nên khó hơn và khó hơn để vừa hiểu VÀ kiểm tra đầy đủ tính chính xác.

Nó tập trung vào thực tế là "kiểm soát từ khóa" như: ifs, elses, whiles, v.v ... tận dụng các bài kiểm tra boolean có thể sai. Việc sử dụng lặp lại "từ khóa kiểm soát" tạo ra mã dễ vỡ.

Lợi ích kèm theo

Ngoài ra, "cách tiếp cận dựa trên bản đồ" khuyến khích các nhà phát triển nghĩ về các cặp đầu vào-đầu ra như một bộ dữ liệu có thể được trích xuất, tái sử dụng, thao tác trong thời gian chạy, kiểm tra và xác minh. Ví dụ: bên dưới tôi viết lại "foo" để chúng tôi không bị khóa vĩnh viễn vào "A-> 12, B-> 21, C-> 45":

int foo(String s){
    HashMap<String, Integer> map = getCurrentMapping();
    return map.get(s);
}

rachet_freak đề cập đến loại tái cấu trúc này trong câu trả lời của anh ta, anh ta lập luận về tốc độ và tái sử dụng, tôi đang tranh luận về tính linh hoạt của thời gian chạy (mặc dù sử dụng một bộ sưu tập bất biến có thể có lợi ích to lớn tùy thuộc vào tình huống)


1
Thêm vào đó tính linh hoạt thời gian chạy là một ý tưởng tuyệt vời về phía trước, hoặc không cần thiết quá mức khiến cho việc tìm hiểu những gì đang xảy ra khó khăn hơn nhiều. :-).
dùng949300

@JimmyJames Liên kết hoạt động với tôi: Đó là: leepoint.net/principles_and_practices/complexity/ Kẻ
Ivan

1
@ user949300 Điểm đáng chú ý là dữ liệu khóa / giá trị ủng hộ phương thức "foo" là một khái niệm riêng biệt có thể mang lại một số dạng rõ ràng. Lượng mã bạn muốn viết để cô lập Bản đồ sẽ phụ thuộc rất lớn vào số lượng mục mà Bản đồ chứa và tần suất các mục đó có thể cần thay đổi.
Ivan

1
@ user949300 Một phần lý do tôi khuyên bạn nên rút chuỗi if-if là tôi đã thấy các chuỗi if-other tồn tại trong các nhóm. Giống như các con gián, nếu có một phương thức dựa trên chuỗi if-if thì có khả năng là một phương thức khác ở cơ sở mã với chuỗi if-if tương tự / giống hệt. Trích xuất Bản đồ trả cổ tức nếu chúng tôi cho rằng có thể có các phương pháp khác sử dụng cấu trúc logic tương tự như tra cứu / chuyển đổi.
Ivan

Tôi cũng đã thấy các bản sao, gần như giống hệt nhau nếu / khối khác nằm rải rác khắp nơi. Vâng đặt
Jon Chesterfield

1

Dữ liệu tốt hơn mã. Ít nhất là vì quá hấp dẫn để thêm một nhánh khác vào mã, nhưng việc thêm một hàng vào bảng rất khó để hiểu sai. Câu hỏi này là một ví dụ nhỏ về điều này. Bạn đang viết một bảng tra cứu. Hoặc viết một triển khai, hoàn thành với logic và tài liệu có điều kiện, hoặc viết bảng ra sau đó tra cứu trong đó.

Một bảng dữ liệu luôn là một đại diện tốt hơn của một số dữ liệu so với mã, tối ưu hóa modulo. Bảng khó diễn đạt như thế nào có thể phụ thuộc vào ngôn ngữ - Tôi không biết Java, nhưng hy vọng nó có thể thực hiện bảng tra cứu đơn giản hơn ví dụ trong OP.

Đây là một bảng tra cứu trong python. Nếu điều này được xem là mời xung đột, vui lòng xem xét rằng câu hỏi không được gắn thẻ java, việc tái cấu trúc là bất khả tri về ngôn ngữ và hầu hết mọi người không biết java.

def foo(s):
    return {
               "A" : 12,
               "B" : 21,
               "C" : 45,
           }[s]

Ý tưởng tái cấu trúc mã để giảm thời gian chạy có giá trị, nhưng tôi muốn sử dụng một trình biên dịch nâng thiết lập chung hơn là tự mình làm.


-1 không phải là một câu trả lời cho câu hỏi.
Pieter B

Theo nghĩa nào? Tôi đã yêu cầu tác giả thay thế chuỗi if khác bằng bản đồ trên cơ sở dữ liệu và mã phải được tách biệt. Đây là một lý do rõ ràng tại sao mã thứ hai tốt hơn mã thứ nhất, mặc dù tất cả các lệnh gọi map.put đều không may.
Jon Chesterfield

2
@JonChesterfield Câu trả lời này về cơ bản là "sử dụng ngôn ngữ tốt hơn", điều này hiếm khi hữu ích.
walpen

@walpen điểm công bằng. Ivan đã thực hiện quan sát tương tự thông qua Java, vì vậy tôi rõ ràng không cần phải thả xuống con trăn. Tôi sẽ xem liệu tôi có thể dọn dẹp nó một chút không
Jon Chesterfield

Trong một số mã java cũ hơn, tôi cần một vài bản đồ cho một cái gì đó rất giống nhau, vì vậy đã viết một tiện ích nhỏ để chuyển các mảng N chiều của mảng 2 chiều thành bản đồ. Một chút hacky nhưng hoạt động tốt. Câu trả lời này chỉ ra sức mạnh của Python / JS trong việc hỗ trợ ký hiệu JSONy một cách đơn giản và dễ dàng.
dùng949300
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.