JavaScript Hashmap tương đương


354

Như đã làm rõ trong bản cập nhật 3 về câu trả lời này, ký hiệu này:

var hash = {};
hash[X]

không thực sự băm đối tượng X; nó thực sự chỉ chuyển đổi Xthành một chuỗi (thông qua .toString()nếu đó là một đối tượng hoặc một số chuyển đổi tích hợp khác cho các loại nguyên thủy khác nhau) và sau đó nhìn chuỗi đó lên, mà không băm nó, trong " hash". Bình đẳng đối tượng cũng không được kiểm tra - nếu hai đối tượng khác nhau có cùng một chuyển đổi chuỗi, chúng sẽ chỉ ghi đè lên nhau.

Với điều này - có bất kỳ triển khai hiệu quả nào của hashmap trong javascript không? (Ví dụ: kết quả thứ 2 của Google javascript hashmapmang lại một triển khai là O (n) cho bất kỳ hoạt động nào. Các kết quả khác bỏ qua thực tế là các đối tượng khác nhau có biểu diễn chuỗi tương đương ghi đè lên nhau.


1
@Claudiu: Xin lỗi vì đã chỉnh sửa, nhưng "Bản đồ" trong tiêu đề thực sự sai lệch. Quay trở lại nếu bạn không đồng ý, tôi không có ý định bảo trợ. :)
Tomalak

6
@Claudiu: Bạn hỏi rất nhiều câu hỏi về javascript. Những câu hỏi hay. Tôi thích điều đó.
một số

2
@Claudiu: Ngoài ra, bạn có thể liên kết đến kết quả Google mà bạn tham khảo không? Các phiên bản địa phương khác nhau của Google trả về các kết quả khác nhau, việc triển khai mà bạn đề cập thậm chí dường như không hiển thị cho tôi.
Tomalak

@Tomalak: Tôi vừa mới viết chính xác điều tương tự!
một số

3
@Claudiu Không, không liên kết với google. Liên kết đến trang bạn đang nói (mà bạn tình cờ tìm thấy thông qua google). Liên kết với google có tất cả các vấn đề tương tự như giải thích những gì cần tìm kiếm: google tùy chỉnh kết quả dựa trên vị trí hoặc lịch sử tìm kiếm, kết quả của google thay đổi theo thời gian (hiện tại, đây là kết quả hàng đầu cho tìm kiếm đó) và bất kỳ điều gì khác có thể làm cho nó hiển thị kết quả khác nhau.
Jasper

Câu trả lời:


370

Tại sao không tự băm các đối tượng của mình theo cách thủ công và sử dụng các chuỗi kết quả làm khóa cho một từ điển JavaScript thông thường? Sau tất cả, bạn đang ở vị trí tốt nhất để biết điều gì làm cho đối tượng của bạn trở nên độc đáo. Đó là những gì tôi làm.

Thí dụ:

var key = function(obj){
  // some unique object-dependent key
  return obj.totallyUniqueEmployeeIdKey; // just an example
};

var dict = {};

dict[key(obj1)] = obj1;
dict[key(obj2)] = obj2;

Bằng cách này, bạn có thể kiểm soát việc lập chỉ mục được thực hiện bằng JavaScript mà không cần phải phân bổ bộ nhớ và xử lý tràn.

Tất nhiên, nếu bạn thực sự muốn "giải pháp cấp công nghiệp", bạn có thể xây dựng một lớp được tham số hóa bởi chức năng chính và với tất cả API cần thiết của bộ chứa, nhưng chúng tôi sử dụng JavaScript và cố gắng đơn giản và gọn nhẹ, vì vậy giải pháp chức năng này là đơn giản và nhanh chóng.

Hàm khóa có thể đơn giản như chọn đúng các thuộc tính của đối tượng, ví dụ: khóa hoặc một bộ khóa vốn là duy nhất, một tổ hợp các khóa, là duy nhất với nhau hoặc phức tạp như sử dụng một số băm mật mã như trong Mã hóa DojoX hoặc DojoX UUID . Mặc dù các giải pháp sau có thể tạo ra các khóa duy nhất, cá nhân tôi cố gắng tránh chúng bằng mọi giá, đặc biệt, nếu tôi biết điều gì làm cho các đối tượng của tôi trở nên độc đáo.

Cập nhật vào năm 2014: Đã trả lời lại vào năm 2008, giải pháp đơn giản này vẫn cần nhiều lời giải thích hơn. Hãy để tôi làm rõ ý tưởng trong một hình thức hỏi đáp.

Giải pháp của bạn không có hàm băm thực sự. Nó đâu rồi???

JavaScript là một ngôn ngữ cấp cao. Nguyên thủy cơ bản của nó ( Object ) bao gồm một bảng băm để giữ các thuộc tính. Bảng băm này thường được viết bằng ngôn ngữ cấp thấp cho hiệu quả. Sử dụng một đối tượng đơn giản với các khóa chuỗi, chúng tôi sử dụng bảng băm được triển khai hiệu quả mà không cần nỗ lực từ phía chúng tôi.

Làm thế nào để bạn biết họ sử dụng băm?

Có ba cách chính để giữ một bộ sưu tập các đối tượng được đánh địa chỉ bằng một khóa:

  • Không có thứ tự. Trong trường hợp này để lấy một đối tượng bằng khóa của nó, chúng ta phải đi qua tất cả các phím dừng khi chúng ta tìm thấy nó. Trung bình sẽ mất n / 2 so sánh.
  • Ra lệnh.
    • Ví dụ # 1: một mảng được sắp xếp - thực hiện tìm kiếm nhị phân, chúng ta sẽ tìm thấy khóa của mình sau khi so sánh trung bình ~ log2 (n). Tốt hơn nhiều.
    • Ví dụ # 2: một cái cây. Một lần nữa, nó sẽ là ~ log (n) lần thử.
  • Bảng băm. Trung bình nó đòi hỏi một thời gian không đổi. So sánh: O (n) so với O (log n) so với O (1). Bùng nổ.

Rõ ràng các đối tượng JavaScript sử dụng các bảng băm ở một số dạng để xử lý các trường hợp chung.

Các nhà cung cấp trình duyệt có thực sự sử dụng bảng băm ???

Có thật không.

Họ có xử lý va chạm không?

Đúng. Xem ở trên. Nếu bạn tìm thấy xung đột trên các chuỗi không bằng nhau, xin vui lòng gửi lỗi với nhà cung cấp.

Vậy ý tưởng của bạn là gì?

Nếu bạn muốn băm một đối tượng, hãy tìm những gì làm cho nó trở nên độc đáo và sử dụng nó làm khóa. Đừng cố tính toán hàm băm thực hoặc mô phỏng bảng băm - nó đã được xử lý hiệu quả bởi đối tượng JavaScript bên dưới.

Sử dụng khóa này với JavaScript Objectđể tận dụng bảng băm tích hợp của nó trong khi tránh các xung đột có thể xảy ra với các thuộc tính mặc định.

Ví dụ để giúp bạn bắt đầu:

  • Nếu các đối tượng của bạn bao gồm một tên người dùng duy nhất - hãy sử dụng nó làm khóa.
  • Nếu nó bao gồm một số khách hàng duy nhất - sử dụng nó làm chìa khóa.
    • Nếu nó bao gồm các số do chính phủ cấp duy nhất như SSN hoặc số hộ chiếu và hệ thống của bạn không cho phép trùng lặp - hãy sử dụng số này làm khóa.
  • Nếu sự kết hợp của các trường là duy nhất - sử dụng nó làm khóa.
    • Viết tắt nhà nước + số giấy phép lái xe làm cho một chìa khóa tuyệt vời.
    • Quốc gia viết tắt + số hộ chiếu là một chìa khóa tuyệt vời quá.
  • Một số hàm trên các trường hoặc toàn bộ đối tượng có thể trả về một giá trị duy nhất - sử dụng nó làm khóa.

