Haskell:抽象遗传算法

发布于 2024-10-17 17:06:33 字数 1022 浏览 1 评论 0原文

我是 Haskell 编程世界的新手,正在尝试一种简单的遗传算法,以找到旅行商问题的良好解决方案。我将解决方案表示为整数的排列,所以我有这种类型的同义词算法

type Genome = [Int]

本身是一组对解决方案进行操作的函数:

mutation :: Genome -> Genome
selectParents :: [Genome] -> [Genome] -> [Genome]
crossover :: Genome -> Genome -> (Genome, Genome)
selectSurvivors :: [Genome] -> [Genome] -> [Genome]

我不确定我的代码有多少与我的问题相关,所以请询问是否有更多需要详细信息。值得一提的是,上面的类型签名实际上是简化的,我实际上使用 State monad 来携带 StdGen,因此所有这些函数实际上都返回有状态计算。

我想用这个做几件事,但无法完全理解。我希望能够为解决方案选择不同的表示形式,在我看来,这将是使用类型类的自然场所,因此 Genome 将是类型类,而 [Int]基因组的特定实例。

现在,我希望能够尝试这些实现,并且希望能够在其他项目中使用这些代码。使用这样的类型类将要求我创建的每个新算法都需要创建另一个 Genome 实例,这是创建库的好方法吗?

一个额外的问题,只是一直困扰我的一件事,有没有办法为函数创建类似类型同义词的东西,这样如果我正在编写一个将函数作为参数的函数,我可以编写同义词而不是整个类型函数的签名 ie 以便像下面这样的东西可以工作。

type someFunc = [Int] -> [Int] -> Int
someOtherFunc :: someFunc -> [Int] -> Int

是的,希望这是对问题的一个足够清晰的解释,感觉我错过了真正明显的答案,但它并没有跳到我身上。干杯

I'm new to the world of Haskell programming and I'm cutting my teeth on a simple genetic algorithm for finding good solutions to the Travelling Salesman problem. I am representing the solutions as permutations on Integers and so I have this type synonym

type Genome = [Int]

The algorithm itself is a set of functions which operate on the solutions:

mutation :: Genome -> Genome
selectParents :: [Genome] -> [Genome] -> [Genome]
crossover :: Genome -> Genome -> (Genome, Genome)
selectSurvivors :: [Genome] -> [Genome] -> [Genome]

I'm not sure how much of my code is relevant to my question so please ask if more details are needed. One thing that might be worth mentioning is that the type signatures above are actually simplified, I am in fact using the State monad to carry around an StdGen so all of these functions actually return stateful computations.

There are several things which I would like to do with this but can't quite get my head around. I want to make it possible to choose different representations for the solutions, it seems to me that this would be a natural place to use a type class, so that Genome would be the type class and [Int] a specific instance of this Genome.

Now, I want to be able to experiment with the implementations, and I want to be able to use the code in other projects. Using a type class like this would require that every new algorithm I create would require me to create another instance of Genome, is this a good way to go about creating a library?

One bonus question, just a thing that's been bothering me, is there any way to create something like a type synonym for a function so that if I'm writing a function which takes functions as arguments I can write the synonym rather than the whole type signature of the function i.e so that something like the following would work.

type someFunc = [Int] -> [Int] -> Int
someOtherFunc :: someFunc -> [Int] -> Int

Right, hopefully that's a lucid enough explanation of the problem, feel like I've missed the really obvious answer but it hasn't jumped out at me. Cheers

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

千鲤 2024-10-24 17:06:33

不幸的是,理想的解决方案通常取决于您的问题领域。 这篇博文讨论了类型类方法和按位方法。我个人认为,如果您想要灵活性,混合方法是最好的。如果有一个好的按位映射,您可以定义它,并从中派生实现,否则您可以手动实现交叉和变异。

ja的方法其实行不通。您的某些基因组功能将需要随机输入,您可以通过使用随机数生成器在状态 monad 中运行来获得随机输入 像这个线程

