什么是索引函数的纯函数方法?

时间:2013-12-01 18:46:41

标签: javascript algorithm haskell functional-programming

例如,一个接收交易列表并返回价值总和列表的函数,按时间索引:

trades = [{time:1,value:8}, {time:1.1,value:8},... {time:1.2,value:7}, time:2.1,value:8} ...]
total_value_by_time = {}

for trade in trades
    if not exists(total_value_by_time[trade.time])
        total_value_by_time[trade.time] = 0
    total_value_by_time[trade.time] += trade.value 

我无法弄清楚如何使用常见的FP方法(如map和reduce)复制此算法。什么是纯粹的功能性方法呢?

5 个答案:

答案 0 :(得分:4)

我认为最自然的解决方案是首先将列表分组相等,然后总结每个组的值。在Haskell,

tradesAccum = sortBy (compare`on`time)
          >>> groupBy ((==)`on`time)
          >>> map (map value >>> sum)

如果您尝试此操作但不知道在哪里可以找到必要的标准功能:

import Data.List (sortBy, groupBy)
import Data.Function (on)
import Control.Arrow ((>>>))

我们也可以使用Map使其具有良好的并行性和效率,但仍然只使用列表。这基本上是上述的变体,但完全实现为支持剪枝的并行合并排序:

import Control.Parallel.Strategies

uniqueFstFoldSnd :: (Ord a, Semigroup b) => [(a, b)] -> [(a, b)]
uniqueFstFoldSnd [] = []
uniqueFstFoldSnd [x] = [x]
uniqueFstFoldSnd l = uncurry merge .
    (withStrategy $
        if len>100 then parTuple2 (evalList r0) (evalList r0)
                   else r0
    ) $ uniqueFstFoldSnd *** uniqueFstFoldSnd $ splitAt (len `quot` 2) l
  where merge [] ys = ys
        merge xs [] = xs
        merge ((k, u):xs) ((l, v):ys)
         | k < l      = (k, u   ) : merge        xs  ((l,v):ys)
         | k > l      = (l, v   ) : merge ((k,u):xs)        ys
         | otherwise  = (k, u<>v) : merge        xs         ys
        len = length l

请注意,并行性尚未显着提升性能;我还在尝试Strategies ...

答案 1 :(得分:2)

Data.Map API中有a function for this个曝光。您的示例归结为fromListWith (+)

答案 2 :(得分:1)

这就是我在Haskell中编写代码的方法

import Data.Map as M
import Data.List(foldl')

total :: [(Double Integer)] -> Map (Double, Integer)
total = foldl' step M.empty
  where step m (key, val) | member key m = M.update key (+val) m
                          | otherwise    = M.insert key val m

一般来说,folds是迭代的函数方法,你可以用它们来代替积累东西的循环。在这种特定情况下,您还可以使用group

答案 3 :(得分:1)

您可以将此功能视为“分解”列表,然后根据结果构建备份映射或字典。由于一切都在减少,因此它会产生相对无趣的地图减少问题。

import qualified Data.Map as Map
import           Data.Map (Map)

type Time = Double
type Value = Double
data Trade = Trade { time :: Time, value :: Value }

-- given some `mapReduce` function...
accum = mapReduce mapper reducer where
  mapper :: Trade -> Map Time Value
  mapper tr = Map.singleton (time tr) (value tr)

  -- This inherits the associativity of (+) so you can 
  -- reduce your mapper-generated `Map`s in any order. It's 
  -- not idempotent, though, so you must ensure that each datum
  -- is added to the reduction exactly once. This is typical
  -- for map reduce
  reducer :: [Map Time Value] -> Map Time Value 
  reducer maps = Map.unionsWith (+)

-- without parallelization this looks like you'd expect
--     reducer . map mapper :: [Trade] -> Map Time Value

有趣的Map函数来自Haskell containers包:Map.singletonMap.unionsWith

通常,“分解”和“减少”都是称为“catamorphisms”的算法( cata - 是用于打破“向下”的希腊语前缀,就像“分解代谢”一样)。纯粹的功能程序在做类似物时非常惊人,因为它们通常是某种“折叠”。

也就是说,我们可以将这个相同的算法编写为一个简洁的线条。我们将使用Data.Map.Strictfoldl'来确保此Haskell代码不会生成任何备用,无用的thunk。

import qualified Data.Map.Strict as Map

accum :: [Trade] -> Map Time Value
accum = foldl' (\oldMap tr -> Map.insertWith (+) (time tr) (value tr) oldMap) Map.empty

答案 4 :(得分:0)

MapCollectReduce方法。

insert (a,b) [] = [(a,[b])]
insert (a,b) ((k, vs):rest) | a == k    = (k, b:vs):rest
                            | otherwise = (k, vs):(insert (a,b) rest)

collect ((k,v):kvs) = insert (k,v) (collect kvs)
collect [] = []

trades l = map (\(k, vs) -> (k, sum vs)) $ collect l

我编写了一个非常原始collect函数,执行起来非常可怕。完成后,您可以获取数据并制作地图(在这种情况下不在此处,将其视为map id)。然后你收集这些对,意思是你用它的关键对这些对进行分组。最后,您计算收集的数据:您将给定密钥的所有值相加。

@leftaroundabout和@ jozefg的答案可能比一英里的表现要好一些,但是对于mapCollectReduce有一个很好的库我相信这会更快。 (这也很平行,但我不认为这对你很重要)