Tôi đã sử dụng đề xuất của bạn và lưu trữ tất cả các đối tượng bằng tên người dùng. Nhưng một số người khôn ngoan được đặt tên là "toString", đó là một tài sản tích hợp! Tôi nên làm gì bây giờ?

Rõ ràng, nếu thậm chí từ xa có thể là khóa kết quả sẽ chỉ bao gồm các ký tự Latinh, bạn nên làm gì đó với nó. Ví dụ: thêm bất kỳ ký tự Unicode không phải là tiếng Latinh nào bạn thích ở đầu hoặc cuối để không xung đột với các thuộc tính mặc định: "#toString", "#MarySmith". Nếu một khóa tổng hợp được sử dụng, hãy tách các thành phần khóa bằng cách sử dụng một số loại dấu phân cách không phải là tiếng Latinh: "tên, thành phố, tiểu bang".

Nói chung, đây là nơi chúng ta phải sáng tạo và chọn các khóa dễ nhất với các giới hạn nhất định (tính duy nhất, xung đột tiềm năng với các thuộc tính mặc định).

Lưu ý: các khóa duy nhất không xung đột theo định nghĩa, trong khi các xung đột băm tiềm năng sẽ được xử lý bởi bên dưới Object.

Tại sao bạn không thích các giải pháp công nghiệp?

IMHO, mã tốt nhất là không có mã nào cả: nó không có lỗi, không cần bảo trì, dễ hiểu và thực thi ngay lập tức. Tất cả "bảng băm trong JavaScript" tôi thấy là> 100 dòng mã và liên quan đến nhiều đối tượng. So sánh nó với : dict[key] = value.

Một điểm khác: thậm chí có thể đánh bại hiệu suất của một đối tượng nguyên thủy được viết bằng ngôn ngữ cấp thấp, sử dụng JavaScript và các đối tượng nguyên thủy giống nhau để thực hiện những gì đã được thực hiện?

Tôi vẫn muốn băm các đối tượng của mình mà không cần bất kỳ phím nào!

Chúng tôi rất may mắn: ECMAScript 6 (dự kiến ​​phát hành vào giữa năm 2015, cho hoặc mất 1-2 năm sau đó để trở nên phổ biến) xác định bản đồthiết lập .

Đánh giá theo định nghĩa, họ có thể sử dụng địa chỉ của đối tượng làm khóa, điều này làm cho các đối tượng khác biệt ngay lập tức mà không cần khóa nhân tạo. OTOH, hai đối tượng khác nhau, nhưng giống hệt nhau, sẽ được ánh xạ là khác biệt.

Phân tích so sánh từ MDN :

Các đối tượng tương tự như Bản đồ ở chỗ cả hai đều cho phép bạn đặt khóa thành giá trị, truy xuất các giá trị đó, xóa khóa và phát hiện xem có thứ gì được lưu trữ tại khóa hay không. Do điều này (và vì không có giải pháp thay thế tích hợp), các Đối tượng đã được sử dụng làm Bản đồ trong lịch sử; tuy nhiên, có những khác biệt quan trọng giúp sử dụng Bản đồ thích hợp hơn trong một số trường hợp nhất định:

  • Các khóa của Đối tượng là Chuỗi và Biểu tượng, trong khi chúng có thể là bất kỳ giá trị nào cho Bản đồ, bao gồm các chức năng, đối tượng và bất kỳ nguyên thủy nào.
  • Các khóa trong Bản đồ được sắp xếp trong khi các khóa được thêm vào đối tượng thì không. Do đó, khi lặp qua nó, một đối tượng Map sẽ trả về các khóa theo thứ tự chèn.
  • Bạn có thể dễ dàng lấy kích thước của Bản đồ với thuộc tính kích thước, trong khi số lượng thuộc tính trong Đối tượng phải được xác định thủ công.
  • Bản đồ là một lần lặp và do đó có thể được lặp lại trực tiếp, trong khi lặp lại trên một Đối tượng đòi hỏi phải có được các khóa của nó theo một cách thức nào đó và lặp đi lặp lại trên chúng.
  • Một đối tượng có một nguyên mẫu, do đó, có các khóa mặc định trong bản đồ có thể va chạm với các khóa của bạn nếu bạn không cẩn thận. Kể từ ES5, điều này có thể được bỏ qua bằng cách sử dụng map = Object.create (null), nhưng điều này hiếm khi được thực hiện.
  • Bản đồ có thể hoạt động tốt hơn trong các tình huống liên quan đến việc bổ sung và loại bỏ thường xuyên các cặp khóa.

13
Đây không giống như một bản đồ thích hợp, vì bạn không xử lý va chạm. Nếu điều này xảy ra là đúng: hash (obj1) == hash (obj2), bạn sẽ mất dữ liệu của mình.
coweather

32
Trời giúp bạn khi cả hai "PAUL AINLEY" và "PAULA INLEY" đăng ký trong hệ thống của bạn ...
Matt R

34
@MattR Trên thực tế, ví dụ của bạn sẽ hoạt động bình thường mà không cần sự trợ giúp của thiên đường ngay cả với hàm băm giả. Tôi hy vọng rằng những người đọc khác sẽ nhận ra rằng hàm băm phi thực tế được đơn giản hóa quá mức đã được sử dụng như một trình giữ chỗ để thể hiện một kỹ thuật khác. Cả hai bình luận mã, và câu trả lời tự nhấn mạnh rằng nó không có thật. Lựa chọn các khóa thích hợp được thảo luận trong đoạn cuối của câu trả lời.
Eugene Lazutkin

6
@EugeneLazutkin - bạn vẫn nhầm, tôi sợ. Ví dụ của bạn vẫn dễ bị va chạm băm. Đừng nghĩ rằng chỉ cần đặt tên cuối cùng sẽ giúp bạn!
Matt R

3
@EugeneLazutkin Hầu hết mọi người không đọc bạn đã trả lời điều này TRƯỚC ES6 thậm chí xuất hiện ... Hãy để tôi chúc mừng vì kiến ​​thức về JS sâu sắc của bạn.
Gabriel Andrés Brancolini

171

Mô tả vấn đề

JavaScript không có loại bản đồ chung tích hợp (đôi khi được gọi là mảng kết hợp hoặc từ điển ) cho phép truy cập các giá trị tùy ý bằng các khóa tùy ý. Cấu trúc dữ liệu cơ bản của JavaScript là đối tượng , một loại bản đồ đặc biệt chỉ chấp nhận các chuỗi làm khóa và có ngữ nghĩa đặc biệt như kế thừa nguyên mẫu, getters và setters và một số voodoo nữa.

Khi sử dụng các đối tượng dưới dạng bản đồ, bạn phải nhớ rằng khóa sẽ được chuyển đổi thành giá trị chuỗi thông qua toString(), dẫn đến ánh xạ 5'5'cùng giá trị và tất cả các đối tượng không ghi đè toString()phương thức vào giá trị được lập chỉ mục bởi '[object Object]'. Bạn cũng có thể vô tình truy cập các thuộc tính được kế thừa của nó nếu bạn không kiểm tra hasOwnProperty().

Kiểu mảng tích hợp của JavaScript không giúp được một bit: Mảng JavaScript không phải là mảng kết hợp, mà chỉ là các đối tượng có một vài thuộc tính đặc biệt hơn. Nếu bạn muốn biết lý do tại sao chúng không thể được sử dụng làm bản đồ, hãy xem tại đây .

Giải pháp của Eugene