class Genome a where
    fitness :: a -> Int
    breed :: (RandomGen g, MonadState g m) => a -> a -> m a
    mutate :: (RandomGen g, MonadState g m) => a -> m a

然后你就有了在基因组上运行的通用函数,无论实现如何。

selectParents :: (Genome a, RandomGen g, MonadState g m) => [a] -> m [a]
selectSurvivors :: (Genome a, RandomGen g, MonadState g m) => [a] -> m [a]

如果您确实有良好的位映射,则可以在 BitArray 上定义固定函数(请注意,每个函数都必须将适应度函数作为参数)

breed :: (RandomGen g, MonadState g m) => BitArray -> BitArray -> m BitArray
mutate :: (RandomGen g, MonadState g m) => BitArray -> m BitArray
selectParents :: (RandomGen g, MonadState g m) => (BitArray -> Int) -> [BitArray] -> m [BitArray]
selectSurvivors :: (RandomGen g, MonadState g m) => (BitArray -> Int) -> [BitArray] -> m [BitArray]

Unfortunately, the ideal solution usually depends on your problem domain. This blog post talks about the typeclass approach and the bitwise approach. I personally think a hybrid approach is best if you want flexibility. If there is a good bitwise mapping, you can define it, and the implementation is derived from that, otherwise you can implement the crossover and mutate manually.

ja's method is actually not going to work. Some of your genome functions are going to need random input, which you can get by running in the state monad with a random number generator like this thread

class Genome a where
    fitness :: a -> Int
    breed :: (RandomGen g, MonadState g m) => a -> a -> m a
    mutate :: (RandomGen g, MonadState g m) => a -> m a

Then you have common functions that operate on sets of genomes, regardless of implementation.

selectParents :: (Genome a, RandomGen g, MonadState g m) => [a] -> m [a]
selectSurvivors :: (Genome a, RandomGen g, MonadState g m) => [a] -> m [a]

If you do have a good bit mapping, you can just define fixed functions on BitArrays (notice that each will have to take the fitness function as a parameter)

breed :: (RandomGen g, MonadState g m) => BitArray -> BitArray -> m BitArray
mutate :: (RandomGen g, MonadState g m) => BitArray -> m BitArray
selectParents :: (RandomGen g, MonadState g m) => (BitArray -> Int) -> [BitArray] -> m [BitArray]
selectSurvivors :: (RandomGen g, MonadState g m) => (BitArray -> Int) -> [BitArray] -> m [BitArray]
李不 2024-10-24 17:06:33

是的,使用类型类来表示基因组是一个好方法。像这样的事情:

class Genome a where
   mutation :: a -> a
   selectParents :: [a] -> [a] -> [a]
   crossover :: a -> a -> (a, a)
   selectSurvivors :: [a] -> [a] -> [a]
instance Genome [a] where
   mutation l = l
   selectParents l1 l2 = l1
   crossover g1 g2 = (g1,g2)
   selectSurvivors l1 l2 = l1
data Tree a = Leaf a | Branch [Tree a]   
instance Genome (Tree a) where
   mutation t = t
   selectParents l1 l2 = l1
   crossover g1 g2 = (g1,g2)
   selectSurvivors l1 l2 = l1

至于为每个算法实例化一个新的数据类型,您可以在库中包含一些实例,但是实例化新的数据类型没有问题 - 这就是重点!

Yes, using a type class to represent a genome is a good way to go. Something like this:

class Genome a where
   mutation :: a -> a
   selectParents :: [a] -> [a] -> [a]
   crossover :: a -> a -> (a, a)
   selectSurvivors :: [a] -> [a] -> [a]
instance Genome [a] where
   mutation l = l
   selectParents l1 l2 = l1
   crossover g1 g2 = (g1,g2)
   selectSurvivors l1 l2 = l1
data Tree a = Leaf a | Branch [Tree a]   
instance Genome (Tree a) where
   mutation t = t
   selectParents l1 l2 = l1
   crossover g1 g2 = (g1,g2)
   selectSurvivors l1 l2 = l1

As for instancing a new datatype for each algorithm, you can include a few instances in your library, but there's no problem instancing new ones - that's the point !

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文