【问题标题】:Efficient functional algorithm for computing closure under an operator算子下计算闭包的高效函数算法
【发布时间】:2013-10-21 01:50:36
【问题描述】:

我对用于计算在一元运算符下的容器闭包的高效函数算法(最好是在 Haskell 中,甚至更优选已经作为库的一部分实现!)感兴趣。

对于列表,我想到的一个基本且低效的示例是:

closure :: Ord a => (a -> a) -> [a] -> [a]
closure f xs = first_dup (iterate (\xs -> nub $ sort $ xs ++ map f xs) xs) where
    first_dup (xs:ys:rest) = if xs == ys then xs else first_dup (ys:rest)

更高效的实现会跟踪每个阶段(“边缘”)生成的新元素,并且不会将函数应用于已应用的元素:

closure' :: Ord a => (a -> a) -> [a] -> [a]
closure' f xs = stable (iterate close (xs, [])) where
    -- return list when it stabilizes, i.e., when fringe is empty
    stable ((fringe,xs):iterates) = if null fringe then xs else stable iterates

    -- one iteration of closure on (fringe, rest);  key invariants:
    -- (1) fringe and rest are disjoint; (2) (map f rest) subset (fringe ++ rest)
    close (fringe, xs) = (fringe', xs') where
        xs' = sort (fringe ++ xs)
        fringe' = filter (`notElem` xs') (map f fringe)

例如,如果xs[0..19] 的非空子列表,那么closure' (\x->(x+3)`mod`20) xs 是[0..19],对于[0],迭代稳定在20 步,对于[0,1],迭代稳定在13 步, 以及 [0,4,8,12,16] 的 4 个步骤。

使用基于树的有序集实现可以获得更高的效率。 这已经完成了吗?对于二元(或更高元数)运算符下的闭包这个相关但更难的问题呢?

【问题讨论】:

  • 懒惰重要吗?
  • 不在我心目中的应用程序中,我有已经关闭的有限集,我想计算子集的关闭。

标签: algorithm haskell functional-programming


【解决方案1】:

使用unordered-containers 中的 Hash Array Mapped Trie 数据结构的类似的东西怎么样。对于无序容器 memberinsertO(min(n,W)) 其中 W 是哈希的长度。

module Closed where

import Data.HashSet (HashSet)
import Data.Hashable
import qualified Data.HashSet as Set

data Closed a = Closed { seen :: HashSet a, iter :: a -> a } 

insert :: (Hashable a, Eq a) => a -> Closed a -> Closed a
insert a c@(Closed set iter)
  | Set.member a set = c
  | otherwise        = insert (iter a) $ Closed (Set.insert a set) iter

empty :: (a -> a) -> Closed a
empty = Closed Set.empty

close :: (Hashable a, Eq a) => (a -> a) -> [a] -> Closed a
close iter = foldr insert (empty iter)

这是上面的一个变体,它以广度优先的方式更懒惰地生成解决方案集。

data Closed' a = Unchanging | Closed' (a -> a) (HashSet a) (Closed' a)

close' :: (Hashable a, Eq a) => (a -> a) -> [a] -> Closed' a
close' iter = build Set.empty where
  inserter :: (Hashable a, Eq a) => a -> (HashSet a, [a]) -> (HashSet a, [a])
  inserter a (set, fresh) | Set.member a set = (set, fresh)
                          | otherwise        = (Set.insert a set, a:fresh)
  build curr [] = Unchanging
  build curr as =
    Closed' iter curr $ step (foldr inserter (curr, []) as)
  step (set, added) = build set (map iter added)

-- Only computes enough iterations of the closure to 
-- determine whether a particular element has been generated yet
-- 
-- Returns both a boolean and a new 'Closed'' value which will 
-- will be more precisely defined and thus be faster to query
member :: (Hashable a, Eq a) => a -> Closed' a -> (Bool, Closed' a)
member _ Unchanging = False
member a c@(Closed' _ set next) | Set.member a set = (True, c)
                                | otherwise        = member a next

improve :: Closed' a -> Maybe ([a], Closed' a)
improve Unchanging = Nothing
improve (Closed' _ set next) = Just (Set.toList set, next)

seen' :: Closed' a -> HashSet a
seen' Unchanging = Set.empty
seen' (Closed' _ set Unchanging) = set
seen' (Closed' _ set next)       = seen' next

然后检查

>>> member 6 $ close (+1) [0]
...

>>> fst . member 6 $ close' (+1) [0]
True

【讨论】:

  • 对于我的主要问题,这是一个很好的“深度优先”解决方案,但我想知道它如何扩展到具有多个一元运算符或二元(和更高元数)运算符的情况。安排插入似乎很棘手。
  • 我添加了广度优先的方法,但我不确定如何以一种好的方式来处理更高数量的运算符。
  • @lambdacalculator 你能提供一个更高数量的闭包的来源吗?我只知道二元关系的闭包,我想了解更多。
  • 我在hackage.haskell.org/package/closure 在 Hackage 上发布了这段代码,我很乐意在 Github 上继续讨论 github.com/tel/closure
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-29
  • 2010-09-27
  • 1970-01-01
  • 2013-11-26
  • 1970-01-01
  • 2020-06-06
相关资源
最近更新 更多