Eugene Lazutkin đã mô tả ý tưởng cơ bản về việc sử dụng hàm băm tùy chỉnh để tạo các chuỗi duy nhất có thể được sử dụng để tra cứu các giá trị liên quan dưới dạng các thuộc tính của đối tượng từ điển. Đây rất có thể sẽ là giải pháp nhanh nhất, bởi vì các đối tượng được triển khai bên trong dưới dạng bảng băm .

  • Lưu ý: Các bảng băm (đôi khi được gọi là bản đồ băm ) là một triển khai cụ thể của khái niệm bản đồ bằng cách sử dụng một mảng sao lưu và tra cứu thông qua các giá trị băm số. Môi trường thời gian chạy có thể sử dụng các cấu trúc khác (như cây tìm kiếm hoặc bỏ qua danh sách ) để triển khai các đối tượng JavaScript, nhưng vì các đối tượng là cấu trúc dữ liệu cơ bản, nên chúng phải được tối ưu hóa đầy đủ.

Để có được giá trị băm duy nhất cho các đối tượng tùy ý, một khả năng là sử dụng bộ đếm toàn cục và lưu trữ giá trị băm trong chính đối tượng (ví dụ: trong một thuộc tính có tên __hash).

Hàm băm thực hiện điều này và hoạt động cho cả các giá trị và đối tượng nguyên thủy là:

function hash(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
}

hash.current = 0;

Chức năng này có thể được sử dụng theo mô tả của Eugene. Để thuận tiện, chúng tôi sẽ tiếp tục bọc nó trong một Maplớp học.

Tôi Mapthực hiện

Việc triển khai sau đây sẽ lưu trữ thêm các cặp khóa-giá trị trong một danh sách được liên kết đôi để cho phép lặp lại nhanh chóng trên cả khóa và giá trị. Để cung cấp hàm băm của riêng bạn, bạn có thể ghi đè hash()phương thức của thể hiện sau khi tạo.

// linking the key-value-pairs is optional
// if no argument is provided, linkItems === undefined, i.e. !== false
// --> linking will be enabled
function Map(linkItems) {
    this.current = undefined;
    this.size = 0;

    if(linkItems === false)
        this.disableLinking();
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// map initialisation from existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;

    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }

    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;
    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
    this.removeAll = Map.illegal;

    return this;
};

// overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;

    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];

    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }

    return this;
};

// only works if linked
Map.prototype.removeAll = function() {
    while(this.size)
        this.remove(this.key());

    return this;
};

// --- linked list helper functions

Map.prototype.link = function(item) {
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    return this.current.key;
};

Map.prototype.value = function() {
    return this.current.value;
};

Thí dụ

Kịch bản sau đây

var map = new Map;

map.put('spam', 'eggs').
    put('foo', 'bar').
    put('foo', 'baz').
    put({}, 'an object').
    put({}, 'another object').
    put(5, 'five').
    put(5, 'five again').
    put('5', 'another five');

for(var i = 0; i++ < map.size; map.next())
    document.writeln(map.hash(map.key()) + ' : ' + map.value());

tạo đầu ra này:

string spam : eggs
string foo : baz
object 1 : an object
object 2 : another object
number 5 : five again
string 5 : another five

Cân nhắc thêm

PEZ đề nghị ghi đè lên toString()phương thức, có lẽ là với hàm băm của chúng tôi. Điều này là không khả thi vì nó không hoạt động đối với các giá trị nguyên thủy (thay đổi toString()cho nguyên thủy là một ý tưởng rất tồi). Nếu chúng tôi muốn toString()trả về các giá trị có ý nghĩa cho các đối tượng tùy ý, chúng tôi sẽ phải sửa đổi Object.prototype, điều mà một số người (không bao gồm bản thân tôi) xem xét verboten .


Chỉnh sửa: Phiên bản hiện tại của Mapviệc triển khai của tôi cũng như các tính năng JavaScript khác có thể được lấy từ đây .


ES5 không tán thành việc sử dụng callee ( goo.gl/EeStE ). Thay vào đó, tôi đề nghị Map._counter = 0, và trong hàm tạo Map làm this._hash = 'object ' + Map._counter++. Sau đó, hàm băm () trở thànhreturn (value && value._hash) || (typeof(value) + ' ' + String(value));
broalid


xin chào @Christoph, bạn có thể cập nhật liên kết của mình đến nơi tôi có thể tìm thấy triển khai Bản đồ của mình không?
NumenorForLife

2
@ jsc123: Tôi sẽ xem xét điều đó - bây giờ bạn có thể nhận được một kho lưu trữ tại pikacode.com/mercurial.intuxication.org/js-hacks.tar.gz
Christoph

58

Tôi biết câu hỏi này khá cũ, nhưng có một số giải pháp thực sự tuyệt vời hiện nay với các thư viện bên ngoài.

JavaScript cũng có ngôn ngữ được cung cấp Maplà tốt.


2
Đây là cách để tiến tới thế kỷ 21. Thật tệ là tôi đã tìm thấy bài đăng của bạn sau khi hoàn thành mã của mình với một số Bản đồ nhà xấu xí. WEEE cần nhiều phiếu hơn cho câu trả lời của bạn
Phụng D.

1
Collections.js có một số triển khai, nhưng tôi không thể tìm thấy bất kỳ phần nào trong underscore.js hoặc lodash ... bạn đã đề cập đến những gì trong gạch dưới sẽ hữu ích?
Codebling

@CodeBling không có ý kiến. Tôi nghĩ rằng tôi đã nhầm lẫn với chức năng bản đồ. Tôi sẽ loại bỏ nó khỏi câu trả lời.
Jamel Toms

3
Điều đó công bằng. Bất cứ ai xem xét Collections.js đều nên biết rằng nó sửa đổi các nguyên mẫu Array, Function, Object và Regapi toàn cầu theo kiểu có vấn đề ( xem các vấn đề tôi gặp phải ở đây ). Mặc dù ban đầu tôi rất hài lòng với bộ sưu tập.js (và do đó là câu trả lời này), nhưng những rủi ro liên quan đến việc sử dụng nó là quá cao, vì vậy tôi đã bỏ nó. Chỉ có nhánh v2 của bộ sưu tập vr của kriskowal (cụ thể là v2.0.2 +) mới loại bỏ các sửa đổi nguyên mẫu toàn cầu và an toàn khi sử dụng.
Codebling

28

Đây là một cách dễ dàng và thuận tiện để sử dụng một cái gì đó tương tự như bản đồ java:

var map= {
        'map_name_1': map_value_1,
        'map_name_2': map_value_2,
        'map_name_3': map_value_3,
        'map_name_4': map_value_4
        }

Và để có được giá trị:

alert( map['map_name_1'] );    // fives the value of map_value_1

......  etc  .....

2
Điều này chỉ làm việc cho các phím chuỗi. Tôi tin rằng OP đã quan tâm đến việc sử dụng các loại khóa.
fractor

26

Theo javascript tiêu chuẩn ECMAScript 2015 (ES6) có triển khai Bản đồ. Thông tin thêm về có thể được tìm thấy ở đây

Cách sử dụng cơ bản:

var myMap = new Map();
var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");

myMap.size; // 3

// getting the values
myMap.get(keyString);    // "value associated with 'a string'"
myMap.get(keyObj);       // "value associated with keyObj"
myMap.get(keyFunc);      // "value associated with keyFunc"

21

Bạn có thể sử dụng ES6 WeakMaphoặc Map:

  • WeakMaps là bản đồ khóa / giá trị trong đó các khóa là đối tượng.

  • Mapđối tượng là bản đồ khóa / giá trị đơn giản. Mọi giá trị (cả đối tượng và giá trị nguyên thủy) có thể được sử dụng làm khóa hoặc giá trị.

Xin lưu ý rằng không được hỗ trợ rộng rãi, nhưng bạn có thể sử dụng ES6 Shim (yêu cầu ES5 hoặc ES5 Shim gốc ) để hỗ trợ Map, nhưng không WeakMap( xem tại sao ).


Năm 2019 họ được hỗ trợ rất tốt và có những phương pháp tuyệt vời! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Khăn

13

Bạn sẽ phải lưu trữ trong một số khớp nối trạng thái nội bộ của các cặp đối tượng / giá trị

