haskell - 以区间为键的类似 map 的容器和类似 zip 的组合操作

标签 haskell containers intervals

我正在寻找一个 Haskell 容器类型,如 Data.Map使用区间作为键,其中最左侧和最右侧的键也可能是无界区间,但不重叠。此外,容器应支持类似于 zipWith 的功能。这允许将两个容器合并为一个新的容器,使用两个键集的交集作为新的键集和两个值集的逐点组合的参数函数。

已经有几个包提供基于区间的 map 。我看过 IntervalMap , fingertreeSegmentTree ,但这些包似乎都没有提供所需的组合功能。他们似乎都使用了交集函数的区间,这在两个 map 中都是相等的,而我需要一个版本,如果需要的话,可以将区间分解成更小的区间。

容器基本上应该为 Ord k => k -> Maybe a 形式的键/值系列提供有效且可存储的映射。 ,即函数只定义在特定的区间或具有更大的区间映射到相同的值。

这是一个小例子来演示这个问题:

... -4 -3 -2 -1  0  1  2  3  4  ...  -- key set
-----------------------------------
... -1 -1 -1 -1  0  1  1  1  1  ...  -- series corresponding to signum
...  5  5  5  5  5  5  5  5  5  ...  -- series corresponding to const 5

第一个系列可以通过映射 [-infinity, -1] -> -1; [0, 0] -> 0; [1, infinity] -> 1 有效表达第二个来自 [-infinity, infinity] -> 5 .现在使用 (*) 应用组合函数作为 arument 函数应该给出一个新的系列
... -4 -3 -2 -1  0  1  2  3  4  ...  -- key set
-----------------------------------
... -5 -5 -5 -5  0  5  5  5  5  ...  -- combined series

这里的关键点——而且所有上述包似乎都不能做到这一点——是,当组合这两个系列的键集时,你还必须考虑不同的值。这两个系列都涵盖了 [-infinity, infinity] 的全系列产品但是为了最后的系列,有必要把它分成三个部分。

还有一些用于处理间隔的包,例如range包,它还提供了对区间列表的交集操作。但是,我没有找到将它与 Map 变体之一结合使用的方法,因为它在使用它们进行计算时会折叠相邻的间隔。

注意:这样的容器有点类似于 ZipList这延伸到双方,这就是为什么我认为也应该可以定义合法的Applicative例如,其中 <*>对应于上述组合函数。

长话短说,是否已经有提供这种容器的包?或者有没有一种简单的方法来使用现有的包来构建一个?

最佳答案

以上评论中最好的建议似乎是 step-function 包,正如 B. Mehta 所建议的那样。我还没有尝试过那个包,但它看起来像是围绕着 SF 构建了一个包装器。类型是我一直在寻找的。

同时,我实现了另一个我想分享的解决方案。组合函数的代码(下面代码中的 combineAscListWith)有点笨拙,因为它比仅获取两个 map 的交集更通用,所以我将勾勒出这个想法:

首先我们需要一个 Interval输入 Ord存储成对 Val a 的实例值可以是 -infinity、某个值 x 或 +infinity。我们可以建立一个表单 IntervalMap这只是一个正常的 Map将这些间隔映射到最终值。

