Từ điển python được lưu trữ trong bộ nhớ như thế nào?
Bộ và từ điển là cấu trúc dữ liệu lý tưởng được sử dụng khi dữ liệu của bạn không có thứ tự nội tại, nhưng có một đối tượng duy nhất có thể được sử dụng để tham chiếu nó (đối tượng tham chiếu thường là một chuỗi, nhưng có thể là bất kỳ loại có thể băm nào). Đối tượng tham chiếu này được gọi là “khóa”, trong khi dữ liệu là “giá trị. ” Từ điển và bộ gần như giống hệt nhau, ngoại trừ bộ không thực sự chứa giá trị. một bộ chỉ đơn giản là một tập hợp các khóa duy nhất. Như tên ngụ ý, tập hợp rất hữu ích để thực hiện các thao tác tập hợp Show Ghi chúLoại có thể băm là loại thực hiện cả hàm ma thuật 6 và 7 hoặc 8. Tất cả các kiểu gốc trong Python đã triển khai những kiểu này và mọi lớp người dùng đều có giá trị mặc định. Xem để biết thêm chi tiết Trong khi chúng ta đã thấy trong chương trước rằng chúng ta bị hạn chế, tốt nhất là, thời gian tra cứu 9 trên danh sách/bộ không có thứ tự nội tại (thông qua thao tác tìm kiếm), từ điển và bộ cung cấp cho chúng ta 0 tra cứu dựa trên chỉ mục tùy ý. Ngoài ra, như danh sách/bộ dữ liệu, từ điển và bộ có thời gian chèn 1. [] Như chúng ta sẽ thấy trong phần , tốc độ này được thực hiện thông qua việc sử dụng bảng băm địa chỉ mở làm cấu trúc dữ liệu cơ bản Tuy nhiên, có một chi phí để sử dụng từ điển và bộ. Đầu tiên, chúng thường chiếm dung lượng lớn hơn trong bộ nhớ. Ngoài ra, mặc dù độ phức tạp của việc chèn/tra cứu là 1, nhưng tốc độ thực tế phụ thuộc rất nhiều vào hàm băm đang được sử dụng. Nếu đánh giá hàm băm chậm, thì mọi thao tác trên từ điển hoặc bộ sẽ chậm tương tự Hãy xem một ví dụ. Giả sử chúng tôi muốn lưu trữ thông tin liên hệ của mọi người trong danh bạ điện thoại. Chúng tôi muốn lưu trữ thông tin này ở dạng giúp dễ dàng trả lời câu hỏi, "Số điện thoại của John Doe là gì?" . Với danh sách, chúng tôi sẽ lưu trữ số điện thoại và tên theo thứ tự và quét qua toàn bộ danh sách để tìm số điện thoại mà chúng tôi yêu cầu, như minh họa trong Ví dụ 4-1. tra cứu danh bạ điện thoại với một danh sách
Ghi chúChúng tôi cũng có thể làm điều này bằng cách sắp xếp danh sách và sử dụng mô-đun 3 để có được hiệu suất 9 Tuy nhiên, với một từ điển, chúng ta có thể chỉ cần có “chỉ mục” là tên và “giá trị” là số điện thoại, như thể hiện trong. Điều này cho phép chúng tôi chỉ cần tra cứu giá trị chúng tôi cần và nhận tham chiếu trực tiếp đến giá trị đó, thay vì phải đọc mọi giá trị trong tập dữ liệu của chúng tôi Ví dụ 4-2. Tra cứu danh bạ điện thoại bằng từ điển
Đối với danh bạ điện thoại lớn, sự khác biệt giữa thời gian tra cứu 1 của từ điển và thời gian 0 để tìm kiếm tuyến tính trên danh sách (hoặc tốt nhất là thời gian 9 với mô-đun chia đôi) là khá đáng kể MẹoTạo một tập lệnh nhân đôi hiệu suất của phương pháp list- 3 so với từ điển để tìm một số trong danh bạ điện thoại. Quy mô thời gian như thế nào khi kích thước của danh bạ điện thoại tăng lên? Mặt khác, nếu chúng tôi muốn trả lời câu hỏi, "Có bao nhiêu tên duy nhất trong danh bạ điện thoại của tôi?" . Nhớ lại rằng một tập hợp chỉ đơn giản là một tập hợp các khóa duy nhất—đây là thuộc tính chính xác mà chúng tôi muốn thực thi trong dữ liệu của mình. Điều này hoàn toàn trái ngược với cách tiếp cận dựa trên danh sách, trong đó thuộc tính đó cần được thực thi tách biệt khỏi cấu trúc dữ liệu bằng cách so sánh tất cả các tên với tất cả các tên khác. minh họa Ví dụ 4-3. Tìm tên duy nhất với danh sách và tập hợp
Chúng tôi phải xem qua tất cả các mục trong danh bạ điện thoại của mình và do đó vòng lặp này có giá 0 Ở đây, chúng ta phải kiểm tra tên hiện tại với tất cả các tên duy nhất mà chúng ta đã thấy. Nếu đó là một tên duy nhất mới, chúng tôi sẽ thêm nó vào danh sách các tên duy nhất của chúng tôi. Sau đó, chúng tôi tiếp tục thông qua danh sách, thực hiện bước này cho mọi mục trong danh bạ điện thoại Đối với phương thức set, thay vì lặp lại tất cả các tên duy nhất mà chúng ta đã thấy, chúng ta chỉ cần thêm tên hiện tại vào tập hợp các tên duy nhất của mình. Vì các bộ đảm bảo tính duy nhất của các khóa mà chúng chứa, nên nếu bạn cố gắng thêm một mục đã có trong bộ, thì mục đó sẽ không được thêm vào. Hơn nữa, chi phí hoạt động này 1 Vòng lặp bên trong của thuật toán danh sách lặp lại trên 81, vòng lặp này bắt đầu trống và sau đó phát triển, trong trường hợp xấu nhất, khi tất cả các tên là duy nhất, có kích thước bằng 82. Điều này có thể được coi là thực hiện cho từng tên trong danh bạ điện thoại trên một danh sách không ngừng phát triển. Do đó, thuật toán hoàn chỉnh hoạt động như 83, vì vòng lặp bên ngoài đóng góp hệ số 0, trong khi vòng lặp bên trong đóng góp hệ số 9 Mặt khác, thuật toán tập hợp không có vòng lặp bên trong; . Do đó, đóng góp không liên tục duy nhất vào độ phức tạp của thuật toán này là vòng lặp trên danh bạ điện thoại, khiến thuật toán này hoạt động trong 0 Khi định thời gian cho hai thuật toán này bằng cách sử dụng một 82 với 10.000 mục nhập và 7.422 tên riêng biệt, chúng tôi thấy sự khác biệt lớn giữa 0 và 83 có thể lớn đến mức nào 8 Nói cách khác, thuật toán thiết lập đã cho chúng tôi tăng tốc 267 lần. Ngoài ra, khi kích thước của 82 tăng lên, tốc độ tăng lên (chúng tôi nhận được tốc độ tăng gấp 557 lần với 82 với 100.000 mục nhập và 15.574 tên riêng) Từ điển và bộ hoạt động như thế nào?Từ điển và bộ sử dụng bảng băm để đạt được tra cứu và chèn 1 của chúng. Hiệu quả này là kết quả của việc sử dụng a rất thông minh để xoay một phím tùy ý (i. e. , một chuỗi hoặc đối tượng) thành chỉ mục cho danh sách. Hàm băm và danh sách sau này có thể được sử dụng để xác định vị trí của bất kỳ phần dữ liệu cụ thể nào ngay lập tức mà không cần tìm kiếm. Bằng cách biến khóa của dữ liệu thành thứ có thể được sử dụng như chỉ mục danh sách, chúng tôi có thể đạt được hiệu suất tương tự như với danh sách. Ngoài ra, thay vì phải tham chiếu đến dữ liệu bằng một chỉ mục số, bản thân chỉ số này ngụ ý một số thứ tự đối với dữ liệu, chúng ta có thể tham chiếu đến nó bằng khóa tùy ý này Để tạo bảng băm từ đầu, chúng tôi bắt đầu với một số bộ nhớ được phân bổ, tương tự như những gì chúng tôi đã bắt đầu với mảng. Đối với một mảng, nếu chúng ta muốn chèn dữ liệu, chúng ta chỉ cần tìm nhóm nhỏ nhất chưa sử dụng và chèn dữ liệu của mình vào đó (và thay đổi kích thước nếu cần). Đối với các bảng băm, trước tiên chúng ta phải tìm ra vị trí của dữ liệu trong đoạn bộ nhớ liền kề này Vị trí của dữ liệu mới phụ thuộc vào hai thuộc tính của dữ liệu chúng tôi đang chèn. giá trị băm của khóa và cách so sánh giá trị với các đối tượng khác. Điều này là do khi chúng ta chèn dữ liệu, khóa đầu tiên được băm và ẩn để nó biến thành một chỉ mục hiệu quả trong một mảng. [] Mặt nạ đảm bảo rằng giá trị băm, có thể lấy giá trị của bất kỳ số nguyên nào, phù hợp với số lượng nhóm được phân bổ. Vì vậy, nếu chúng tôi đã phân bổ 8 khối bộ nhớ và giá trị băm của chúng tôi là 35, thì chúng tôi coi bộ chứa ở chỉ mục 36. Tuy nhiên, nếu từ điển của chúng tôi đã phát triển để yêu cầu 512 khối bộ nhớ, thì mặt nạ sẽ trở thành 37 (và trong trường hợp này, chúng tôi sẽ xem xét nhóm ở chỉ mục 38). Bây giờ chúng ta phải kiểm tra xem thùng này đã được sử dụng chưa. Nếu nó trống, chúng ta có thể chèn khóa và giá trị vào khối bộ nhớ này. Chúng tôi lưu trữ khóa để có thể đảm bảo rằng chúng tôi đang truy xuất đúng giá trị khi tra cứu. Nếu nó đang được sử dụng và giá trị của bộ chứa bằng với giá trị chúng tôi muốn chèn (so sánh được thực hiện với 39 tích hợp sẵn), thì cặp khóa/giá trị đã có trong bảng băm và chúng tôi có thể trả về. Tuy nhiên, nếu các giá trị không khớp nhau, thì chúng ta phải tìm một nơi mới để đặt dữ liệu Để tìm chỉ mục mới, chúng tôi tính toán chỉ mục mới bằng cách sử dụng một hàm tuyến tính đơn giản, một phương pháp được gọi là thăm dò. Cơ chế thăm dò của Python thêm phần đóng góp từ các bit bậc cao hơn của hàm băm ban đầu (hãy nhớ rằng đối với bảng có độ dài 8, chúng tôi chỉ xem xét 3 bit cuối cùng của hàm băm cho chỉ mục ban đầu, thông qua việc sử dụng giá trị mặt nạ là 80). Việc sử dụng các bit bậc cao hơn này sẽ mang lại cho mỗi hàm băm một chuỗi các hàm băm tiếp theo có thể khác nhau, giúp tránh xung đột trong tương lai. Có rất nhiều sự tự do khi chọn thuật toán để tạo một chỉ mục mới; . Dữ liệu được phân phối tốt như thế nào trong bảng băm được gọi là “hệ số tải” và có liên quan đến hàm băm. Mã giả minh họa cách tính chỉ số băm được sử dụng trong CPython 2. 7 Ví dụ 4-4. Trình tự tra cứu từ điển 3 81 trả về một số nguyên, trong khi mã C thực tế trong CPython sử dụng một số nguyên không dấu. Do đó, mã giả này không sao chép 100% hành vi trong CPython; Thăm dò này là một sửa đổi của phương pháp ngây thơ của “thăm dò tuyến tính. ” Trong thăm dò tuyến tính, chúng tôi chỉ đơn giản đưa ra các giá trị 82, trong đó 83 được khởi tạo thành giá trị băm của khóa và giá trị '5` không quan trọng đối với cuộc thảo luận hiện tại. [] Một điều quan trọng cần lưu ý là thăm dò tuyến tính chỉ xử lý vài byte cuối cùng của hàm băm và bỏ qua phần còn lại (i. e. , đối với một từ điển có tám phần tử, chúng tôi chỉ xem xét 3 bit cuối cùng vì tại thời điểm đó, mặt nạ là 84). Điều này có nghĩa là nếu băm hai mục cho ba chữ số nhị phân cuối cùng giống nhau, chúng ta sẽ không chỉ xảy ra xung đột mà chuỗi các chỉ số được thăm dò sẽ giống nhau. Lược đồ nhiễu loạn mà Python sử dụng sẽ bắt đầu xem xét nhiều bit hơn từ hàm băm của các mục để giải quyết vấn đề này Một quy trình tương tự được thực hiện khi chúng tôi đang thực hiện tra cứu trên một khóa cụ thể. khóa đã cho được chuyển đổi thành một chỉ mục và chỉ mục đó được kiểm tra. Nếu khóa trong chỉ mục đó khớp (hãy nhớ rằng chúng tôi cũng lưu trữ khóa gốc khi thực hiện thao tác chèn), thì chúng tôi có thể trả về giá trị đó. Nếu không, chúng tôi tiếp tục tạo các chỉ mục mới bằng cách sử dụng cùng một sơ đồ, cho đến khi chúng tôi tìm thấy dữ liệu hoặc chạm vào một thùng trống. Nếu chúng ta nhấn vào một thùng rỗng, chúng ta có thể kết luận rằng dữ liệu không tồn tại trong bảng minh họa quá trình thêm một số dữ liệu vào bảng băm. Ở đây, chúng tôi đã chọn tạo một hàm băm chỉ sử dụng chữ cái đầu tiên của đầu vào. Chúng tôi thực hiện điều này bằng cách sử dụng hàm 85 của Python trên chữ cái đầu tiên của đầu vào để lấy biểu diễn số nguyên của chữ cái đó (nhớ lại các hàm băm phải trả về số nguyên). Như chúng ta sẽ thấy trong phần , Python cung cấp các hàm băm cho hầu hết các loại của nó, vì vậy thông thường bạn sẽ không phải tự cung cấp một hàm băm trừ những trường hợp cực đoan Hình 4-1. Bảng băm kết quả từ việc chèn có va chạm Việc chèn khóa “Barcelona” gây ra xung đột và một chỉ mục mới được tính toán bằng lược đồ trong. Từ điển này cũng có thể được tạo bằng Python bằng cách sử dụng mã trong Ví dụ 4-5. Hàm băm tùy chỉnh 8 Trong trường hợp này, “Barcelona” và “Rome” gây ra xung đột hàm băm ( hiển thị kết quả của việc chèn này). Chúng tôi thấy điều này bởi vì đối với một từ điển có bốn phần tử, chúng tôi có giá trị mặt nạ là 86. Do đó, “Barcelona” sẽ cố gắng sử dụng chỉ số 87. Tương tự, “Rome” sẽ cố gắng sử dụng chỉ số 88 89 MẹoLàm việc thông qua các vấn đề sau. Một cuộc thảo luận về va chạm băm sau
Đối với 500 thành phố, sẽ có khoảng 474 phần tử từ điển xung đột với giá trị trước đó (500–26), với mỗi hàm băm có 500/26 = 19. 2 thành phố liên kết với nó. Đối với 1.000 thành phố, 974 phần tử sẽ xung đột và mỗi hàm băm sẽ có 1.000/26 = 38. 4 thành phố liên kết với nó. Điều này là do hàm băm chỉ đơn giản dựa trên giá trị số của chữ cái đầu tiên, chỉ có thể nhận giá trị từ 00– 01, chỉ cho phép 26 giá trị hàm băm độc lập. Điều này có nghĩa là một lần tra cứu trong bảng này có thể yêu cầu tới 38 lần tra cứu tiếp theo để tìm ra giá trị chính xác. Để khắc phục điều này, chúng ta phải tăng số lượng giá trị băm có thể có bằng cách xem xét các khía cạnh khác của thành phố trong hàm băm. Hàm băm mặc định trên một chuỗi xem xét mọi ký tự để tối đa hóa số lượng giá trị có thể. Xem để giải thích thêm Khi một giá trị bị xóa khỏi bảng băm, chúng ta không thể chỉ ghi một 02 vào bộ nhớ đó. Điều này là do chúng tôi đã sử dụng 02 làm giá trị trọng điểm trong khi thăm dò xung đột hàm băm. Do đó, chúng ta phải viết một giá trị đặc biệt biểu thị rằng nhóm trống, nhưng vẫn có thể có các giá trị sau nó để xem xét khi giải quyết xung đột hàm băm. Các vị trí trống này có thể được ghi vào trong tương lai và bị xóa khi bảng băm được thay đổi kích thước Khi nhiều mục được chèn vào bảng băm, bảng phải được thay đổi kích thước để phù hợp với nó. Có thể chỉ ra rằng một bảng không đầy quá hai phần ba sẽ tiết kiệm không gian tối ưu trong khi vẫn có giới hạn tốt về số lần va chạm mong đợi. Do đó, khi một bảng đạt đến điểm tới hạn này, nó sẽ phát triển. Để làm điều này, một bảng lớn hơn được phân bổ (i. e. , nhiều bộ chứa hơn trong bộ nhớ được dành riêng), mặt nạ được điều chỉnh để phù hợp với bảng mới và tất cả các thành phần của bảng cũ được chèn lại vào bảng mới. Điều này yêu cầu tính toán lại các chỉ số, vì mặt nạ đã thay đổi sẽ thay đổi chỉ mục kết quả. Do đó, việc thay đổi kích thước các bảng băm lớn có thể khá tốn kém. Tuy nhiên, vì chúng tôi chỉ thực hiện thao tác thay đổi kích thước này khi bảng quá nhỏ, trái ngược với mỗi lần chèn, nên chi phí phân bổ của một lần chèn vẫn là 1 Theo mặc định, kích thước nhỏ nhất của từ điển hoặc bộ là 8 (nghĩa là nếu bạn chỉ lưu trữ ba giá trị, Python vẫn sẽ phân bổ tám phần tử). Khi thay đổi kích thước, số lượng nhóm tăng gấp 4 lần cho đến khi chúng tôi đạt 50.000 phần tử, sau đó kích thước tăng gấp 2 lần. Điều này mang lại các kích thước có thể sau đây 0 Điều quan trọng cần lưu ý là việc thay đổi kích thước có thể xảy ra để làm cho bảng băm lớn hơn hoặc nhỏ hơn. Nghĩa là, nếu đủ nhiều phần tử của bảng băm bị xóa, bảng có thể được thu nhỏ kích thước. Tuy nhiên, thay đổi kích thước chỉ xảy ra trong quá trình chèn Hàm băm và EntropyCác đối tượng trong Python nói chung có thể băm được, vì chúng đã có sẵn các hàm 6 và 8 được liên kết với chúng. Đối với các loại số ( 07 và 08), hàm băm chỉ đơn giản dựa trên giá trị bit của số mà chúng đại diện. Bộ dữ liệu và chuỗi có giá trị băm dựa trên nội dung của chúng. Mặt khác, danh sách không hỗ trợ băm vì giá trị của chúng có thể thay đổi. Vì các giá trị của danh sách có thể thay đổi, nên hàm băm đại diện cho danh sách cũng có thể thay đổi vị trí tương đối của khóa đó trong bảng băm. [] Các lớp do người dùng định nghĩa cũng có các hàm so sánh và hàm băm mặc định. Hàm 6 mặc định chỉ trả về vị trí của đối tượng trong bộ nhớ như được cung cấp bởi hàm 10 tích hợp. Tương tự, toán tử 8 so sánh giá trị số của vị trí của đối tượng trong bộ nhớ Điều này thường được chấp nhận, vì hai thể hiện của một lớp nói chung là khác nhau và không xung đột trong một bảng băm. Tuy nhiên, trong một số trường hợp, chúng tôi muốn sử dụng các đối tượng 12 hoặc 13 để phân biệt giữa các mục. Lấy định nghĩa lớp sau 1 Nếu chúng ta khởi tạo nhiều đối tượng 14 có cùng giá trị cho 15 và 16, thì tất cả chúng sẽ là các đối tượng độc lập trong bộ nhớ và do đó có các vị trí khác nhau trong bộ nhớ, điều này sẽ cung cấp cho chúng tất cả các giá trị băm khác nhau. Điều này có nghĩa là đặt tất cả chúng vào một 12 sẽ dẫn đến việc tất cả chúng đều có các mục riêng lẻ 0 Chúng ta có thể khắc phục điều này bằng cách tạo một hàm băm tùy chỉnh dựa trên nội dung thực tế của đối tượng thay vì vị trí của đối tượng trong bộ nhớ. Hàm băm có thể tùy ý miễn là nó luôn cho cùng một kết quả cho cùng một đối tượng. (Cũng có những cân nhắc liên quan đến entropy của hàm băm, mà chúng ta sẽ thảo luận sau. ) Việc định nghĩa lại lớp 14 sau đây sẽ mang lại kết quả mà chúng ta mong đợi 2 Điều này cho phép chúng tôi tạo các mục nhập trong một tập hợp hoặc từ điển được lập chỉ mục bởi các thuộc tính của đối tượng 14 chứ không phải địa chỉ bộ nhớ của đối tượng được khởi tạo 0 Như đã đề cập ở phần trước khi chúng ta thảo luận về xung đột băm, hàm băm được chọn tùy chỉnh phải cẩn thận để phân phối đồng đều các giá trị băm để tránh xung đột. Có nhiều xung đột sẽ làm giảm hiệu suất của bảng băm. nếu hầu hết các khóa có xung đột, thì chúng ta cần phải liên tục "thăm dò" các giá trị khác, đi bộ một cách hiệu quả một phần lớn tiềm năng của từ điển để tìm khóa được đề cập. Trong trường hợp xấu nhất, khi tất cả các khóa trong từ điển xung đột với nhau, hiệu suất tra cứu trong từ điển là 0 và do đó giống như khi chúng ta tìm kiếm trong danh sách Nếu chúng tôi biết rằng chúng tôi đang lưu trữ 5.000 giá trị trong từ điển và chúng tôi cần tạo hàm băm cho đối tượng mà chúng tôi muốn sử dụng làm khóa, chúng tôi phải biết rằng từ điển sẽ được lưu trữ trong bảng băm có kích thước 32.768 và Ý tưởng về “hàm băm của tôi được phân phối tốt như thế nào” được gọi là entropy của hàm băm. Entropy, được định nghĩa là trong đó 02 là xác suất mà hàm băm cho hàm băm 83. Nó được tối đa hóa khi mọi giá trị băm có xác suất được chọn bằng nhau. Hàm băm tối đa hóa entropy được gọi là hàm băm lý tưởng vì nó đảm bảo số lượng va chạm tối thiểu Đối với một từ điển lớn vô hạn, hàm băm được sử dụng cho số nguyên là lý tưởng. Điều này là do giá trị băm của một số nguyên đơn giản là chính số nguyên đó. Đối với một từ điển lớn vô hạn, giá trị mặt nạ là vô hạn và do đó chúng tôi xem xét tất cả các bit trong giá trị băm. Do đó, với hai số bất kỳ, chúng tôi có thể đảm bảo rằng giá trị băm của chúng sẽ không giống nhau Tuy nhiên, nếu chúng tôi làm cho từ điển này trở nên hữu hạn, thì chúng tôi không thể có sự đảm bảo này nữa. Ví dụ: đối với từ điển có bốn thành phần, mặt nạ chúng tôi sử dụng là 86. Do đó, giá trị băm của số 05 là 06 và giá trị băm của 07 là 08, và do đó các mục nhập của chúng sẽ xung đột Ghi chúĐể tìm mặt nạ cho một từ điển có số lượng phần tử tùy ý, 09, trước tiên chúng tôi tìm số lượng nhóm tối thiểu mà từ điển phải có để vẫn đầy hai phần ba ( 20). Sau đó, chúng tôi tìm kích thước từ điển nhỏ nhất sẽ chứa số phần tử này (8; 32; 128; 512; 2.048; v.v. ) và tìm số bit cần thiết để giữ số này. Ví dụ: nếu 21, thì chúng ta phải có ít nhất 1.731 nhóm, nghĩa là chúng ta cần một từ điển có 2.048 nhóm. Vì vậy, mặt nạ là 22 Không có hàm băm nào tốt nhất để sử dụng khi sử dụng từ điển hữu hạn. Tuy nhiên, biết trước phạm vi giá trị nào sẽ được sử dụng và độ lớn của từ điển sẽ giúp đưa ra lựa chọn tốt. Ví dụ: nếu chúng tôi đang lưu trữ tất cả 676 tổ hợp của hai chữ cái viết thường làm khóa trong từ điển (aa, ab, ac, v.v. ), thì hàm băm tốt sẽ là hàm được hiển thị trong Ví dụ 4-6. Hàm băm hai ký tự tối ưu 1 Điều này không có xung đột băm đối với bất kỳ sự kết hợp nào của hai chữ cái viết thường, xem xét mặt nạ là 23 (từ điển gồm 676 giá trị sẽ được giữ trong bảng băm có độ dài 2.048, có mặt nạ là 24) chỉ ra rất rõ ràng sự phân nhánh của việc có một hàm băm tồi đối với một lớp do người dùng định nghĩa—ở đây, chi phí của một hàm băm tồi (trên thực tế, đó là hàm băm tồi tệ nhất có thể. ) là 21. Tốc độ tra cứu chậm gấp 8 lần Ví dụ 4-7. Sự khác biệt về thời gian giữa các hàm băm tốt và xấu 2 Mẹo
Từ điển và không gian tênTra cứu từ điển rất nhanh; . Một khu vực mà bề mặt này nằm trong quản lý không gian tên của Python, nơi sử dụng nhiều từ điển để thực hiện tra cứu của nó Bất cứ khi nào một biến, hàm hoặc mô-đun được gọi trong Python, sẽ có một hệ thống phân cấp xác định nơi nó tìm kiếm các đối tượng này. Đầu tiên, Python nhìn vào bên trong mảng 25, mảng này có các mục nhập cho tất cả các biến cục bộ. Python làm việc chăm chỉ để tra cứu biến cục bộ nhanh chóng và đây là phần duy nhất của chuỗi không yêu cầu tra cứu từ điển. Nếu nó không tồn tại ở đó, thì từ điển 26 được tìm kiếm. Cuối cùng, nếu đối tượng không được tìm thấy ở đó, đối tượng 27 sẽ được tìm kiếm. Điều quan trọng cần lưu ý là mặc dù 25 và 26 rõ ràng là từ điển và về mặt kỹ thuật, 27 là đối tượng mô-đun, khi tìm kiếm 27 cho một thuộc tính nhất định, chúng tôi chỉ thực hiện tra cứu từ điển bên trong bản đồ 25 của nó (đây là trường hợp cho tất cả các đối tượng mô-đun và . ) Để làm cho điều này rõ ràng hơn, hãy xem một ví dụ đơn giản về cách gọi các hàm được xác định trong các phạm vi khác nhau (). Chúng ta có thể phân tách các chức năng bằng mô-đun 03 () để hiểu rõ hơn về cách các tra cứu không gian tên này đang diễn ra Ví dụ 4-8. tra cứu không gian tên 3 Ví dụ 4-9. Tra cứu không gian tên được tháo rời 4 Hàm đầu tiên, 04, thực hiện cuộc gọi đến 05 bằng cách xem rõ ràng thư viện toán học. Điều này cũng hiển nhiên trong mã byte được tạo. trước tiên phải tải tham chiếu đến mô-đun 06, sau đó chúng tôi thực hiện tra cứu thuộc tính trên mô-đun này cho đến khi cuối cùng chúng tôi có tham chiếu đến hàm 05. Điều này được thực hiện thông qua hai lần tra cứu từ điển, một để tìm mô-đun 06 và một để tìm hàm 05 trong mô-đun Mặt khác, 10 nhập hàm ________ 005 một cách rõ ràng từ mô-đun ________ 006 và sau đó hàm này có thể truy cập trực tiếp trong không gian tên chung. Điều này có nghĩa là chúng ta có thể tránh tra cứu mô-đun 06 và tra cứu thuộc tính tiếp theo. Tuy nhiên, chúng ta vẫn phải tìm hàm 05 trong không gian tên chung. Đây là một lý do khác để nói rõ ràng về những chức năng bạn đang nhập từ một mô-đun. Thực hành này không chỉ làm cho mã dễ đọc hơn, bởi vì trình đọc biết chính xác chức năng nào được yêu cầu từ các nguồn bên ngoài, mà còn tăng tốc độ mã Cuối cùng, 15 định nghĩa hàm 05 làm đối số từ khóa, với giá trị mặc định của nó là tham chiếu đến hàm 05 trong mô-đun 06. Mặc dù chúng ta vẫn cần tìm tham chiếu đến hàm này trong mô-đun, nhưng điều này chỉ cần thiết khi hàm 15 được xác định lần đầu. Sau này, tham chiếu đến hàm 05 được lưu trữ trong định nghĩa hàm dưới dạng biến cục bộ ở dạng đối số từ khóa mặc định. Như đã đề cập trước đó, các biến cục bộ không cần tra từ điển để tìm; . Vì vậy, việc tìm kiếm hàm khá nhanh Mặc dù các hiệu ứng này là một kết quả thú vị của cách quản lý các không gian tên trong Python, nhưng 15 chắc chắn không phải là “Pythonic. ” May mắn thay, những tra cứu từ điển bổ sung này chỉ bắt đầu giảm hiệu suất khi chúng được gọi nhiều (i. e. , trong khối trong cùng của một vòng lặp rất nhanh, chẳng hạn như trong ví dụ về tập hợp Julia). Với suy nghĩ này, một giải pháp dễ đọc hơn sẽ là đặt một biến cục bộ với tham chiếu toàn cục trước khi vòng lặp bắt đầu. Chúng ta sẽ vẫn phải thực hiện tra cứu toàn cầu một lần bất cứ khi nào hàm được gọi, nhưng tất cả các lệnh gọi đến hàm đó trong vòng lặp sẽ được thực hiện nhanh hơn. Điều này nói lên thực tế là mã bị chậm thậm chí một phút có thể được khuếch đại nếu mã đó đang được chạy hàng triệu lần. Mặc dù việc tra cứu từ điển có thể chỉ mất vài trăm nano giây, nhưng nếu chúng ta lặp lại hàng triệu lần trong quá trình tra cứu này thì nó có thể nhanh chóng tăng lên. Trên thực tế, nhìn vào chúng ta thấy một số 9. Tăng tốc 4% chỉ bằng cách đặt hàm 05 cục bộ vào vòng lặp chặt chẽ gọi nó Ví dụ 4-10. Ảnh hưởng của tra cứu không gian tên chậm trong vòng lặp 5 Từ điển và bộ cung cấp một cách tuyệt vời để lưu trữ dữ liệu có thể được lập chỉ mục bằng một khóa. Cách khóa này được sử dụng, thông qua hàm băm, có thể ảnh hưởng lớn đến hiệu suất kết quả của cấu trúc dữ liệu. Hơn nữa, hiểu cách thức hoạt động của từ điển giúp bạn hiểu rõ hơn không chỉ về cách tổ chức dữ liệu mà còn về cách tổ chức mã của bạn, vì từ điển là một phần nội tại của chức năng nội bộ của Python Trong chương tiếp theo, chúng ta sẽ khám phá các trình tạo, cho phép chúng ta cung cấp dữ liệu cho mã với nhiều quyền kiểm soát hơn đối với việc đặt hàng và không phải lưu trữ toàn bộ tập dữ liệu trong bộ nhớ trước. Điều này cho phép chúng tôi vượt qua nhiều rào cản có thể gặp phải khi sử dụng bất kỳ cấu trúc dữ liệu nội tại nào của Python Từ điển Python được lưu trữ trong bộ nhớ như thế nào?Từ điển bao gồm một số nhóm . Mỗi nhóm này chứa mã băm của đối tượng chứa cặp khóa-giá trị. Một con trỏ tới đối tượng khóa và một con trỏ tới đối tượng giá trị.
Từ điển được lưu trữ như thế nào?Cách cơ bản nhất để lưu từ điển trong Python là lưu trữ chúng dưới dạng chuỗi trong tệp văn bản .
Làm thế nào để từ điển hoạt động nội bộ trong Python?Từ điển sử dụng hàm băm của mỗi khóa để thay đổi một số thông tin của khóa thành một số nguyên được gọi là giá trị băm . Giá trị băm cho bạn biết nên đặt cặp khóa-giá trị nào vào nhóm nào. Bằng cách này, mỗi khi bạn cần tra cứu hoặc tìm cặp khóa-giá trị đó, bạn sẽ biết chính xác nhóm nào cần tìm kiếm.
Từ điển sử dụng bao nhiêu bộ nhớ?Mỗi chữ cái thường chiếm 1 byte. Điều đó có nghĩa là bạn cần khoảng 1 MB bộ nhớ để lưu toàn bộ từ điển. Tất nhiên bạn có thể xì hơi (zip) và lưu trữ trong bộ nhớ, với tỷ lệ nén khoảng 90%. |