I'm trying to implement a Fisher-Yates shuffle of some data. This algorithm is easy to implement for one-dimensional arrays. However, I need to be able to shuffle data in a two-dimensional matrix.
An approach which I think could generalize nicely to higher dimensional arrays is to convert my arbitrarily dimensioned matrix to a single-dimensional array of indices, shuffle those, and then reorganize the matrix by swapping the element at each index of this index array with the element at the index of the element of the index array. In other words, to take a 2x2 matrix such as:
1 2
3 4
I would convert this into this "array":
[(0, (0,0)), (1, (0,1)), (2, ((1,0)), (3, (1,1))]
This I would then scramble per normal into, say,
[(0, (1,0)), (1, (0,1)), (2, ((1,1)), (3, (0,0))]
Once reorganized, the original matrix would become:
2 3
4 1
My basic approach here is that I want to have a type class that looks something like this:
class Shufflable a where
indices :: a -> Array Int b
reorganize :: a -> Array Int b -> a
Then I'll have a function to perform the shuffle which looks like this:
fisherYates :: (RandomGen g) => g -> Array Int b -> (Array Int b, g)
The thinking is that (minus the RandomGen plumbing) I should be able to shuffle a shuffleable thing like so:
shuffle :: (Shufflable a, RandomGen g) => a -> g -> (a, g)
shuffle array = reorganize array (fisherYates (indices array))
Here's what I have so far:
{-# 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
My problems:
- I feel like this is a lot of language extensions for solving a simple problem. Would it be easier to understand or write another way?
- I feel like the community is moving towards type families over functional dependencies. Is there a way to use that instead to solve this problem?
- A part of me wonders if the
fisherYates
function can be moved into theShuffle
typeclass somehow. Would it be possible and/or worth doing to set this up so that you either implementshuffle
or implement bothindices
andreorganize
?
Thanks!