foreach
hỗ trợ lặp qua ba loại giá trị khác nhau:
Sau đây, tôi sẽ cố gắng giải thích chính xác cách thức lặp lại hoạt động trong các trường hợp khác nhau. Cho đến nay, trường hợp đơn giản nhất là Traversable
các đối tượng, vì về foreach
cơ bản, đây chỉ là đường cú pháp cho mã dọc theo các dòng này:
foreach ($it as $k => $v) { /* ... */ }
/* translates to: */
if ($it instanceof IteratorAggregate) {
$it = $it->getIterator();
}
for ($it->rewind(); $it->valid(); $it->next()) {
$v = $it->current();
$k = $it->key();
/* ... */
}
Đối với các lớp bên trong, các cuộc gọi phương thức thực tế được tránh bằng cách sử dụng API nội bộ về cơ bản chỉ phản chiếu Iterator
giao diện ở cấp độ C.
Lặp lại các mảng và các đối tượng đơn giản là phức tạp hơn đáng kể. Trước hết, cần lưu ý rằng trong "mảng" PHP là các từ điển thực sự được sắp xếp theo thứ tự và chúng sẽ được duyệt theo thứ tự này (khớp với thứ tự chèn miễn là bạn không sử dụng thứ gì đó như sort
). Điều này trái ngược với việc lặp theo thứ tự tự nhiên của các khóa (cách danh sách trong các ngôn ngữ khác thường hoạt động) hoặc không có thứ tự xác định nào cả (từ điển trong các ngôn ngữ khác thường hoạt động như thế nào).
Điều tương tự cũng áp dụng cho các đối tượng, vì các thuộc tính đối tượng có thể được xem như là một tên thuộc tính ánh xạ từ điển (có thứ tự) khác với các giá trị của chúng, cộng với một số xử lý khả năng hiển thị. Trong phần lớn các trường hợp, các thuộc tính đối tượng không thực sự được lưu trữ theo cách khá kém hiệu quả này. Tuy nhiên, nếu bạn bắt đầu lặp lại một đối tượng, biểu diễn được đóng gói thường được sử dụng sẽ được chuyển đổi thành một từ điển thực sự. Tại thời điểm đó, việc lặp lại các đối tượng đơn giản trở nên rất giống với việc lặp lại các mảng (đó là lý do tại sao tôi không thảo luận nhiều về việc lặp lại đối tượng đơn giản ở đây).
Càng xa càng tốt. Lặp lại từ điển không thể quá khó, phải không? Các vấn đề bắt đầu khi bạn nhận ra rằng một mảng / đối tượng có thể thay đổi trong quá trình lặp. Có nhiều cách điều này có thể xảy ra:
- Nếu bạn lặp theo tham chiếu bằng cách sử dụng
foreach ($arr as &$v)
thì $arr
được chuyển thành tham chiếu và bạn có thể thay đổi nó trong quá trình lặp.
- Trong PHP 5, điều tương tự cũng được áp dụng ngay cả khi bạn lặp theo giá trị, nhưng mảng là tham chiếu trước:
$ref =& $arr; foreach ($ref as $v)
- Các đối tượng có xử lý thông qua ngữ nghĩa, đối với hầu hết các mục đích thực tế có nghĩa là chúng hành xử giống như các tài liệu tham khảo. Vì vậy, các đối tượng luôn có thể được thay đổi trong quá trình lặp.
Vấn đề với việc cho phép sửa đổi trong quá trình lặp là trường hợp phần tử bạn hiện đang bị loại bỏ. Giả sử bạn sử dụng một con trỏ để theo dõi phần tử mảng nào bạn đang ở. Nếu phần tử này được giải phóng, bạn sẽ bị bỏ lại một con trỏ lơ lửng (thường dẫn đến một segfault).
Có nhiều cách khác nhau để giải quyết vấn đề này. PHP 5 và PHP 7 khác nhau đáng kể về vấn đề này và tôi sẽ mô tả cả hai hành vi sau đây. Tóm tắt là cách tiếp cận của PHP 5 khá ngu ngốc và dẫn đến tất cả các loại vấn đề cạnh kỳ lạ, trong khi cách tiếp cận liên quan hơn của PHP 7 dẫn đến hành vi nhất quán và dễ đoán hơn.
Như một sơ bộ cuối cùng, cần lưu ý rằng PHP sử dụng tính năng tham chiếu và sao chép khi ghi để quản lý bộ nhớ. Điều này có nghĩa là nếu bạn "sao chép" một giá trị, bạn thực sự chỉ cần sử dụng lại giá trị cũ và tăng số tham chiếu của nó (đếm lại). Chỉ khi bạn thực hiện một số loại sửa đổi, một bản sao thực sự (được gọi là "sao chép") sẽ được thực hiện. Xem Bạn đang bị lừa dối để giới thiệu rộng rãi hơn về chủ đề này.
PHP 5
Con trỏ mảng nội bộ và HashPulum
Mảng trong PHP 5 có một "con trỏ mảng bên trong" (IAP) chuyên dụng, hỗ trợ chính xác các sửa đổi: Bất cứ khi nào một phần tử được loại bỏ, sẽ có một kiểm tra xem IAP có trỏ đến phần tử này không. Nếu có, nó được chuyển sang phần tử tiếp theo thay thế.
Mặc dù foreach
sử dụng IAP, có một biến chứng bổ sung: Chỉ có một IAP, nhưng một mảng có thể là một phần của nhiều foreach
vòng lặp:
// Using by-ref iteration here to make sure that it's really
// the same array in both loops and not a copy
foreach ($arr as &$v1) {
foreach ($arr as &$v) {
// ...
}
}
Để hỗ trợ hai vòng lặp đồng thời chỉ với một con trỏ mảng bên trong, hãy foreach
thực hiện các shenanigans sau: Trước khi thân vòng lặp được thực thi, foreach
sẽ sao lưu một con trỏ tới phần tử hiện tại và hàm băm của nó thành một pereach HashPointer
. Sau khi thân vòng lặp chạy, IAP sẽ được đặt lại thành phần tử này nếu nó vẫn tồn tại. Tuy nhiên, nếu phần tử đã bị xóa, chúng tôi sẽ sử dụng bất cứ nơi nào IAP hiện đang ở. Sơ đồ này hầu hết là các loại công việc, nhưng có rất nhiều hành vi kỳ lạ mà bạn có thể thoát khỏi nó, một số trong đó tôi sẽ trình bày dưới đây.
Sao chép mảng
IAP là một tính năng có thể nhìn thấy của một mảng (được thể hiện thông qua current
họ các hàm), do đó các thay đổi đối với số IAP là các sửa đổi theo ngữ nghĩa sao chép khi ghi. Thật không may, điều này có nghĩa foreach
là trong nhiều trường hợp buộc phải sao chép mảng mà nó đang lặp lại. Các điều kiện chính xác là:
- Mảng không phải là một tham chiếu (is_Vf = 0). Nếu đó là một tài liệu tham khảo, thì những thay đổi đối với nó được cho là truyền bá, vì vậy nó không nên bị trùng lặp.
- Mảng có số lượng> 1. Nếu
refcount
là 1, thì mảng không được chia sẻ và chúng tôi có thể tự do sửa đổi nó trực tiếp.
Nếu mảng không được nhân đôi (is_Vf = 0, refcount = 1), thì chỉ mảng của nó refcount
sẽ được tăng (*). Ngoài ra, nếu foreach
tham chiếu được sử dụng, thì mảng (có khả năng trùng lặp) sẽ được chuyển thành tham chiếu.
Hãy xem mã này như một ví dụ khi xảy ra sự trùng lặp:
function iterate($arr) {
foreach ($arr as $v) {}
}
$outerArr = [0, 1, 2, 3, 4];
iterate($outerArr);
Tại đây, $arr
sẽ được nhân đôi để ngăn chặn các thay đổi IAP trên $arr
bị rò rỉ sang $outerArr
. Xét về các điều kiện trên, mảng không phải là tham chiếu (is numf = 0) và được sử dụng ở hai nơi (refcount = 2). Yêu cầu này là không may và là một yếu tố của việc triển khai dưới mức tối ưu (không có vấn đề sửa đổi trong quá trình lặp ở đây, vì vậy chúng tôi không thực sự cần sử dụng IAP ở nơi đầu tiên).
(*) Tăng thêm refcount
ở đây nghe có vẻ vô hại, nhưng vi phạm ngữ nghĩa sao chép trên ghi (COW): Điều này có nghĩa là chúng tôi sẽ sửa đổi IAP của một refcount = 2, trong khi COW chỉ ra rằng sửa đổi chỉ có thể được thực hiện trên refcount = 1 giá trị. Vi phạm này dẫn đến thay đổi hành vi người dùng có thể nhìn thấy (trong khi COW thường trong suốt) vì thay đổi IAP trên mảng lặp sẽ có thể quan sát được - nhưng chỉ cho đến khi sửa đổi không phải IAP đầu tiên trên mảng. Thay vào đó, ba tùy chọn "hợp lệ" sẽ là a) luôn luôn trùng lặp, b) không tăng refcount
và do đó cho phép mảng lặp được sửa đổi tùy ý trong vòng lặp hoặc c) hoàn toàn không sử dụng IAP (PHP 7 giải pháp).
Thứ tự thăng tiến
Có một chi tiết triển khai cuối cùng mà bạn phải biết để hiểu đúng các mẫu mã dưới đây. Cách lặp "thông thường" thông qua một số cấu trúc dữ liệu sẽ trông giống như thế này trong mã giả:
reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
code();
move_forward(arr);
}
Tuy nhiên foreach
, là một bông tuyết khá đặc biệt, chọn làm mọi thứ hơi khác một chút:
reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
move_forward(arr);
code();
}
Cụ thể, con trỏ mảng đã được di chuyển về phía trước trước khi thân vòng lặp chạy. Điều này có nghĩa là trong khi thân vòng lặp đang hoạt động trên phần tử $i
, IAP đã có sẵn ở phần tử $i+1
. Đây là lý do tại sao các mẫu mã hiển thị sửa đổi trong quá trình lặp sẽ luôn unset
là phần tử tiếp theo , thay vì phần tử hiện tại.
Ví dụ: Các trường hợp thử nghiệm của bạn
Ba khía cạnh được mô tả ở trên sẽ cung cấp cho bạn một ấn tượng gần như hoàn chỉnh về các đặc điểm riêng của việc foreach
triển khai và chúng ta có thể chuyển sang thảo luận về một số ví dụ.
Hành vi của các trường hợp thử nghiệm của bạn rất đơn giản để giải thích tại thời điểm này:
Trong trường hợp thử nghiệm 1 và 2 $array
bắt đầu với refcount = 1, do đó, nó sẽ không bị trùng lặp bởi foreach
: Chỉ refcount
tăng được. Khi thân vòng lặp sau đó sửa đổi mảng (có refcount = 2 tại điểm đó), sự trùng lặp sẽ xảy ra tại điểm đó. Foreach sẽ tiếp tục làm việc trên một bản sao chưa sửa đổi $array
.
Trong trường hợp thử nghiệm 3, một lần nữa mảng không bị trùng lặp, do đó foreach
sẽ sửa đổi IAP của $array
biến. Khi kết thúc việc lặp lại, IAP là NULL (có nghĩa là việc lặp lại đã hoàn thành), each
biểu thị bằng cách quay lại false
.
Trong các trường hợp thử nghiệm 4 và 5 cả hai each
và reset
là các hàm tham chiếu. Có $array
một refcount=2
khi nó được truyền cho họ, vì vậy nó phải được nhân đôi. Như vậy foreach
sẽ làm việc trên một mảng riêng biệt một lần nữa.
Ví dụ: Tác dụng của current
foreach
Một cách tốt để thể hiện các hành vi trùng lặp khác nhau là quan sát hành vi của current()
hàm bên trong một foreach
vòng lặp. Xem xét ví dụ này:
foreach ($array as $val) {
var_dump(current($array));
}
/* Output: 2 2 2 2 2 */
Ở đây bạn nên biết rằng đó current()
là một hàm by-ref (thực ra là: prefer-ref), mặc dù nó không sửa đổi mảng. Nó phải là để chơi tốt với tất cả các chức năng khác giống như next
tất cả các phụ. Truyền qua tham chiếu ngụ ý rằng mảng phải được tách ra và do đó $array
và foreach-array
sẽ khác nhau. Lý do bạn nhận được 2
thay vì 1
cũng được đề cập ở trên: foreach
tiến bộ con trỏ mảng trước khi chạy mã người dùng, không phải sau. Vì vậy, mặc dù mã nằm ở phần tử đầu tiên, nhưng foreach
đã nâng con trỏ lên phần tử thứ hai.
Bây giờ hãy thử một sửa đổi nhỏ:
$ref = &$array;
foreach ($array as $val) {
var_dump(current($array));
}
/* Output: 2 3 4 5 false */
Ở đây chúng ta có trường hợp is_Vf = 1, vì vậy mảng không được sao chép (giống như trên). Nhưng bây giờ nó là một tham chiếu, mảng không còn phải được sao chép khi chuyển đến hàm by-ref current()
. Do đó current()
và foreach
làm việc trên cùng một mảng. Tuy nhiên, bạn vẫn thấy hành vi tắt, do cách foreach
tiến con trỏ.
Bạn nhận được hành vi tương tự khi thực hiện lặp đi lặp lại:
foreach ($array as &$val) {
var_dump(current($array));
}
/* Output: 2 3 4 5 false */
Ở đây, phần quan trọng là foreach sẽ tạo $array
một is numf = 1 khi nó được lặp lại bởi tham chiếu, vì vậy về cơ bản bạn có tình huống tương tự như trên.
Một biến thể nhỏ khác, lần này chúng ta sẽ gán mảng cho một biến khác:
$foo = $array;
foreach ($array as $val) {
var_dump(current($array));
}
/* Output: 1 1 1 1 1 */
Ở đây số lần đếm $array
là 2 khi vòng lặp được bắt đầu, vì vậy lần đầu tiên chúng ta thực sự phải thực hiện sao chép trước. Do đó $array
và mảng được sử dụng bởi foreach sẽ hoàn toàn tách biệt với phần đầu. Đó là lý do tại sao bạn có được vị trí của IAP ở bất cứ đâu trước vòng lặp (trong trường hợp này là ở vị trí đầu tiên).
Ví dụ: Sửa đổi trong quá trình lặp
Cố gắng tính đến các sửa đổi trong quá trình lặp là nơi tất cả các rắc rối foreach của chúng tôi bắt nguồn, vì vậy nó phục vụ để xem xét một số ví dụ cho trường hợp này.
Hãy xem xét các vòng lặp lồng nhau này trên cùng một mảng (trong đó phép lặp by-ref được sử dụng để đảm bảo rằng nó thực sự giống nhau):
foreach ($array as &$v1) {
foreach ($array as &$v2) {
if ($v1 == 1 && $v2 == 1) {
unset($array[1]);
}
echo "($v1, $v2)\n";
}
}
// Output: (1, 1) (1, 3) (1, 4) (1, 5)
Phần dự kiến ở đây là (1, 2)
thiếu từ đầu ra vì phần tử 1
đã bị xóa. Điều có lẽ bất ngờ là vòng lặp bên ngoài dừng lại sau phần tử đầu tiên. Tại sao vậy?
Lý do đằng sau điều này là hack vòng lặp lồng nhau được mô tả ở trên: Trước khi thân vòng lặp chạy, vị trí và hàm băm IAP hiện tại được sao lưu vào a HashPointer
. Sau phần thân vòng lặp, nó sẽ được khôi phục, nhưng chỉ khi phần tử vẫn tồn tại, nếu không thì vị trí IAP hiện tại (bất kể nó có thể là gì) được sử dụng thay thế. Trong ví dụ trên đây chính xác là trường hợp: Phần tử hiện tại của vòng lặp bên ngoài đã bị xóa, vì vậy nó sẽ sử dụng IAP, đã được đánh dấu là đã hoàn thành bởi vòng lặp bên trong!
Một hậu quả khác của HashPointer
cơ chế sao lưu + khôi phục là những thay đổi đối với IAP thông qua reset()
vv thường không ảnh hưởng foreach
. Ví dụ, đoạn mã sau thực thi như reset()
thể không có mặt ở tất cả:
$array = [1, 2, 3, 4, 5];
foreach ($array as &$value) {
var_dump($value);
reset($array);
}
// output: 1, 2, 3, 4, 5
Lý do là, trong khi reset()
tạm thời sửa đổi IAP, nó sẽ được khôi phục thành phần tử foreach hiện tại sau thân vòng lặp. Để buộc reset()
tạo hiệu ứng trên vòng lặp, bạn phải loại bỏ thêm phần tử hiện tại để cơ chế sao lưu / khôi phục không thành công:
$array = [1, 2, 3, 4, 5];
$ref =& $array;
foreach ($array as $value) {
var_dump($value);
unset($array[1]);
reset($array);
}
// output: 1, 1, 3, 4, 5
Nhưng, những ví dụ đó vẫn còn lành mạnh. Thú vui thực sự bắt đầu nếu bạn nhớ rằng HashPointer
khôi phục sử dụng một con trỏ đến phần tử và hàm băm của nó để xác định xem nó có còn tồn tại hay không. Nhưng: Băm có va chạm, và con trỏ có thể được sử dụng lại! Điều này có nghĩa là, với sự lựa chọn cẩn thận các phím mảng, chúng ta có thể foreach
tin rằng một phần tử đã bị xóa vẫn tồn tại, vì vậy nó sẽ nhảy trực tiếp vào nó. Một ví dụ:
$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
$ref =& $array;
foreach ($array as $value) {
unset($array['EzFY']);
$array['FYFY'] = 4;
reset($array);
var_dump($value);
}
// output: 1, 4
Ở đây chúng ta thường mong đợi đầu ra 1, 1, 3, 4
theo các quy tắc trước đó. Làm thế nào những gì xảy ra là 'FYFY'
có cùng hàm băm với phần tử bị loại bỏ 'EzFY'
và bộ cấp phát xảy ra để sử dụng lại cùng một vị trí bộ nhớ để lưu trữ phần tử. Vì vậy, foreach kết thúc trực tiếp nhảy đến phần tử mới được chèn, do đó cắt ngắn vòng lặp.
Thay thế thực thể lặp trong vòng lặp
Một trường hợp kỳ lạ cuối cùng mà tôi muốn đề cập, đó là PHP cho phép bạn thay thế thực thể lặp trong vòng lặp. Vì vậy, bạn có thể bắt đầu lặp lại trên một mảng và sau đó thay thế nó bằng một mảng khác giữa chừng. Hoặc bắt đầu lặp lại trên một mảng và sau đó thay thế nó bằng một đối tượng:
$arr = [1, 2, 3, 4, 5];
$obj = (object) [6, 7, 8, 9, 10];
$ref =& $arr;
foreach ($ref as $val) {
echo "$val\n";
if ($val == 3) {
$ref = $obj;
}
}
/* Output: 1 2 3 6 7 8 9 10 */
Như bạn có thể thấy trong trường hợp này, PHP sẽ chỉ bắt đầu lặp lại thực thể khác từ đầu một khi sự thay thế đã xảy ra.
PHP 7
Trình lặp Hashtable
Nếu bạn vẫn còn nhớ, vấn đề chính của việc lặp mảng là làm thế nào để xử lý loại bỏ các phần tử giữa các lần lặp. PHP 5 đã sử dụng một con trỏ mảng bên trong (IAP) cho mục đích này, điều này hơi tối ưu, vì một con trỏ mảng phải được kéo dài để hỗ trợ nhiều vòng lặp foreach đồng thời và tương tác với reset()
v.v.
PHP 7 sử dụng một cách tiếp cận khác, cụ thể là, nó hỗ trợ tạo ra một số lượng các trình lặp có thể băm bên ngoài, an toàn tùy ý. Các trình lặp này phải được đăng ký trong mảng, từ đó chúng có cùng ngữ nghĩa với IAP: Nếu một phần tử mảng bị loại bỏ, tất cả các trình lặp có thể băm chỉ vào phần tử đó sẽ được chuyển sang phần tử tiếp theo.
Điều này có nghĩa rằng foreach
sẽ không còn sử dụng IAP ở tất cả . Các foreach
vòng lặp sẽ được hoàn toàn không ảnh hưởng đến kết quả current()
vv và hành vi của mình sẽ không bao giờ bị ảnh hưởng bởi các chức năng như reset()
, vv
Sao chép mảng
Một thay đổi quan trọng khác giữa PHP 5 và PHP 7 liên quan đến sao chép mảng. Bây giờ IAP không còn được sử dụng nữa, việc lặp mảng theo giá trị sẽ chỉ thực hiện một refcount
bước tăng (thay vì sao chép mảng) trong mọi trường hợp. Nếu mảng được sửa đổi trong foreach
vòng lặp, tại thời điểm đó sẽ xảy ra sự trùng lặp (theo bản sao khi ghi) và foreach
sẽ tiếp tục hoạt động trên mảng cũ.
Trong hầu hết các trường hợp, thay đổi này là minh bạch và không có tác dụng nào khác ngoài hiệu suất tốt hơn. Tuy nhiên, có một trường hợp dẫn đến hành vi khác nhau, cụ thể là trường hợp mảng là tham chiếu trước:
$array = [1, 2, 3, 4, 5];
$ref = &$array;
foreach ($array as $val) {
var_dump($val);
$array[2] = 0;
}
/* Old output: 1, 2, 0, 4, 5 */
/* New output: 1, 2, 3, 4, 5 */
Lặp lại giá trị trước của mảng tham chiếu là trường hợp đặc biệt. Trong trường hợp này, không có sự trùng lặp xảy ra, vì vậy tất cả các sửa đổi của mảng trong quá trình lặp sẽ được phản ánh bởi vòng lặp. Trong PHP 7, trường hợp đặc biệt này không còn nữa: Lặp lại theo giá trị của một mảng sẽ luôn tiếp tục làm việc trên các phần tử gốc, bỏ qua mọi sửa đổi trong vòng lặp.
Tất nhiên, điều này không áp dụng cho phép lặp theo tham chiếu. Nếu bạn lặp lại theo tham chiếu, tất cả các sửa đổi sẽ được phản ánh bởi vòng lặp. Thật thú vị, điều tương tự cũng đúng với phép lặp theo giá trị của các đối tượng đơn giản:
$obj = new stdClass;
$obj->foo = 1;
$obj->bar = 2;
foreach ($obj as $val) {
var_dump($val);
$obj->bar = 42;
}
/* Old and new output: 1, 42 */
Điều này phản ánh ngữ nghĩa của các đối tượng (nghĩa là chúng hành xử giống như tham chiếu ngay cả trong bối cảnh giá trị).
Ví dụ
Hãy xem xét một vài ví dụ, bắt đầu với các trường hợp thử nghiệm của bạn:
Các trường hợp kiểm tra 1 và 2 giữ nguyên đầu ra: Lặp lại mảng giá trị luôn luôn làm việc trên các phần tử gốc. (Trong trường hợp này, refcounting
hành vi chẵn và trùng lặp hoàn toàn giống nhau giữa PHP 5 và PHP 7).
Trường hợp thử nghiệm 3 thay đổi: Foreach
không còn sử dụng IAP, do đó each()
không bị ảnh hưởng bởi vòng lặp. Nó sẽ có cùng một đầu ra trước và sau.
Các trường hợp thử nghiệm 4 và 5 giữ nguyên: each()
và reset()
sẽ nhân đôi mảng trước khi thay đổi IAP, trong khi foreach
vẫn sử dụng mảng ban đầu. (Không phải thay đổi IAP sẽ có vấn đề, ngay cả khi mảng được chia sẻ.)
Nhóm ví dụ thứ hai có liên quan đến hành vi của current()
các reference/refcounting
cấu hình khác nhau . Điều này không còn có ý nghĩa, vì current()
hoàn toàn không bị ảnh hưởng bởi vòng lặp, vì vậy giá trị trả về của nó luôn giữ nguyên.
Tuy nhiên, chúng tôi nhận được một số thay đổi thú vị khi xem xét sửa đổi trong quá trình lặp. Tôi hy vọng bạn sẽ tìm thấy saner hành vi mới. Ví dụ đầu tiên:
$array = [1, 2, 3, 4, 5];
foreach ($array as &$v1) {
foreach ($array as &$v2) {
if ($v1 == 1 && $v2 == 1) {
unset($array[1]);
}
echo "($v1, $v2)\n";
}
}
// Old output: (1, 1) (1, 3) (1, 4) (1, 5)
// New output: (1, 1) (1, 3) (1, 4) (1, 5)
// (3, 1) (3, 3) (3, 4) (3, 5)
// (4, 1) (4, 3) (4, 4) (4, 5)
// (5, 1) (5, 3) (5, 4) (5, 5)
Như bạn có thể thấy, vòng lặp bên ngoài không còn hủy bỏ sau lần lặp đầu tiên. Lý do là cả hai vòng lặp đều có các vòng lặp băm hoàn toàn riêng biệt và không còn bất kỳ sự lây nhiễm chéo nào của cả hai vòng thông qua IAP được chia sẻ.
Một trường hợp cạnh kỳ lạ khác đã được sửa bây giờ, là hiệu ứng kỳ lạ bạn nhận được khi xóa và thêm các phần tử có cùng hàm băm:
$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
foreach ($array as &$value) {
unset($array['EzFY']);
$array['FYFY'] = 4;
var_dump($value);
}
// Old output: 1, 4
// New output: 1, 3, 4
Trước đây, cơ chế khôi phục HashPulum đã nhảy ngay sang phần tử mới vì nó "trông" giống như phần tử bị loại bỏ (do va chạm băm và con trỏ). Vì chúng tôi không còn dựa vào hàm băm cho bất cứ điều gì, điều này không còn là vấn đề nữa.