仅使用纯函数反转对象
Invert object using only pure functions
假设您有这样一个对象:
{
a: [1,2,3],
b: [2],
c: [1,4]
}
您需要将其转换为:
{
1: ['a', 'c'],
2: ['a', 'b'],
3: ['a'],
4: ['c']
}
使用命令式方式和可变数据结构很简单:
function convert (obj) {
var key, values
, result = {};
for (key in obj) {
values = obj[key];
values.forEach(function (value) {
result[value] = result[value] || []; // bad — mutability :(
result[value].push(key); // bad — mutability :(
});
}
return result;
}
但是有没有办法只使用纯函数而不使用任何赋值来做到这一点?
允许使用某些库进行函数式编程。
好吧,它是一个纯数据转换,所以你绝对可以在不使用可变变量的情况下完成它。让我们在 Haskell 中进行操作,这为我们提供了没有显式突变的静态保证。我们从一个例子开始:
d :: [(Char, [Integer])]
d = [('a', [1,2,3]), ('b', [2]), ('c', [1,4])]
好的,那么新结构的键是什么?它们是第一个结构的独特元素:
> import Data.List
> let keys = sort . nub . concatMap snd $ d
[1,2,3,4]
然后我们需要知道每个值出现在哪一列。所以将每个值与其键配对:
> let pairs = concatMap (\(k,v) -> map (,k) v) d
[(1,'a'),(2,'a'),(3,'a'),(2,'b'),(1,'c'),(4,'c')]
现在我们可以构建结果了:
> [ (k, map snd . filter ((== k) . fst) $ pairs ) | k <- keys ]
[(1,"ac"),(2,"ab"),(3,"a"),(4,"c")]
全盛时期:
{-# LANGUAGE TupleSections #-}
import Data.List
rotate d = [ (k, map snd . filter ((== k) . fst) $ pairs ) | k <- keys ]
where
keys = sort . nub . concatMap snd $ d
pairs = concatMap (\(k,v) -> map (,k) v) d
例如
*A> rotate [('a', [1,2,3]), ('b', [2]), ('c', [1,4])]
[(1,"ac"),(2,"ab"),(3,"a"),(4,"c")]
或者没有命名中间结构的无点版本:
rotate2 = map (\x -> (head (map fst x), map snd x))
. groupBy ((==) `on` fst)
. sortBy (comparing fst)
. concatMap (\(k,v) -> map (,k) v)
不错的小面试题:)
找到了使用 Ramda 库执行此操作的方法:
compose(
reduce(function(acc, key) {
return assoc(key, filter(compose(contains(key), propOf(src)), keys(src)), acc);
}, {}),
uniq,
flatten,
values
)(src)
给定像 Haskell 的 Data.Map
这样的关联数据结构,我将通过为每一对 (v, ks) :: (String, [Int])
创建几个仅包含 k |-> [v]
的映射来编写此代码(即每个键都映射到一个包含唯一值的单例列表)然后合并它们。
与 Haskell 语法保持一致,这会让你得到类似
的东西
import qualified Data.Map as Map
insideOut :: (Ord k) => [(v, [k])] -> [(k, [v])]
insideOut vks = Map.toList . Map.unionsWith (++) $
[ Map.singleton k [v] | (v, ks) <- vks, k <- ks]
假设您有这样一个对象:
{
a: [1,2,3],
b: [2],
c: [1,4]
}
您需要将其转换为:
{
1: ['a', 'c'],
2: ['a', 'b'],
3: ['a'],
4: ['c']
}
使用命令式方式和可变数据结构很简单:
function convert (obj) {
var key, values
, result = {};
for (key in obj) {
values = obj[key];
values.forEach(function (value) {
result[value] = result[value] || []; // bad — mutability :(
result[value].push(key); // bad — mutability :(
});
}
return result;
}
但是有没有办法只使用纯函数而不使用任何赋值来做到这一点? 允许使用某些库进行函数式编程。
好吧,它是一个纯数据转换,所以你绝对可以在不使用可变变量的情况下完成它。让我们在 Haskell 中进行操作,这为我们提供了没有显式突变的静态保证。我们从一个例子开始:
d :: [(Char, [Integer])]
d = [('a', [1,2,3]), ('b', [2]), ('c', [1,4])]
好的,那么新结构的键是什么?它们是第一个结构的独特元素:
> import Data.List
> let keys = sort . nub . concatMap snd $ d
[1,2,3,4]
然后我们需要知道每个值出现在哪一列。所以将每个值与其键配对:
> let pairs = concatMap (\(k,v) -> map (,k) v) d
[(1,'a'),(2,'a'),(3,'a'),(2,'b'),(1,'c'),(4,'c')]
现在我们可以构建结果了:
> [ (k, map snd . filter ((== k) . fst) $ pairs ) | k <- keys ]
[(1,"ac"),(2,"ab"),(3,"a"),(4,"c")]
全盛时期:
{-# LANGUAGE TupleSections #-}
import Data.List
rotate d = [ (k, map snd . filter ((== k) . fst) $ pairs ) | k <- keys ]
where
keys = sort . nub . concatMap snd $ d
pairs = concatMap (\(k,v) -> map (,k) v) d
例如
*A> rotate [('a', [1,2,3]), ('b', [2]), ('c', [1,4])]
[(1,"ac"),(2,"ab"),(3,"a"),(4,"c")]
或者没有命名中间结构的无点版本:
rotate2 = map (\x -> (head (map fst x), map snd x))
. groupBy ((==) `on` fst)
. sortBy (comparing fst)
. concatMap (\(k,v) -> map (,k) v)
不错的小面试题:)
找到了使用 Ramda 库执行此操作的方法:
compose(
reduce(function(acc, key) {
return assoc(key, filter(compose(contains(key), propOf(src)), keys(src)), acc);
}, {}),
uniq,
flatten,
values
)(src)
给定像 Haskell 的 Data.Map
这样的关联数据结构,我将通过为每一对 (v, ks) :: (String, [Int])
创建几个仅包含 k |-> [v]
的映射来编写此代码(即每个键都映射到一个包含唯一值的单例列表)然后合并它们。
与 Haskell 语法保持一致,这会让你得到类似
的东西import qualified Data.Map as Map
insideOut :: (Ord k) => [(v, [k])] -> [(k, [v])]
insideOut vks = Map.toList . Map.unionsWith (++) $
[ Map.singleton k [v] | (v, ks) <- vks, k <- ks]