HashMap = function(){
  this._dict = [];
}
HashMap.prototype._get = function(key){
  for(var i=0, couplet; couplet = this._dict[i]; i++){
    if(couplet[0] === key){
      return couplet;
    }
  }
}
HashMap.prototype.put = function(key, value){
  var couplet = this._get(key);
  if(couplet){
    couplet[1] = value;
  }else{
    this._dict.push([key, value]);
  }
  return this; // for chaining
}
HashMap.prototype.get = function(key){
  var couplet = this._get(key);
  if(couplet){
    return couplet[1];
  }
}

Và sử dụng nó như vậy:

var color = {}; // unique object instance
var shape = {}; // unique object instance
var map = new HashMap();
map.put(color, "blue");
map.put(shape, "round");
console.log("Item is", map.get(color), "and", map.get(shape));

Tất nhiên, việc thực hiện này cũng ở đâu đó dọc theo dòng O (n). Các ví dụ của Eugene ở trên là cách duy nhất để có được hàm băm hoạt động với bất kỳ loại tốc độ nào bạn mong đợi từ hàm băm thực.

Cập nhật:

Một cách tiếp cận khác, dọc theo câu trả lời của Eugene là bằng cách nào đó đính kèm một ID duy nhất cho tất cả các đối tượng. Một trong những cách tiếp cận yêu thích của tôi là sử dụng một trong các phương thức tích hợp được kế thừa từ siêu lớp Object, thay thế nó bằng một hàm tùy chỉnh thông qua và gắn các thuộc tính vào đối tượng hàm đó. Nếu bạn đã viết lại phương thức HashMap của tôi để làm điều này, nó sẽ giống như:

HashMap = function(){
  this._dict = {};
}
HashMap.prototype._shared = {id: 1};
HashMap.prototype.put = function put(key, value){
  if(typeof key == "object"){
    if(!key.hasOwnProperty._id){
      key.hasOwnProperty = function(key){
        return Object.prototype.hasOwnProperty.call(this, key);
      }
      key.hasOwnProperty._id = this._shared.id++;
    }
    this._dict[key.hasOwnProperty._id] = value;
  }else{
    this._dict[key] = value;
  }
  return this; // for chaining
}
HashMap.prototype.get = function get(key){
  if(typeof key == "object"){
    return this._dict[key.hasOwnProperty._id];
  }
  return this._dict[key];
}

Phiên bản này dường như chỉ nhanh hơn một chút, nhưng trên lý thuyết nó sẽ nhanh hơn đáng kể đối với các tập dữ liệu lớn.


Một mảng kết hợp, tức là một mảng gồm 2 bộ, là một Bản đồ, không phải là HashMap; HashMap là một Bản đồ sử dụng băm để có hiệu suất tốt hơn.
Erik Kaplun

Đúng, nhưng tại sao lại chia tóc về chủ đề này? Không có cách nào để tạo một bản đồ băm thực sự trong JavaScript vì bạn không thể lấy địa chỉ bộ nhớ đối tượng. Và các cặp khóa / giá trị đối tượng tích hợp của JavaScript (được sử dụng trong ví dụ thứ hai của tôi) có thể đóng vai trò là HashMaps, nhưng không nhất thiết, vì nó tùy thuộc vào thời gian chạy được sử dụng trong trình duyệt như cách triển khai tra cứu.
chậu cây

11

Thật không may, không có câu trả lời nào ở trên là tốt cho trường hợp của tôi: các đối tượng chính khác nhau có thể có cùng mã băm. Do đó, tôi đã viết một phiên bản HashMap đơn giản giống như Java:

function HashMap() {
    this.buckets = {};
}

HashMap.prototype.put = function(key, value) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        bucket = new Array();
        this.buckets[hashCode] = bucket;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            bucket[i].value = value;
            return;
        }
    }
    bucket.push({ key: key, value: value });
}

HashMap.prototype.get = function(key) {
    var hashCode = key.hashCode();
    var bucket = this.buckets[hashCode];
    if (!bucket) {
        return null;
    }
    for (var i = 0; i < bucket.length; ++i) {
        if (bucket[i].key.equals(key)) {
            return bucket[i].value;
        }
    }
}

HashMap.prototype.keys = function() {
    var keys = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            keys.push(bucket[i].key);
        }
    }
    return keys;
}

HashMap.prototype.values = function() {
    var values = new Array();
    for (var hashKey in this.buckets) {
        var bucket = this.buckets[hashKey];
        for (var i = 0; i < bucket.length; ++i) {
            values.push(bucket[i].value);
        }
    }
    return values;
}

Lưu ý: các đối tượng chính phải "thực hiện" các phương thức hashCode () và equals ().


7
Sở thích của new Array()hơn []là đảm bảo tính giống Java tuyệt đối của mã của bạn? :)
Erik Kaplun

6

Tôi đã triển khai HashMap JavaScript mà mã có thể được lấy từ http://github.com/lambder/HashMapJS/tree/master

Đây là mã:

/*
 =====================================================================
 @license MIT
 @author Lambder
 @copyright 2009 Lambder.
 @end
 =====================================================================
 */
var HashMap = function() {
  this.initialize();
}

HashMap.prototype = {
  hashkey_prefix: "<#HashMapHashkeyPerfix>",
  hashcode_field: "<#HashMapHashkeyPerfix>",

  initialize: function() {
    this.backing_hash = {};
    this.code = 0;
  },
  /*
   maps value to key returning previous assocciation
   */
  put: function(key, value) {
    var prev;
    if (key && value) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        prev = this.backing_hash[hashCode];
      } else {
        this.code += 1;
        hashCode = this.hashkey_prefix + this.code;
        key[this.hashcode_field] = hashCode;
      }
      this.backing_hash[hashCode] = value;
    }
    return prev;
  },
  /*
   returns value associated with given key
   */
  get: function(key) {
    var value;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        value = this.backing_hash[hashCode];
      }
    }
    return value;
  },
  /*
   deletes association by given key.
   Returns true if the assocciation existed, false otherwise
   */
  del: function(key) {
    var success = false;
    if (key) {
      var hashCode = key[this.hashcode_field];
      if (hashCode) {
        var prev = this.backing_hash[hashCode];
        this.backing_hash[hashCode] = undefined;
        if(prev !== undefined)
          success = true;
      }
    }
    return success;
  }
}

//// Usage

// creation

var my_map = new HashMap();

// insertion

var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};

my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);

// retrieval

if(my_map.get(a_key) !== a_value){
  throw("fail1")
}
if(my_map.get(b_key) !== c_value){
  throw("fail2")
}
if(prev_b !== b_value){
  throw("fail3")
}

// deletion

var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);

if(a_existed !== true){
  throw("fail4")
}
if(c_existed !== false){
  throw("fail5")
}
if(a2_existed !== false){
  throw("fail6")
}

2
Mã của bạn dường như không hoạt động với việc đặt cùng một đối tượng trong nhiều HashMaps.
Erik Kaplun

5

Trong ECMA6, bạn có thể sử dụng WeakMap

Thí dụ:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // a value can be anything, including an object or a function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps!

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, because there is no value for o2 on wm2
wm2.get(o3); // undefined, because that is the set value

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (even if the value itself is 'undefined')

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined, because wm3 was cleared and there is no value for o1 anymore

wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false

Nhưng:

Because of references being weak, WeakMap keys are not enumerable (i.e. there is no method giving you a list of the keys). 

oh khen ngợi jesus cuối cùng họ đã thêm các tài liệu tham khảo yếu vào javascript. đó là khoảng thời gian ... +1 cho điều đó, nhưng điều này thực sự sẽ rất tệ khi sử dụng vì các tài liệu tham khảo còn yếu
Claudiu

2

Javascript không tích hợp Map / hashmap. Nó nên được gọi là mảng kết hợp .

hash["X"]là bằng hash.X, nhưng cho phép "X" là một biến chuỗi. Nói cách khác, hash[x]là chức năng tương đương vớieval("hash."+x.toString())

