Câu trả lời khác giả định rằng hai danh sách đầu vào là hữu hạn. Thông thường, mã Haskell thành ngữ bao gồm danh sách vô hạn, và do đó, nó là đáng giá bình luận một thời gian ngắn về cách sản xuất một sản phẩm Cartesian vô hạn trong trường hợp đó là cần thiết.
Cách tiếp cận tiêu chuẩn là sử dụng chéo; bằng văn bản cho một đầu vào dọc theo phía trên và các đầu vào khác dọc bên trái, chúng ta có thể viết một bảng hai chiều có chứa sản phẩm Descartes đầy đủ như thế này:
1 2 3 4 ...
a a1 a2 a3 a4 ...
b b1 b2 b3 b4 ...
c c1 c2 c3 c4 ...
d d1 d2 d3 d4 ...
. . . . . .
. . . . . .
. . . . . .
Tất nhiên, làm việc trên bất kỳ hàng duy nhất sẽ cho chúng ta các yếu tố vô hạn trước khi nó đạt đến hàng tiếp theo; tương tự như đi cột-khôn ngoan sẽ là thảm họa. Nhưng chúng ta có thể đi dọc theo đường chéo đi xuống và sang trái, bắt đầu một lần nữa ở xa hơn một chút về phía bên phải mỗi khi chúng ta tới được cạnh của lưới điện.
a1
a2
b1
a3
b2
c1
a4
b3
c2
d1
... v.v. Theo thứ tự, điều này sẽ cung cấp cho chúng tôi:
a1 a2 b1 a3 b2 c1 a4 b3 c2 d1 ...
Để mã này trong Haskell, đầu tiên chúng ta có thể viết các phiên bản sản xuất bảng hai chiều:
cartesian2d :: [a] -> [b] -> [[(a, b)]]
cartesian2d as bs = [[(a, b) | a <- as] | b <- bs]
Một phương pháp hiệu quả của diagonalizing là để sau đó lặp lại đầu tiên dọc theo đường chéo và sau đó dọc theo chiều sâu của mỗi đường chéo, kéo ra từng phần tử thích hợp mỗi lần. Để đơn giản giải thích, tôi sẽ giả định rằng cả hai danh sách đầu vào là vô hạn, vì vậy chúng tôi không phải lộn xộn xung quanh với kiểm tra giới hạn.
diagonalBad :: [[a]] -> [a]
diagonalBad xs =
[ xs !! row !! col
| diagonal <- [0..]
, depth <- [0..diagonal]
, let row = depth
col = diagonal - depth
]
thực hiện này là một chút may mắn: lặp đi lặp lại danh sách chỉ mục hoạt động !!
ngày càng trở nên đắt đỏ hơn khi chúng ta đi, đưa ra một hiệu suất khá xấu tiệm cận. Việc triển khai hiệu quả hơn sẽ thực hiện ý tưởng trên nhưng thực hiện nó bằng cách sử dụng khóa kéo. Vì vậy, chúng tôi sẽ chia lưới vô hạn của chúng tôi thành ba hình dạng như sau:
a1 a2/a3 a4 ...
/
/
b1/b2 b3 b4 ...
/
/
/
c1 c2 c3 c4 ...
---------------------------------
d1 d2 d3 d4 ...
. . . . .
. . . . .
. . . . .
Hình tam giác trên cùng bên trái sẽ là các bit chúng tôi đã phát ra; tứ giác trên cùng bên phải sẽ là những hàng đã được phát ra một phần nhưng vẫn sẽ đóng góp vào kết quả; và hình chữ nhật phía dưới sẽ là các hàng mà chúng tôi chưa bắt đầu phát ra. Để bắt đầu, tam giác trên và tứ giác trên sẽ trống, và hình chữ nhật phía dưới sẽ là toàn bộ lưới. Trên mỗi bước, chúng ta có thể phát ra phần tử đầu tiên của mỗi hàng trong tứ giác trên (về cơ bản di chuyển đường nghiêng qua một), sau đó thêm một hàng mới từ hình chữ nhật dưới lên tứ giác trên (về cơ bản di chuyển đường ngang xuống một).
diagonal :: [[a]] -> [a]
diagonal = go [] where
go upper lower = [h | h:_ <- upper] ++ case lower of
[] -> concat (transpose upper')
row:lower' -> go (row:upper') lower'
where upper' = [t | _:t <- upper]
Mặc dù điều này có vẻ phức tạp hơn một chút nhưng hiệu quả hơn đáng kể. Nó cũng xử lý các giới hạn kiểm tra mà chúng tôi punted trên trong phiên bản đơn giản.
Nhưng bạn không nên tự viết tất cả mã này! Thay vào đó, bạn nên sử dụng gói universe. Trong Data.Universe.Helpers
, có (+*+)
, những gói cùng nhau trên cartesian2d
và diagonal
chức năng để cung cấp cho chỉ hoạt động sản phẩm Descartes:
Data.Universe.Helpers> "abcd" +*+ [1..4]
[('a',1),('a',2),('b',1),('a',3),('b',2),('c',1),('a',4),('b',3),('c',2),('d',1),('b',4),('c',3),('d',2),('c',4),('d',3),('d',4)]
Bạn cũng có thể nhìn thấy các đường chéo mình nếu cấu trúc đó trở nên hữu ích:
Data.Universe.Helpers> mapM_ print . diagonals $ cartesian2d "abcd" [1..4]
[('a',1)]
[('a',2),('b',1)]
[('a',3),('b',2),('c',1)]
[('a',4),('b',3),('c',2),('d',1)]
[('b',4),('c',3),('d',2)]
[('c',4),('d',3)]
[('d',4)]
Nếu bạn có nhiều danh sách để cùng nhau sản xuất, việc lặp lại (+*+)
có thể sai lệch danh sách nhất định; bạn có thể sử dụng choices :: [[a]] -> [[a]]
cho nhu cầu sản phẩm nes chiều của bạn.
Cảm ơn, đơn giản nhưng thanh lịch, thực sự đã giúp với vấn đề khác :) –
Tốt cho Erlang, cảm ơn. Ít thay đổi về cú pháp: cartProd (Xs, Ys) -> [{X, Y} || X <- Xs, Y <- Ys]. – Dacav