haskell——设置定点库?
我正在寻找一个库,它将在多个可变数量的运算符下计算集合的不动点/闭包。例如,
fixwith [(+)] [1]
对于整数,应计算所有 N(自然数,1..
)。我尝试着写一下,但还是有些不足。它的效率不是很高,而且我有一种感觉,我对多参数函数的处理不是最优雅的。此外,是否可以使用内置的 fix
函数而不是手动递归来编写?
class OperatorN α β | β -> α where
wrap_op :: β -> (Int, [α] -> α)
instance OperatorN α (() -> α) where
wrap_op f = (0, \[] -> f ())
instance OperatorN α (α -> α) where
wrap_op f = (1, \[x] -> f x)
instance OperatorN α ((α, α) -> α) where
wrap_op f = (2, \[x, y] -> f (x, y))
instance OperatorN α ((α, α, α) -> α) where
wrap_op f = (3, \[x, y, z] -> f (x, y, z))
instance OperatorN α ((α, α, α, α) -> α) where
wrap_op f = (4, \[x, y, z, w] -> f (x, y, z, w))
type WrappedOp α = (Int, [α] -> α)
fixwith_next :: Eq α => [WrappedOp α] -> [α] -> [α]
fixwith_next ops s = List.nub (foldl (++) s (map g ops)) where
g (0, f) = [f []]
g (arity, f) = do
x <- s
let fx = \xs -> f (x:xs)
g (arity - 1, fx)
fixwith ops s
| next <- fixwith_next ops s
, next /= s
= fixwith ops next
fixwith _ s = s
示例,
> fixwith [wrap_op $ uncurry (*)] [-1 :: Int]
[-1,1]
> fixwith [wrap_op $ uncurry (*)] [1 :: Int]
[1]
> fixwith [wrap_op $ max 3, wrap_op $ \() -> 0] [1 :: Int]
[1,3,0]
设置版本
这并不能提高性能那么多,尽管我想我只需要弄清楚如何进行更少的计算以使其实际上更快。
import qualified Control.RMonad as RMonad
class OperatorN α β | β -> α where
wrap_op :: β -> (Int, [α] -> α)
instance OperatorN α (() -> α) where
wrap_op f = (0, \[] -> f ())
instance OperatorN α (α -> α) where
wrap_op f = (1, \[x] -> f x)
instance OperatorN α ((α, α) -> α) where
wrap_op f = (2, \[x, y] -> f (x, y))
instance OperatorN α ((α, α, α) -> α) where
wrap_op f = (3, \[x, y, z] -> f (x, y, z))
instance OperatorN α ((α, α, α, α) -> α) where
wrap_op f = (4, \[x, y, z, w] -> f (x, y, z, w))
type WrappedOp α = (Int, [α] -> α)
fixwith_next :: Ord α => [WrappedOp α] -> Set α -> Set α
fixwith_next ops s = Set.unions $ s : map g ops where
g (0, f) = RMonad.return $ f []
g (arity, f) = s RMonad.>>= \x ->
g (arity - 1, \xs -> f (x:xs))
fixwith' ops s
| next <- fixwith_next ops s
, next /= s
= fixwith' ops next
fixwith' _ s = s
fixwith ops s = Set.toList $ fixwith' ops (Set.fromList s)
设置懒惰的版本
我使用RMonad
对此进行了一些清理,并按照丹尼尔的建议使其变得懒惰。遗憾的是,我认为大部分时间都花在实际的乘法例程上,因此我没有看到此更改带来任何性能优势。不过,懒惰也很酷。
notin :: Ord α => Set α -> Set α -> Set α
notin = flip Set.difference
class Ord α => OperatorN α β | β -> α where
next_values :: β -> Set α -> Set α
instance Ord α => OperatorN α (α -> α) where
next_values f s = notin s $ s RMonad.>>= \x -> RMonad.return (f x)
instance Ord α => OperatorN α (α -> α -> α) where
next_values f s = s RMonad.>>= \x -> next_values (f x) s
instance Ord α => OperatorN α (α -> α -> α -> α) where
next_values f s = s RMonad.>>= \x -> next_values (f x) s
instance Ord α => OperatorN α (α -> α -> α -> α -> α) where
next_values f s = s RMonad.>>= \x -> next_values (f x) s
-- bind lambdas with next_values
fixwith_next :: Ord α => [Set α -> Set α] -> Set α -> Set α
fixwith_next nv_bnd s = Set.unions $ map (\f -> f s) nv_bnd -- bound next values
fixwith' :: Ord α => [Set α -> Set α] -> Set α -> [α]
fixwith' ops s@(fixwith_next ops -> next)
| Set.size next == 0 = []
| otherwise = (Set.toList next) ++ fixwith' ops (Set.union s next)
fixwith ops s = (Set.toList s) ++ fixwith' ops s
fixwith_lst ops = fixwith ops . Set.fromList
例如,
> take 3 $ fixwith [next_values (+2)] (Set.fromList [1])
[1,3,5]
我不得不失去一元运算,但这并不是一个交易杀手。
I'm looking for a library that will compute the fixed point / closure of a set under a number of operators of variable arity. For example,
fixwith [(+)] [1]
for the integers should compute all of N (the naturals, 1..
). I tried taking a stab at writing it, but some things are lacking. It's not very efficient, and I have a feeling that my handling of multi-arity functions is not the most elegant. Further, would it be possible to write using the builtin fix
function instead of manual recursion?
class OperatorN α β | β -> α where
wrap_op :: β -> (Int, [α] -> α)
instance OperatorN α (() -> α) where
wrap_op f = (0, \[] -> f ())
instance OperatorN α (α -> α) where
wrap_op f = (1, \[x] -> f x)
instance OperatorN α ((α, α) -> α) where
wrap_op f = (2, \[x, y] -> f (x, y))
instance OperatorN α ((α, α, α) -> α) where
wrap_op f = (3, \[x, y, z] -> f (x, y, z))
instance OperatorN α ((α, α, α, α) -> α) where
wrap_op f = (4, \[x, y, z, w] -> f (x, y, z, w))
type WrappedOp α = (Int, [α] -> α)
fixwith_next :: Eq α => [WrappedOp α] -> [α] -> [α]
fixwith_next ops s = List.nub (foldl (++) s (map g ops)) where
g (0, f) = [f []]
g (arity, f) = do
x <- s
let fx = \xs -> f (x:xs)
g (arity - 1, fx)
fixwith ops s
| next <- fixwith_next ops s
, next /= s
= fixwith ops next
fixwith _ s = s
examples,
> fixwith [wrap_op $ uncurry (*)] [-1 :: Int]
[-1,1]
> fixwith [wrap_op $ uncurry (*)] [1 :: Int]
[1]
> fixwith [wrap_op $ max 3, wrap_op $ \() -> 0] [1 :: Int]
[1,3,0]
set version
This doesn't improve performance all that much, though I guess I just need to figure out how to do less computation to make it actually faster.
import qualified Control.RMonad as RMonad
class OperatorN α β | β -> α where
wrap_op :: β -> (Int, [α] -> α)
instance OperatorN α (() -> α) where
wrap_op f = (0, \[] -> f ())
instance OperatorN α (α -> α) where
wrap_op f = (1, \[x] -> f x)
instance OperatorN α ((α, α) -> α) where
wrap_op f = (2, \[x, y] -> f (x, y))
instance OperatorN α ((α, α, α) -> α) where
wrap_op f = (3, \[x, y, z] -> f (x, y, z))
instance OperatorN α ((α, α, α, α) -> α) where
wrap_op f = (4, \[x, y, z, w] -> f (x, y, z, w))
type WrappedOp α = (Int, [α] -> α)
fixwith_next :: Ord α => [WrappedOp α] -> Set α -> Set α
fixwith_next ops s = Set.unions $ s : map g ops where
g (0, f) = RMonad.return $ f []
g (arity, f) = s RMonad.>>= \x ->
g (arity - 1, \xs -> f (x:xs))
fixwith' ops s
| next <- fixwith_next ops s
, next /= s
= fixwith' ops next
fixwith' _ s = s
fixwith ops s = Set.toList $ fixwith' ops (Set.fromList s)
set version that's lazy
I used RMonad
to clean this up a little, and made it lazy as Daniel suggested. I think most of the time is being spent in the actual multiplication routines, sadly, so I didn't see any performance benefit from this change. The laziness is cool though.
notin :: Ord α => Set α -> Set α -> Set α
notin = flip Set.difference
class Ord α => OperatorN α β | β -> α where
next_values :: β -> Set α -> Set α
instance Ord α => OperatorN α (α -> α) where
next_values f s = notin s $ s RMonad.>>= \x -> RMonad.return (f x)
instance Ord α => OperatorN α (α -> α -> α) where
next_values f s = s RMonad.>>= \x -> next_values (f x) s
instance Ord α => OperatorN α (α -> α -> α -> α) where
next_values f s = s RMonad.>>= \x -> next_values (f x) s
instance Ord α => OperatorN α (α -> α -> α -> α -> α) where
next_values f s = s RMonad.>>= \x -> next_values (f x) s
-- bind lambdas with next_values
fixwith_next :: Ord α => [Set α -> Set α] -> Set α -> Set α
fixwith_next nv_bnd s = Set.unions $ map (\f -> f s) nv_bnd -- bound next values
fixwith' :: Ord α => [Set α -> Set α] -> Set α -> [α]
fixwith' ops s@(fixwith_next ops -> next)
| Set.size next == 0 = []
| otherwise = (Set.toList next) ++ fixwith' ops (Set.union s next)
fixwith ops s = (Set.toList s) ++ fixwith' ops s
fixwith_lst ops = fixwith ops . Set.fromList
example
> take 3 $ fixwith [next_values (+2)] (Set.fromList [1])
[1,3,5]
I had to lose unary operations, but that's not a deal killer.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不,
fix
是一个转移注意力的话题。它正在计算与您不同类型的定点。您对数量的处理非常务实。有许多不同的方法可以让它变得不那么乏味;请参阅我之前的答案之一 就是这样一种方式。我相信最终也会有人加入并添加另一个令人兴奋的基于类型级数字的解决方案。 =)
为了提高效率,我不确定仅使用
Eq
实例是否可以做得更好。您可能会考虑从(本地)g
函数的调用结果中过滤掉s
值 - 也就是说,让fixwith_next
仅返回新元素。这应该会使终止检查更快,甚至可能使高效、懒惰的fixwith
成为可能。如果您可以接受严格要求并需要
Ord
实例,那么使用真正的Set
可能也会提高效率。Nope,
fix
is a red herring. It's computing a different kind of fixed-point than you are.Your handling of arity is very pragmatic. There are a number of different ways you could make it a bit less boiler-platey; see one of my previous answers for one such way. I'm sure someone will come on and add another mind-blowing type-level numerals-based solution eventually, as well. =)
For efficiency, I'm not sure you can do much better with only an
Eq
instance anyway. You might consider filtering outs
values from the results of the calls to the (local)g
function -- that is, lettingfixwith_next
return only the new elements. This ought to make the termination check faster and may even make it possible to have a productive, lazyfixwith
.If you're alright with strictness and requiring an
Ord
instance, using realSet
s will probably improve the efficiency as well.