Nó tương tự như object.properations hơn là ánh xạ khóa-giá trị. Nếu bạn đang tìm kiếm ánh xạ Khóa / giá trị tốt hơn trong Javascript, vui lòng sử dụng đối tượng Bản đồ mà bạn có thể tìm thấy trên web.


2

Hãy thử triển khai bảng băm JavaScript của tôi: http://www.timdown.co.uk/jshashtable

Nó tìm kiếm một phương thức hashCode () của các đối tượng chính hoặc bạn có thể cung cấp hàm băm khi tạo đối tượng Hashtable.


2

Đây có vẻ là một giải pháp khá mạnh mẽ: https://github.com/flesler/hashmap . Nó thậm chí sẽ hoạt động tốt cho các chức năng và các đối tượng trông giống hệt nhau. Cách hack duy nhất mà nó sử dụng là thêm một thành viên tối nghĩa vào một đối tượng để xác định nó. Nếu chương trình của bạn không ghi đè lên biến tối nghĩa đó (giống như hashid ), thì bạn là vàng.


2

Nếu hiệu suất là không quan trọng (ví dụ như số lượng các phím là tương đối nhỏ) và bạn không muốn làm ô nhiễm của bạn (hoặc có lẽ không phải của bạn) các đối tượng với các lĩnh vực khác như _hash, _idvv, sau đó bạn có thể tận dụng thực tế là Array.prototype.indexOfsử dụng bình đẳng nghiêm ngặt. Đây là một cách thực hiện đơn giản:

var Dict = (function(){
    // IE 8 and earlier has no Array.prototype.indexOf
    function indexOfPolyfill(val) {
      for (var i = 0, l = this.length; i < l; ++i) {
        if (this[i] === val) {
          return i;
        }
      }
      return -1;
    }

    function Dict(){
      this.keys = [];
      this.values = [];
      if (!this.keys.indexOf) {
        this.keys.indexOf = indexOfPolyfill;
      }
    };

    Dict.prototype.has = function(key){
      return this.keys.indexOf(key) != -1;
    };

    Dict.prototype.get = function(key, defaultValue){
      var index = this.keys.indexOf(key);
      return index == -1 ? defaultValue : this.values[index];
    };

    Dict.prototype.set = function(key, value){
      var index = this.keys.indexOf(key);
      if (index == -1) {
        this.keys.push(key);
        this.values.push(value);
      } else {
        var prevValue = this.values[index];
        this.values[index] = value;
        return prevValue;
      }
    };

    Dict.prototype.delete = function(key){
      var index = this.keys.indexOf(key);
      if (index != -1) {
        this.keys.splice(index, 1);
        return this.values.splice(index, 1)[0];
      }
    };

    Dict.prototype.clear = function(){
      this.keys.splice(0, this.keys.length);
      this.values.splice(0, this.values.length);
    };

    return Dict;
})();

Ví dụ về cách sử dụng:

var a = {}, b = {},
    c = { toString: function(){ return '1'; } },
    d = 1, s = '1', u = undefined, n = null,
    dict = new Dict();

// keys and values can be anything
dict.set(a, 'a');
dict.set(b, 'b');
dict.set(c, 'c');
dict.set(d, 'd');
dict.set(s, 's');
dict.set(u, 'u');
dict.set(n, 'n');

dict.get(a); // 'a'
dict.get(b); // 'b'
dict.get(s); // 's'
dict.get(u); // 'u'
dict.get(n); // 'n'
// etc.

So với ES6 WeakMap, nó có hai vấn đề: O (n) thời gian tìm kiếm và không yếu (nghĩa là nó sẽ gây rò rỉ bộ nhớ nếu bạn không sử dụng deletehoặc cleargiải phóng các phím).


2

Thực hiện bản đồ của tôi, xuất phát từ ví dụ của Christoph:

Cách sử dụng ví dụ:

var map = new Map();  //creates an "in-memory" map
var map = new Map("storageId");  //creates a map that is loaded/persisted using html5 storage

function Map(storageId) {
    this.current = undefined;
    this.size = 0;
    this.storageId = storageId;
    if (this.storageId) {
        this.keys = new Array();
        this.disableLinking();
    }
}

Map.noop = function() {
    return this;
};

Map.illegal = function() {
    throw new Error("illegal operation for maps without linking");
};

// map initialisation from existing object
// doesn't add inherited properties if not explicitly instructed to:
// omitting foreignKeys means foreignKeys === undefined, i.e. == false
// --> inherited properties won't be added
Map.from = function(obj, foreignKeys) {
    var map = new Map;
    for(var prop in obj) {
        if(foreignKeys || obj.hasOwnProperty(prop))
            map.put(prop, obj[prop]);
    }
    return map;
};

Map.prototype.disableLinking = function() {
    this.link = Map.noop;
    this.unlink = Map.noop;
    this.disableLinking = Map.noop;

    this.next = Map.illegal;
    this.key = Map.illegal;
    this.value = Map.illegal;
//    this.removeAll = Map.illegal;


    return this;
};

// overwrite in Map instance if necessary
Map.prototype.hash = function(value) {
    return (typeof value) + ' ' + (value instanceof Object ?
        (value.__hash || (value.__hash = ++arguments.callee.current)) :
        value.toString());
};

Map.prototype.hash.current = 0;

// --- mapping functions

Map.prototype.get = function(key) {
    var item = this[this.hash(key)];
    if (item === undefined) {
        if (this.storageId) {
            try {
                var itemStr = localStorage.getItem(this.storageId + key);
                if (itemStr && itemStr !== 'undefined') {
                    item = JSON.parse(itemStr);
                    this[this.hash(key)] = item;
                    this.keys.push(key);
                    ++this.size;
                }
            } catch (e) {
                console.log(e);
            }
        }
    }
    return item === undefined ? undefined : item.value;
};

Map.prototype.put = function(key, value) {
    var hash = this.hash(key);

    if(this[hash] === undefined) {
        var item = { key : key, value : value };
        this[hash] = item;

        this.link(item);
        ++this.size;
    }
    else this[hash].value = value;
    if (this.storageId) {
        this.keys.push(key);
        try {
            localStorage.setItem(this.storageId + key, JSON.stringify(this[hash]));
        } catch (e) {
            console.log(e);
        }
    }
    return this;
};

Map.prototype.remove = function(key) {
    var hash = this.hash(key);
    var item = this[hash];
    if(item !== undefined) {
        --this.size;
        this.unlink(item);

        delete this[hash];
    }
    if (this.storageId) {
        try {
            localStorage.setItem(this.storageId + key, undefined);
        } catch (e) {
            console.log(e);
        }
    }
    return this;
};

// only works if linked
Map.prototype.removeAll = function() {
    if (this.storageId) {
        for (var i=0; i<this.keys.length; i++) {
            this.remove(this.keys[i]);
        }
        this.keys.length = 0;
    } else {
        while(this.size)
            this.remove(this.key());
    }
    return this;
};

// --- linked list helper functions

Map.prototype.link = function(item) {
    if (this.storageId) {
        return;
    }
    if(this.size == 0) {
        item.prev = item;
        item.next = item;
        this.current = item;
    }
    else {
        item.prev = this.current.prev;
        item.prev.next = item;
        item.next = this.current;
        this.current.prev = item;
    }
};

Map.prototype.unlink = function(item) {
    if (this.storageId) {
        return;
    }
    if(this.size == 0)
        this.current = undefined;
    else {
        item.prev.next = item.next;
        item.next.prev = item.prev;
        if(item === this.current)
            this.current = item.next;
    }
};

// --- iterator functions - only work if map is linked

Map.prototype.next = function() {
    this.current = this.current.next;
};

Map.prototype.key = function() {
    if (this.storageId) {
        return undefined;
    } else {
        return this.current.key;
    }
};

Map.prototype.value = function() {
    if (this.storageId) {
        return undefined;
    }
    return this.current.value;
};

