我正在尝试通过Learn You A Haskell 来学习 Haskell ......但我很不耐烦,想实现我最喜欢的算法,看看我是否可以。
我正在研究用于循环检测的乌龟/野兔算法(弗洛伊德算法)。
这是我到目前为止的代码:
idx :: (Eq a) => (a -> a) -> a -> a -> a
idx f tortoise hare
| (f tortoise) == (f (f hare)) = (f f hare)
| otherwise = (idx f) (f tortoise) (f f hare)
mu :: (Eq a) => (a -> a) -> a -> a -> Integer -> (Integer, a)
mu f tortoise hare cntr
| (f tortoise) == (f hare) = (cntr+1, f tortoise)
| otherwise = (mu f) (f tortoise) (f hare) (cntr+1)
lam :: (Eq a) => (a -> a) -> a -> a -> Integer -> Integer
lam f tortoise hare cntr
| tortoise == hare = cntr+1
| otherwise = (lam f) tortoise (f hare) (cntr+1)
floyd :: (Eq a) => (a -> a) -> a -> (Integer, Integer)
floyd f x0 =
let z = (idx f) x0 x0
(y1, t) = (mu f) x0 z 0
y2 = (lam f) t (f t) 0
in (y1, y2)
tester :: (Integer a) => a -> a
tester a
| a == 0 = 2
| a == 2 = 6
| a == 6 = 1
| a == 1 = 3
| a == 3 = 6
| a == 4 = 0
| a == 5 = 1
| otherwise = error "Input must be between 0 and 6"
(floyd tester) 0
这试图将逻辑分解为三个步骤。首先获取 f_idx == f_{2*idx} 的索引,然后从头开始移动以获取参数 mu(从第一个元素到循环开始的距离),然后移动直到遇到重复(循环的长度) .
该功能floyd
是我将这些组合在一起的骇人听闻的尝试。
除了这有点不起作用之外,我在加载模块时也遇到了问题,我不知道为什么:
Prelude> :load M:\papers\programming\floyds.hs
[1 of 1] Compiling Main ( M:\papers\programming\floyds.hs, interpreted )
M:\papers\programming\floyds.hs:23:12:
`Integer' is applied to too many type arguments
In the type signature for `tester': tester :: Integer a => a -> a
Failed, modules loaded: none.
更改所有出现的Integer
toInt
或Num
不让它变得更好。
我不理解Int
. 在本教程中,大多数函数的类型声明始终具有以下形式
function_name :: (Some_Type a) => <stuff involving a and possibly other types>
但是当我替换为(Eq a)
或者(Num a)
我(Int a)
得到一个类似的错误(类型应用于太多参数)。
我试着阅读这个,但它不同意教程的符号(例如这些示例中定义的几乎每个函数)。
我一定是严重误解了 Types 与 TypeClasses,但这正是我认为我确实理解的导致我在上面的代码中进行类型声明的原因。
后续可能是:函数类型声明中有多个 TypeClass 的语法是什么?就像是:
mu :: (Eq a, Int b) => (a -> a) -> a -> a -> b -> (b, a)
(但这也给出了编译错误,说Int
应用于太多参数)。
添加
清理并根据答案进行更改,下面的代码似乎正在工作:
idx :: (Eq a) => (a -> a) -> a -> a -> a
idx f tortoise hare
| (f tortoise) == (f (f hare)) = (f (f hare))
| otherwise = (idx f) (f tortoise) (f (f hare))
mu :: (Eq a) => (a -> a) -> a -> a -> Integer -> (Integer, a)
mu f tortoise hare cntr
| (f tortoise) == (f hare) = (cntr+1, (f tortoise))
| otherwise = (mu f) (f tortoise) (f hare) (cntr+1)
lam :: (Eq a) => (a -> a) -> a -> a -> Integer -> Integer
lam f tortoise hare cntr
| tortoise == hare = cntr+1
| otherwise = (lam f) tortoise (f hare) (cntr+1)
floyd :: (Eq a) => (a -> a) -> a -> (Integer, Integer)
floyd f x0 =
let z = (idx f) x0 x0
(y1, t) = (mu f) x0 z 0
y2 = (lam f) t (f t) 0
in (y1, y2)
tester :: (Integral a) => a -> a
tester a
| a == 0 = 2
| a == 2 = 6
| a == 6 = 1
| a == 1 = 3
| a == 3 = 6
| a == 4 = 0
| a == 5 = 1
| otherwise = error "Input must be between 0 and 6"
然后我看到
*Main> floyd tester 2
(1,3)
并给出这个测试功能(基本上就像维基百科示例中的那个),这是有道理的。如果你开始 ax0 = 2
那么序列是2 -> 6 -> 1 -> 3 -> 6...
,所以mu
是 1 (你必须移动一个元素才能到达序列的开头)和lam
3 (序列每三个条目重复一次)。
我想在您可能“重复”之前是否总是将第一点视为老化存在一些问题。
如果有人对此有任何建议,我将不胜感激。特别是,我的cntr
构造对我来说似乎不起作用..这是一种计算重复调用次数的方法。我不确定是否有更好/不同的方式不像保存变量的状态。