当结合两个这样的 IntervalMaps通过交集,我们首先将映射转换为键/值对列表。接下来,我们并行遍历两个列表,将两个列表压缩到另一个对应于最终交叉图的列表中。组合列表元素时有两种主要情况:

  • 最左边的两个间隔都以相同的值开始。在那种情况下,我们发现了一个实际上重叠/相交的间隔。我们将较长的间隔剪辑为较短的间隔,并使用与两个间隔关联的值来获得结果值,现在该值与较短的间隔一起进入结果列表。较长时间间隔的其余部分返回到输入列表。
  • 其中一个间隔的起始值小于另一个,这意味着我们发现了两个系列中不重叠的一部分。所以对于交集,可以舍弃所有不重叠的区间(甚至整个区间)。其余的(如果有)返回到输入列表。

  • 为了完整起见,这里是完整的示例代码。同样,代码相当笨拙;基于步进函数的实现肯定会更优雅。
    import           Control.Applicative
    import           Data.List
    import qualified Data.Map as Map
    
    
    data Val a = NegInf | Val a | Inf deriving (Show, Read, Eq, Ord)
    
    instance Enum a => Enum (Val a) where
        succ v = case v of
            NegInf -> NegInf
            Val x  -> Val $ succ x
            Inf    -> Inf
        pred v = case v of
            NegInf -> NegInf
            Val x  -> Val $ pred x
            Inf    -> Inf
        toEnum = Val . toEnum
        fromEnum (Val x) = fromEnum x
    
    
    data Interval a = Interval { lowerBound :: Val a, upperBound :: Val a } deriving (Show, Read, Eq)
    
    instance Ord a => Ord (Interval a) where
        compare ia ib = let (a, a') = (lowerBound ia, upperBound ia)
                            (b, b') = (lowerBound ib, upperBound ib)
                        in  case () of
                                _ | a' < b             -> LT
                                _ | b' < a             -> GT
                                _ | a == b && a' == b' -> EQ
                                _ -> error "Ord.Interval.compare: undefined for overlapping intervals"
    
    
    newtype IntervalMap i a = IntervalMap { unIntervalMap :: Map.Map (Interval i) a }
                              deriving (Show, Read)
    
    instance Functor (IntervalMap i) where
        fmap f = IntervalMap . fmap f . unIntervalMap
    
    instance (Ord i, Enum i) => Applicative (IntervalMap i) where
        pure = IntervalMap . Map.singleton (Interval NegInf Inf)
        (<*>) = intersectionWith ($)
    
    
    intersectionWith :: (Ord i, Enum i) => (a -> b -> c)
                     -> IntervalMap i a -> IntervalMap i b -> IntervalMap i c
    intersectionWith f = combineWith (liftA2 f)
    
    combineWith :: (Ord i, Enum i) => (Maybe a -> Maybe b -> Maybe c)
                -> IntervalMap i a -> IntervalMap i b -> IntervalMap i c
    combineWith f (IntervalMap mpA) (IntervalMap mpB) =
        let cs = combineAscListWith f (Map.toAscList mpA) (Map.toAscList mpB)
        in IntervalMap $ Map.fromList [ (i, v) | (i, Just v) <- cs ]
    
    combineAscListWith :: (Ord i, Enum i) => (Maybe a -> Maybe b -> c)
                -> [(Interval i, a)] -> [(Interval i, b)] -> [(Interval i, c)]
    combineAscListWith f as bs = case (as, bs) of
        ([], _) -> map (\(i, v) -> (i, f Nothing (Just v))) bs
        (_, []) -> map (\(i, v) -> (i, f (Just v) Nothing)) as
        ((Interval a a', va) : as', (Interval b b', vb) : bs')
            | a == b -> case () of
                _ | a' == b' -> (Interval a a', f (Just va) (Just vb)) : combineAscListWith f as' bs'
                _ | a' < b'  -> (Interval a a', f (Just va) (Just vb)) : combineAscListWith f as' ((Interval (succ a') b', vb) : bs')
                _ | a' > b'  -> (Interval a b', f (Just va) (Just vb)) : combineAscListWith f ((Interval (succ b') a', va) : as') bs'
            | a < b  -> case () of
                _ | a' < b   -> ((Interval a a', f (Just va) Nothing)) :
                    (if succ a' == b then id else ((Interval (succ a') (pred b), f Nothing Nothing) :)) (combineAscListWith f as' bs)
                _ | True     -> (Interval a (pred b), f (Just va) Nothing) : combineAscListWith f ((Interval b a', va) : as') bs
            | a > b  -> case () of
                _ | b' < a   -> ((Interval b b', f Nothing (Just vb))) :
                    (if succ b' == a then id else ((Interval (succ b') (pred a), f Nothing Nothing) :)) (combineAscListWith f as bs')
                _ | True     -> (Interval b (pred a), f Nothing (Just vb)) : combineAscListWith f as ((Interval a b', vb) : bs')
    
    
    showIntervalMap :: (Show i, Show a, Eq i) => IntervalMap i a -> String
    showIntervalMap = intercalate "; " . map (\(i, v) -> showInterval i ++ " -> " ++ show v)
        . Map.toAscList . unIntervalMap
        where
            showInterval (Interval (Val a) (Val b)) | a == b = "[" ++ show a ++ "]"
            showInterval (Interval a b) = "[" ++ showVal a ++ " .. " ++ showVal b ++ "]"
            showVal NegInf  = "-inf"
            showVal (Val x) = show x
            showVal Inf     = "inf"
    
    main :: IO ()
    main = do
        let signumMap = IntervalMap $ Map.fromList [(Interval NegInf (Val $ -1), -1),
                (Interval (Val 0) (Val 0), 0), (Interval (Val 1) Inf, 1)]
        putStrLn $ showIntervalMap $ (*) <$> signumMap <*> pure 5
    

    关于haskell - 以区间为键的类似 map 的容器和类似 zip 的组合操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51497198/

    相关文章:

    optimization - 关于 GHC 实现的好的介绍性文字吗?

    haskell - Control.Exception.evaluate 对等效函数生成的 thunk 的处理方式不同

    haskell - 减小 Snap 二进制文件的大小?

    linux - 无法在正在运行的 Docker 容器中 ssh localhost

    r - 划分为类 : jenks vs kmeans

    haskell - 如何将高阶函数应用于 Haskell 中的有效函数?

    docker - 如何增加 docker 容器中/dev/shm 的大小

    html - Container Div 没有封装内容

    r - 查找间隔(段)的成对重叠

    mysql - SELECT 2 个日期时间列中的差异 WHERE 间隔