1

Thêm một giải pháp nữa: gần như HashMaplà lớp đầu tiên tôi chuyển từ Java sang Javascript. Bạn có thể nói có rất nhiều chi phí, nhưng việc triển khai gần như 100% so với triển khai của Java và bao gồm tất cả các giao diện và các lớp con.

Dự án có thể được tìm thấy ở đây: https://github.com/Airblader/jsava Tôi cũng sẽ đính kèm mã nguồn (hiện tại) cho lớp HashMap, nhưng như đã nêu, nó cũng phụ thuộc vào siêu lớp, v.v. Khung OOP được sử dụng là qooxdoo.

Chỉnh sửa: Xin lưu ý rằng mã này đã lỗi thời và tham khảo dự án github cho công việc hiện tại. Khi viết bài này, cũng có một ArrayListtriển khai.

qx.Class.define( 'jsava.util.HashMap', {
    extend: jsava.util.AbstractMap,
    implement: [jsava.util.Map, jsava.io.Serializable, jsava.lang.Cloneable],

    construct: function () {
        var args = Array.prototype.slice.call( arguments ),
            initialCapacity = this.self( arguments ).DEFAULT_INITIAL_CAPACITY,
            loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR;

        switch( args.length ) {
            case 1:
                if( qx.Class.implementsInterface( args[0], jsava.util.Map ) ) {
                    initialCapacity = Math.max( ((args[0].size() / this.self( arguments ).DEFAULT_LOAD_FACTOR) | 0) + 1,
                        this.self( arguments ).DEFAULT_INITIAL_CAPACITY );
                    loadFactor = this.self( arguments ).DEFAULT_LOAD_FACTOR;
                } else {
                    initialCapacity = args[0];
                }
                break;
            case 2:
                initialCapacity = args[0];
                loadFactor = args[1];
                break;
        }

        if( initialCapacity < 0 ) {
            throw new jsava.lang.IllegalArgumentException( 'Illegal initial capacity: ' + initialCapacity );
        }
        if( initialCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) {
            initialCapacity = this.self( arguments ).MAXIMUM_CAPACITY;
        }
        if( loadFactor <= 0 || isNaN( loadFactor ) ) {
            throw new jsava.lang.IllegalArgumentException( 'Illegal load factor: ' + loadFactor );
        }

        var capacity = 1;
        while( capacity < initialCapacity ) {
            capacity <<= 1;
        }

        this._loadFactor = loadFactor;
        this._threshold = (capacity * loadFactor) | 0;
        this._table = jsava.JsavaUtils.emptyArrayOfGivenSize( capacity, null );
        this._init();
    },

    statics: {
        serialVersionUID: 1,

        DEFAULT_INITIAL_CAPACITY: 16,
        MAXIMUM_CAPACITY: 1 << 30,
        DEFAULT_LOAD_FACTOR: 0.75,

        _hash: function (hash) {
            hash ^= (hash >>> 20) ^ (hash >>> 12);
            return hash ^ (hash >>> 7) ^ (hash >>> 4);
        },

        _indexFor: function (hashCode, length) {
            return hashCode & (length - 1);
        },

        Entry: qx.Class.define( 'jsava.util.HashMap.Entry', {
            extend: jsava.lang.Object,
            implement: [jsava.util.Map.Entry],

            construct: function (hash, key, value, nextEntry) {
                this._value = value;
                this._next = nextEntry;
                this._key = key;
                this._hash = hash;
            },

            members: {
                _key: null,
                _value: null,
                /** @type jsava.util.HashMap.Entry */
                _next: null,
                /** @type Number */
                _hash: 0,

                getKey: function () {
                    return this._key;
                },

                getValue: function () {
                    return this._value;
                },

                setValue: function (newValue) {
                    var oldValue = this._value;
                    this._value = newValue;
                    return oldValue;
                },

                equals: function (obj) {
                    if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.HashMap.Entry ) ) {
                        return false;
                    }

                    /** @type jsava.util.HashMap.Entry */
                    var entry = obj,
                        key1 = this.getKey(),
                        key2 = entry.getKey();
                    if( key1 === key2 || (key1 !== null && key1.equals( key2 )) ) {
                        var value1 = this.getValue(),
                            value2 = entry.getValue();
                        if( value1 === value2 || (value1 !== null && value1.equals( value2 )) ) {
                            return true;
                        }
                    }

                    return false;
                },

                hashCode: function () {
                    return (this._key === null ? 0 : this._key.hashCode()) ^
                        (this._value === null ? 0 : this._value.hashCode());
                },

                toString: function () {
                    return this.getKey() + '=' + this.getValue();
                },

                /**
                 * This method is invoked whenever the value in an entry is
                 * overwritten by an invocation of put(k,v) for a key k that's already
                 * in the HashMap.
                 */
                _recordAccess: function (map) {
                },

                /**
                 * This method is invoked whenever the entry is
                 * removed from the table.
                 */
                _recordRemoval: function (map) {
                }
            }
        } )
    },

    members: {
        /** @type jsava.util.HashMap.Entry[] */
        _table: null,
        /** @type Number */
        _size: 0,
        /** @type Number */
        _threshold: 0,
        /** @type Number */
        _loadFactor: 0,
        /** @type Number */
        _modCount: 0,
        /** @implements jsava.util.Set */
        __entrySet: null,

        /**
         * Initialization hook for subclasses. This method is called
         * in all constructors and pseudo-constructors (clone, readObject)
         * after HashMap has been initialized but before any entries have
         * been inserted.  (In the absence of this method, readObject would
         * require explicit knowledge of subclasses.)
         */
        _init: function () {
        },

        size: function () {
            return this._size;
        },

        isEmpty: function () {
            return this._size === 0;
        },

        get: function (key) {
            if( key === null ) {
                return this.__getForNullKey();
            }

            var hash = this.self( arguments )._hash( key.hashCode() );
            for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )];
                 entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash && ((k = entry._key) === key || key.equals( k )) ) {
                    return entry._value;
                }
            }

            return null;
        },

        __getForNullKey: function () {
            for( var entry = this._table[0]; entry !== null; entry = entry._next ) {
                if( entry._key === null ) {
                    return entry._value;
                }
            }

            return null;
        },

        containsKey: function (key) {
            return this._getEntry( key ) !== null;
        },

        _getEntry: function (key) {
            var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() );
            for( var entry = this._table[this.self( arguments )._indexFor( hash, this._table.length )];
                 entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash
                    && ( ( k = entry._key ) === key || ( key !== null && key.equals( k ) ) ) ) {
                    return entry;
                }
            }

            return null;
        },

        put: function (key, value) {
            if( key === null ) {
                return this.__putForNullKey( value );
            }

            var hash = this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length );
            for( var entry = this._table[i]; entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash && ( (k = entry._key) === key || key.equals( k ) ) ) {
                    var oldValue = entry._value;
                    entry._value = value;
                    entry._recordAccess( this );
                    return oldValue;
                }
            }

            this._modCount++;
            this._addEntry( hash, key, value, i );
            return null;
        },

        __putForNullKey: function (value) {
            for( var entry = this._table[0]; entry !== null; entry = entry._next ) {
                if( entry._key === null ) {
                    var oldValue = entry._value;
                    entry._value = value;
                    entry._recordAccess( this );
                    return oldValue;
                }
            }

            this._modCount++;
            this._addEntry( 0, null, value, 0 );
            return null;
        },

        __putForCreate: function (key, value) {
            var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length );
            for( var entry = this._table[i]; entry !== null; entry = entry._next ) {
                /** @type jsava.lang.Object */
                var k;
                if( entry._hash === hash
                    && ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) {
                    entry._value = value;
                    return;
                }
            }

            this._createEntry( hash, key, value, i );
        },

        __putAllForCreate: function (map) {
            var iterator = map.entrySet().iterator();
            while( iterator.hasNext() ) {
                var entry = iterator.next();
                this.__putForCreate( entry.getKey(), entry.getValue() );
            }
        },

        _resize: function (newCapacity) {
            var oldTable = this._table,
                oldCapacity = oldTable.length;
            if( oldCapacity === this.self( arguments ).MAXIMUM_CAPACITY ) {
                this._threshold = Number.MAX_VALUE;
                return;
            }

            var newTable = jsava.JsavaUtils.emptyArrayOfGivenSize( newCapacity, null );
            this._transfer( newTable );
            this._table = newTable;
            this._threshold = (newCapacity * this._loadFactor) | 0;
        },

        _transfer: function (newTable) {
            var src = this._table,
                newCapacity = newTable.length;
            for( var j = 0; j < src.length; j++ ) {
                var entry = src[j];
                if( entry !== null ) {
                    src[j] = null;
                    do {
                        var next = entry._next,
                            i = this.self( arguments )._indexFor( entry._hash, newCapacity );
                        entry._next = newTable[i];
                        newTable[i] = entry;
                        entry = next;
                    } while( entry !== null );
                }
            }
        },

        putAll: function (map) {
            var numKeyToBeAdded = map.size();
            if( numKeyToBeAdded === 0 ) {
                return;
            }

            if( numKeyToBeAdded > this._threshold ) {
                var targetCapacity = (numKeyToBeAdded / this._loadFactor + 1) | 0;
                if( targetCapacity > this.self( arguments ).MAXIMUM_CAPACITY ) {
                    targetCapacity = this.self( arguments ).MAXIMUM_CAPACITY;
                }

                var newCapacity = this._table.length;
                while( newCapacity < targetCapacity ) {
                    newCapacity <<= 1;
                }
                if( newCapacity > this._table.length ) {
                    this._resize( newCapacity );
                }
            }

            var iterator = map.entrySet().iterator();
            while( iterator.hasNext() ) {
                var entry = iterator.next();
                this.put( entry.getKey(), entry.getValue() );
            }
        },

        remove: function (key) {
            var entry = this._removeEntryForKey( key );
            return entry === null ? null : entry._value;
        },

        _removeEntryForKey: function (key) {
            var hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length ),
                prev = this._table[i],
                entry = prev;

            while( entry !== null ) {
                var next = entry._next,
                    /** @type jsava.lang.Object */
                        k;
                if( entry._hash === hash
                    && ( (k = entry._key) === key || ( key !== null && key.equals( k ) ) ) ) {
                    this._modCount++;
                    this._size--;
                    if( prev === entry ) {
                        this._table[i] = next;
                    } else {
                        prev._next = next;
                    }
                    entry._recordRemoval( this );
                    return entry;
                }
                prev = entry;
                entry = next;
            }

            return entry;
        },

        _removeMapping: function (obj) {
            if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) {
                return null;
            }

            /** @implements jsava.util.Map.Entry */
            var entry = obj,
                key = entry.getKey(),
                hash = (key === null) ? 0 : this.self( arguments )._hash( key.hashCode() ),
                i = this.self( arguments )._indexFor( hash, this._table.length ),
                prev = this._table[i],
                e = prev;

            while( e !== null ) {
                var next = e._next;
                if( e._hash === hash && e.equals( entry ) ) {
                    this._modCount++;
                    this._size--;
                    if( prev === e ) {
                        this._table[i] = next;
                    } else {
                        prev._next = next;
                    }
                    e._recordRemoval( this );
                    return e;
                }
                prev = e;
                e = next;
            }

            return e;
        },

        clear: function () {
            this._modCount++;
            var table = this._table;
            for( var i = 0; i < table.length; i++ ) {
                table[i] = null;
            }
            this._size = 0;
        },

        containsValue: function (value) {
            if( value === null ) {
                return this.__containsNullValue();
            }

            var table = this._table;
            for( var i = 0; i < table.length; i++ ) {
                for( var entry = table[i]; entry !== null; entry = entry._next ) {
                    if( value.equals( entry._value ) ) {
                        return true;
                    }
                }
            }

            return false;
        },

        __containsNullValue: function () {
            var table = this._table;
            for( var i = 0; i < table.length; i++ ) {
                for( var entry = table[i]; entry !== null; entry = entry._next ) {
                    if( entry._value === null ) {
                        return true;
                    }
                }
            }

            return false;
        },

        clone: function () {
            /** @type jsava.util.HashMap */
            var result = null;
            try {
                result = this.base( arguments );
            } catch( e ) {
                if( !qx.Class.isSubClassOf( e.constructor, jsava.lang.CloneNotSupportedException ) ) {
                    throw e;
                }
            }

            result._table = jsava.JsavaUtils.emptyArrayOfGivenSize( this._table.length, null );
            result.__entrySet = null;
            result._modCount = 0;
            result._size = 0;
            result._init();
            result.__putAllForCreate( this );

            return result;
        },

        _addEntry: function (hash, key, value, bucketIndex) {
            var entry = this._table[bucketIndex];
            this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry );
            if( this._size++ >= this._threshold ) {
                this._resize( 2 * this._table.length );
            }
        },

        _createEntry: function (hash, key, value, bucketIndex) {
            var entry = this._table[bucketIndex];
            this._table[bucketIndex] = new (this.self( arguments ).Entry)( hash, key, value, entry );
            this._size++;
        },

        keySet: function () {
            var keySet = this._keySet;
            return keySet !== null ? keySet : ( this._keySet = new this.KeySet( this ) );
        },

        values: function () {
            var values = this._values;
            return values !== null ? values : ( this._values = new this.Values( this ) );
        },

        entrySet: function () {
            return this.__entrySet0();
        },

        __entrySet0: function () {
            var entrySet = this.__entrySet;
            return entrySet !== null ? entrySet : ( this.__entrySet = new this.EntrySet( this ) );
        },

        /** @private */
        HashIterator: qx.Class.define( 'jsava.util.HashMap.HashIterator', {
            extend: jsava.lang.Object,
            implement: [jsava.util.Iterator],

            type: 'abstract',

            /** @protected */
            construct: function (thisHashMap) {
                this.__thisHashMap = thisHashMap;
                this._expectedModCount = this.__thisHashMap._modCount;
                if( this.__thisHashMap._size > 0 ) {
                    var table = this.__thisHashMap._table;
                    while( this._index < table.length && ( this._next = table[this._index++] ) === null ) {
                        // do nothing
                    }
                }
            },

            members: {
                __thisHashMap: null,

                /** @type jsava.util.HashMap.Entry */
                _next: null,
                /** @type Number */
                _expectedModCount: 0,
                /** @type Number */
                _index: 0,
                /** @type jsava.util.HashMap.Entry */
                _current: null,

                hasNext: function () {
                    return this._next !== null;
                },

                _nextEntry: function () {
                    if( this.__thisHashMap._modCount !== this._expectedModCount ) {
                        throw new jsava.lang.ConcurrentModificationException();
                    }

                    var entry = this._next;
                    if( entry === null ) {
                        throw new jsava.lang.NoSuchElementException();
                    }

                    if( (this._next = entry._next) === null ) {
                        var table = this.__thisHashMap._table;
                        while( this._index < table.length && ( this._next = table[this._index++] ) === null ) {
                            // do nothing
                        }
                    }

                    this._current = entry;
                    return entry;
                },

                remove: function () {
                    if( this._current === null ) {
                        throw new jsava.lang.IllegalStateException();
                    }

                    if( this.__thisHashMap._modCount !== this._expectedModCount ) {
                        throw new jsava.lang.ConcurrentModificationException();
                    }

                    var key = this._current._key;
                    this._current = null;
                    this.__thisHashMap._removeEntryForKey( key );
                    this._expectedModCount = this.__thisHashMap._modCount;
                }
            }
        } ),

        _newKeyIterator: function () {
            return new this.KeyIterator( this );
        },

        _newValueIterator: function () {
            return new this.ValueIterator( this );
        },

        _newEntryIterator: function () {
            return new this.EntryIterator( this );
        },

        /** @private */
        ValueIterator: qx.Class.define( 'jsava.util.HashMap.ValueIterator', {
            extend: jsava.util.HashMap.HashIterator,

            construct: function (thisHashMap) {
                this.base( arguments, thisHashMap );
            },

            members: {
                next: function () {
                    return this._nextEntry()._value;
                }
            }
        } ),

        /** @private */
        KeyIterator: qx.Class.define( 'jsava.util.HashMap.KeyIterator', {
            extend: jsava.util.HashMap.HashIterator,

            construct: function (thisHashMap) {
                this.base( arguments, thisHashMap );
            },

            members: {
                next: function () {
                    return this._nextEntry().getKey();
                }
            }
        } ),

        /** @private */
        EntryIterator: qx.Class.define( 'jsava.util.HashMap.EntryIterator', {
            extend: jsava.util.HashMap.HashIterator,

            construct: function (thisHashMap) {
                this.base( arguments, thisHashMap );
            },

            members: {
                next: function () {
                    return this._nextEntry();
                }
            }
        } ),

        /** @private */
        KeySet: qx.Class.define( 'jsava.util.HashMap.KeySet', {
            extend: jsava.util.AbstractSet,

            construct: function (thisHashMap) {
                this.base( arguments );
                this.__thisHashMap = thisHashMap;
            },

            members: {
                __thisHashMap: null,

                iterator: function () {
                    return this.__thisHashMap._newKeyIterator();
                },

                size: function () {
                    return this.__thisHashMap._size;
                },

                contains: function (obj) {
                    return this.__thisHashMap.containsKey( obj );
                },

                remove: function (obj) {
                    return this.__thisHashMap._removeEntryForKey( obj ) !== null;
                },

                clear: function () {
                    this.__thisHashMap.clear();
                }
            }
        } ),

        /** @private */
        Values: qx.Class.define( 'jsava.util.HashMap.Values', {
            extend: jsava.util.AbstractCollection,

            construct: function (thisHashMap) {
                this.base( arguments );
                this.__thisHashMap = thisHashMap;
            },

            members: {
                __thisHashMap: null,

                iterator: function () {
                    return this.__thisHashMap._newValueIterator();
                },

                size: function () {
                    return this.__thisHashMap._size;
                },

                contains: function (obj) {
                    return this.__thisHashMap.containsValue( obj );
                },

                clear: function () {
                    this.__thisHashMap.clear();
                }
            }
        } ),

        /** @private */
        EntrySet: qx.Class.define( 'jsava.util.HashMap.EntrySet', {
            extend: jsava.util.AbstractSet,

            construct: function (thisHashMap) {
                this.base( arguments );
                this.__thisHashMap = thisHashMap;
            },

            members: {
                __thisHashMap: null,

                iterator: function () {
                    return this.__thisHashMap._newEntryIterator();
                },

                size: function () {
                    return this.__thisHashMap._size;
                },

                contains: function (obj) {
                    if( obj === null || !qx.Class.implementsInterface( obj, jsava.util.Map.Entry ) ) {
                        return false;
                    }

                    /** @implements jsava.util.Map.Entry */
                    var entry = obj,
                        candidate = this.__thisHashMap._getEntry( entry.getKey() );
                    return candidate !== null && candidate.equals( entry );
                },

                remove: function (obj) {
                    return this.__thisHashMap._removeMapping( obj ) !== null;
                },

                clear: function () {
                    this.__thisHashMap.clear();
                }
            }
        } )
    }
} );

