Các thủ tục chung có nghĩa là chúng ta không phải viết lại độ phức tạp mỗi lần chúng ta cần sử dụng một hành vi cụ thể.
concatMap
(hoặc flatMap
) chính xác là những gì chúng ta cần trong tình huống này.
// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
xs.concat (ys)
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
xs.map(f).reduce(concat, [])
// id :: a -> a
const id = x =>
x
// flatten :: [[a]] -> [a]
const flatten =
concatMap (id)
// your sample data
const data =
[["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]
console.log (flatten (data))
tầm nhìn xa
Và vâng, bạn đã đoán đúng, nó chỉ làm phẳng một cấp độ, đó chính xác là cách nó nên làm việc
Hãy tưởng tượng một số dữ liệu được đặt như thế này
// Player :: (String, Number) -> Player
const Player = (name,number) =>
[ name, number ]
// team :: ( . Player) -> Team
const Team = (...players) =>
players
// Game :: (Team, Team) -> Game
const Game = (teamA, teamB) =>
[ teamA, teamB ]
// sample data
const teamA =
Team (Player ('bob', 5), Player ('alice', 6))
const teamB =
Team (Player ('ricky', 4), Player ('julian', 2))
const game =
Game (teamA, teamB)
console.log (game)
// [ [ [ 'bob', 5 ], [ 'alice', 6 ] ],
// [ [ 'ricky', 4 ], [ 'julian', 2 ] ] ]
Ok, bây giờ nói rằng chúng ta muốn in một danh sách mà chương trình tất cả các cầu thủ sẽ được tham gia vào game
...
const gamePlayers = game =>
flatten (game)
gamePlayers (game)
// => [ [ 'bob', 5 ], [ 'alice', 6 ], [ 'ricky', 4 ], [ 'julian', 2 ] ]
Nếu flatten
quy trình của chúng tôi cũng làm phẳng các mảng lồng nhau, chúng tôi sẽ kết thúc với kết quả rác này
const gamePlayers = game =>
badGenericFlatten(game)
gamePlayers (game)
// => [ 'bob', 5, 'alice', 6, 'ricky', 4, 'julian', 2 ]
sâu, em bé
Điều đó không có nghĩa là đôi khi bạn cũng không muốn làm phẳng các mảng lồng nhau - chỉ đó không phải là hành vi mặc định.
Chúng tôi có thể làm một deepFlatten
thủ tục dễ dàng
// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
xs.concat (ys)
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
xs.map(f).reduce(concat, [])
// id :: a -> a
const id = x =>
x
// flatten :: [[a]] -> [a]
const flatten =
concatMap (id)
// deepFlatten :: [[a]] -> [a]
const deepFlatten =
concatMap (x =>
Array.isArray (x) ? deepFlatten (x) : x)
// your sample data
const data =
[0, [1, [2, [3, [4, 5], 6]]], [7, [8]], 9]
console.log (flatten (data))
// [ 0, 1, [ 2, [ 3, [ 4, 5 ], 6 ] ], 7, [ 8 ], 9 ]
console.log (deepFlatten (data))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Đó Bây giờ bạn có một công cụ cho mỗi công việc - một công cụ flatten
để xóa một cấp độ lồng và một công cụ để xóa sổ tất cả các lồngdeepFlatten
.
Có lẽ bạn có thể gọi nó obliterate
hoặc nuke
nếu bạn không thích cái tên đó deepFlatten
.
Đừng lặp lại hai lần!
Tất nhiên các triển khai trên là thông minh và súc tích, nhưng sử dụng .map
theo sau là một cuộc gọi đến.reduce
có nghĩa là chúng ta thực sự đang thực hiện nhiều lần lặp hơn mức cần thiết
Sử dụng một tổ hợp đáng tin cậy mà tôi đang gọi mapReduce
giúp giữ các lần lặp lại ở mức tối thiểu; nó có một chức năng ánh xạ m :: a -> b
, một hàm khử r :: (b,a) ->b
và trả về một hàm khử mới - bộ kết hợp này là trung tâm của các bộ chuyển đổi ; nếu bạn quan tâm, tôi đã viết những câu trả lời khác về chúng
// mapReduce = (a -> b, (b,a) -> b, (b,a) -> b)
const mapReduce = (m,r) =>
(acc,x) => r (acc, m (x))
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
xs.reduce (mapReduce (f, concat), [])
// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
xs.concat (ys)
// id :: a -> a
const id = x =>
x
// flatten :: [[a]] -> [a]
const flatten =
concatMap (id)
// deepFlatten :: [[a]] -> [a]
const deepFlatten =
concatMap (x =>
Array.isArray (x) ? deepFlatten (x) : x)
// your sample data
const data =
[ [ [ 1, 2 ],
[ 3, 4 ] ],
[ [ 5, 6 ],
[ 7, 8 ] ] ]
console.log (flatten (data))
// [ [ 1. 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ] ]
console.log (deepFlatten (data))
// [ 1, 2, 3, 4, 5, 6, 7, 8 ]