【问题标题】:Haskell: shuffling data without functional dependenciesHaskell:无函数依赖的混洗数据
【发布时间】:2012-01-25 17:57:39
【问题描述】:

我正在尝试对某些数据实施 Fisher-Yates 洗牌。该算法对于一维数组很容易实现。但是,我需要能够在二维矩阵中打乱数据。

我认为可以很好地推广到高维数组的一种方法是将我的任意维度矩阵转换为一维索引数组,将它们打乱,然后通过交换该索引数组的每个索引处的元素来重组矩阵与索引数组元素的索引处的元素。换句话说,取一个 2x2 矩阵,例如:

1  2
3  4

我会把它转换成这个“数组”:

[(0, (0,0)),  (1, (0,1)),  (2, ((1,0)),  (3, (1,1))]

然后我会按照正常情况争先恐后地进入,比如说,

[(0, (1,0)),  (1, (0,1)),  (2, ((1,1)),  (3, (0,0))]

重组后,原来的矩阵会变成:

2  3
4  1

我在这里的基本方法是我想要一个看起来像这样的类型类:

class Shufflable a where
  indices    :: a -> Array Int b
  reorganize :: a -> Array Int b -> a

然后我将有一个执行 shuffle 的函数,如下所示:

fisherYates :: (RandomGen g) => g -> Array Int b -> (Array Int b, g)

我的想法是(减去 RandomGen 管道)我应该能够像这样洗牌一个可洗牌的东西:

shuffle :: (Shufflable a, RandomGen g) => a -> g -> (a, g)
shuffle array = reorganize array (fisherYates (indices array))

这是我目前所拥有的:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances  #-}

module Shuffle where

import Data.Array hiding (indices)
import System.Random         

fisherYates :: (RandomGen g) => Array Int e -> g -> (Array Int e, g)
fisherYates arr gen = go max gen arr
    where
      (_, max) = bounds arr

      go 0 g arr = (arr, g)
      go i g arr = go (i-1) g' (swap arr i j)
          where
            (j, g') = randomR (0, i) g

class Shuffle a b | a -> b where
  indices    :: a -> Array Int b
  reorganize :: a -> Array Int b -> a

shuffle :: (Shuffle a b, RandomGen g) => a -> g -> (a, g)
shuffle a gen = (reorganize a indexes, gen')
  where
    (indexes, gen') = fisherYates (indices a) gen

instance (Ix ix) => Shuffle (Array ix e) ix where
  reorganize a = undefined
  indices a    = array (0, maxIdx) (zip [0..maxIdx] (range bound))
      where
        bound = bounds a
        maxIdx = rangeSize bound - 1

swap :: Ix i => Array i e -> i -> i -> Array i e
swap arr i j = arr // [ (i, i'), (j, j') ]
    where
      i' = arr!j
      j' = arr!i

我的问题:

  1. 我觉得这是解决一个简单问题的很多语言扩展。换一种方式更容易理解还是写出来?
  2. 我觉得社区正在转向类型族而不是功能依赖。有没有办法用它来解决这个问题?
  3. 我的一部分想知道fisherYates 函数是否可以以某种方式移动到Shuffle 类型类中。是否有可能和/或值得这样做,以便您实现 shuffle 或同时实现 indices 和 reorganize?

谢谢!

【问题讨论】:

    标签: haskell random functional-dependencies type-families


    【解决方案1】:

    您可能想查看repa,它提供了 n 维数组,将它们的 形状(维度)编码到类型中;您可以使用它编写适用于任何形状数组的通用操作。

    我认为你可以通过使用backpermute 或fromFunction 构造数组并转换索引来完全避免类型类(它比看起来更有效,因为当你强制它时它会变成一个未装箱的数组;事实上, backpermute 是根据 fromFunction 实现的)。

    repa 本身使用了相当多的语言扩展,但您可能会发现它比标准库的数组更可取,因为性能(repa 的数组未装箱,并且提供的标准操作可以很好地完成自动并行化等功能)和便利性(IMO repa 的 API 比标准数组更好)。

    这是一个很好的introduction to repa。

    诚然,这些都不能直接简化您的代码。但是,如果 repa 的数组非常适合您,那么您最终得到的代码可能会避免您当前解决方案的许多复杂性。


    也就是说,将函数依赖转换为类型族非常简单; Shuffle 类变为

    class Shuffle a where
      type Elt a
      indices    :: a -> Array Int (Elt a)
      reorganize :: a -> Array Int (Elt a) -> a
    

    实例变为

    instance (Ix ix) => Shuffle (Array ix e) where
      type Elt (Array ix e) = ix
      ...
    

    并且Shuffle a b 约束变为Shuffle a。

    【讨论】:

    • 谢谢!以前没遇到过repa,会很有帮助的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-10
    • 1970-01-01
    • 2014-05-10
    • 2023-04-10
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多