Hmm cách tiếp cận thú vị .. bạn đã xem xét thử một cách tiếp cận tự động? nghĩa là, chạy trình biên dịch Java-javascript trên mã nguồn để triển khai java hiện tại?
Claudiu

Không :) Đây chỉ là một dự án thú vị đối với tôi và có khá nhiều thứ tôi không thể đơn giản là "sao chép" mã. Tôi không biết về trình biên dịch Java-Javascript, mặc dù tôi sẽ tin rằng chúng tồn tại. Tôi không chắc họ sẽ dịch cái này tốt đến mức nào. Mặc dù vậy, tôi khá chắc chắn rằng họ sẽ không tạo ra mã chất lượng tốt trong mọi trường hợp.
Ingo Bürk

Ah gotcha. Tôi đã nghĩ đến trình biên dịch Google Web Toolkit , nhưng có vẻ như cuối cùng họ đã làm những gì bạn đang làm ở đây cho các thư viện cốt lõi: "Trình biên dịch GWT hỗ trợ phần lớn ngôn ngữ Java. Thư viện thời gian chạy GWT mô phỏng một tập hợp con có liên quan của Thư viện thời gian chạy Java. ". Có lẽ một cái gì đó để xem xét để xem làm thế nào những người khác giải quyết vấn đề tương tự!
Claudiu

