Các câu trả lời khác thể hiện sự khác biệt giữa mảng_walk (sửa đổi tại chỗ) và mảng_map (trả lại bản sao đã sửa đổi) khá tốt. Tuy nhiên, họ không thực sự đề cập đến mảng_reduce, đây là một cách chiếu sáng để hiểu mảng_map và mảng_filter.
Hàm Array_reduce có một mảng, hàm hai đối số và 'bộ tích lũy', như thế này:
array_reduce(array('a', 'b', 'c', 'd'),
'my_function',
$accumulator)
Các phần tử của mảng được kết hợp với bộ tích lũy cùng một lúc, sử dụng hàm đã cho. Kết quả của cuộc gọi trên giống như thực hiện điều này:
my_function(
my_function(
my_function(
my_function(
$accumulator,
'a'),
'b'),
'c'),
'd')
Nếu bạn thích suy nghĩ về các vòng lặp, thì nó giống như làm như sau (Tôi thực sự đã sử dụng điều này như một dự phòng khi mảng_reduce không có sẵn):
function array_reduce($array, $function, $accumulator) {
foreach ($array as $element) {
$accumulator = $function($accumulator, $element);
}
return $accumulator;
}
Phiên bản lặp này cho thấy rõ lý do tại sao tôi gọi đối số thứ ba là 'bộ tích lũy': chúng ta có thể sử dụng nó để tích lũy kết quả qua mỗi lần lặp.
Vậy điều này có liên quan gì với Array_map và Array_filter? Hóa ra cả hai đều là một loại mảng_reduce đặc biệt. Chúng ta có thể thực hiện chúng như thế này:
array_map($function, $array) === array_reduce($array, $MAP, array())
array_filter($array, $function) === array_reduce($array, $FILTER, array())
Bỏ qua thực tế là Array_map và Array_filter đưa các đối số của chúng theo một thứ tự khác nhau; đó chỉ là một cách giải quyết khác của PHP. Điểm quan trọng là phía bên tay phải giống hệt nhau ngoại trừ các chức năng mà tôi đã gọi là $ MAP và $ FILTER. Vậy, chúng trông như thế nào?
$MAP = function($accumulator, $element) {
$accumulator[] = $function($element);
return $accumulator;
};
$FILTER = function($accumulator, $element) {
if ($function($element)) $accumulator[] = $element;
return $accumulator;
};
Như bạn có thể thấy, cả hai hàm đều lấy $ tích lũy và trả lại nó. Có hai sự khác biệt trong các chức năng này:
- $ MAP sẽ luôn nối với $ ắc quy, nhưng $ LỌC sẽ chỉ làm như vậy nếu hàm $ (phần tử $) là TRUE.
- $ FILTER nối thêm phần tử gốc, nhưng $ MAP nối thêm hàm $ (phần tử $).
Lưu ý rằng điều này là xa những chuyện vặt vãnh vô dụng; chúng ta có thể sử dụng nó để làm cho thuật toán của chúng ta hiệu quả hơn!
Chúng ta thường có thể thấy mã như hai ví dụ sau:
// Transform the valid inputs
array_map('transform', array_filter($inputs, 'valid'))
// Get all numeric IDs
array_filter(array_map('get_id', $inputs), 'is_numeric')
Việc sử dụng Array_map và Array_filter thay vì các vòng lặp làm cho các ví dụ này trông khá đẹp. Tuy nhiên, nó có thể rất không hiệu quả nếu đầu vào $ lớn, vì cuộc gọi đầu tiên (bản đồ hoặc bộ lọc) sẽ đi qua đầu vào $ và xây dựng một mảng trung gian. Mảng trung gian này được chuyển thẳng vào cuộc gọi thứ hai, nó sẽ đi qua toàn bộ một lần nữa, sau đó mảng trung gian sẽ cần được thu gom rác.
Chúng ta có thể thoát khỏi mảng trung gian này bằng cách khai thác thực tế là Array_map và Array_filter đều là ví dụ của mảng_reduce. Bằng cách kết hợp chúng, chúng ta chỉ phải duyệt qua các đầu vào $ một lần trong mỗi ví dụ:
// Transform valid inputs
array_reduce($inputs,
function($accumulator, $element) {
if (valid($element)) $accumulator[] = transform($element);
return $accumulator;
},
array())
// Get all numeric IDs
array_reduce($inputs,
function($accumulator, $element) {
$id = get_id($element);
if (is_numeric($id)) $accumulator[] = $id;
return $accumulator;
},
array())
LƯU Ý: Việc triển khai mảng_map và mảng_filter ở trên của tôi sẽ không hoạt động chính xác như của PHP, vì mảng_map của tôi chỉ có thể xử lý một mảng tại một thời điểm và mảng_filter của tôi sẽ không sử dụng "rỗng" làm hàm $ mặc định của nó. Ngoài ra, sẽ không bảo quản chìa khóa.
Không khó để khiến chúng hoạt động như của PHP, nhưng tôi cảm thấy rằng những sự phức tạp này sẽ khiến ý tưởng cốt lõi trở nên khó phát hiện hơn.