Vâng. Tôi chắc chắn rằng giải pháp của Google vượt xa tôi, nhưng một lần nữa, tôi chỉ vui chơi một chút. Thật không may, mã nguồn dường như đã bị thu hồi (?), Ít nhất là tôi không thể duyệt nó và các liên kết thú vị dường như đã chết. Thật tệ, tôi rất thích nhìn vào nó.
Ingo Bürk

Vui chơi xung quanh là cách tốt nhất để học =). cảm ơn vì đã chia sẻ
Claudiu

0

Một bản đồ khác do tôi thực hiện. Với ngẫu nhiên, 'generic' và 'iterator' =)

var HashMap = function (TKey, TValue) {
    var db = [];
    var keyType, valueType;

    (function () {
        keyType = TKey;
        valueType = TValue;
    })();

    var getIndexOfKey = function (key) {
        if (typeof key !== keyType)
            throw new Error('Type of key should be ' + keyType);
        for (var i = 0; i < db.length; i++) {
            if (db[i][0] == key)
                return i;
        }
        return -1;
    }

    this.add = function (key, value) {
        if (typeof key !== keyType) {
            throw new Error('Type of key should be ' + keyType);
        } else if (typeof value !== valueType) {
            throw new Error('Type of value should be ' + valueType);
        }
        var index = getIndexOfKey(key);
        if (index === -1)
            db.push([key, value]);
        else
            db[index][1] = value;
        return this;
    }

    this.get = function (key) {
        if (typeof key !== keyType || db.length === 0)
            return null;
        for (var i = 0; i < db.length; i++) {
            if (db[i][0] == key)
                return db[i][1];
        }
        return null;
    }

    this.size = function () {
        return db.length;
    }

    this.keys = function () {
        if (db.length === 0)
            return [];
        var result = [];
        for (var i = 0; i < db.length; i++) {
            result.push(db[i][0]);
        }
        return result;
    }

    this.values = function () {
        if (db.length === 0)
            return [];
        var result = [];
        for (var i = 0; i < db.length; i++) {
            result.push(db[i][1]);
        }
        return result;
    }

    this.randomize = function () {
        if (db.length === 0)
            return this;
        var currentIndex = db.length, temporaryValue, randomIndex;
        while (0 !== currentIndex) {
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex--;
            temporaryValue = db[currentIndex];
            db[currentIndex] = db[randomIndex];
            db[randomIndex] = temporaryValue;
        }
        return this;
    }

    this.iterate = function (callback) {
        if (db.length === 0)
            return false;
        for (var i = 0; i < db.length; i++) {
            callback(db[i][0], db[i][1]);
        }
        return true;
    }
}

Thí dụ:

var a = new HashMap("string", "number");
a.add('test', 1132)
 .add('test14', 666)
 .add('1421test14', 12312666)
 .iterate(function (key, value) {console.log('a['+key+']='+value)});
/*
a[test]=1132
a[test14]=666
a[1421test14]=12312666 
*/
a.randomize();
/*
a[1421test14]=12312666
a[test]=1132
a[test14]=666
*/
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.