Haskell: перемешивание данных без функциональных зависимостей

Я пытаюсь реализовать перестановку Фишера-Ятса некоторых данных. Этот алгоритм легко реализовать для одномерных массивов. Однако мне нужно иметь возможность перетасовывать данные в двумерной матрице.

Подход, который, как мне кажется, можно хорошо обобщить на массивы большей размерности, заключается в том, чтобы преобразовать мою матрицу произвольной размерности в одномерный массив индексов, перетасовать их, а затем реорганизовать матрицу, поменяв местами элемент с каждым индексом этого индексного массива с элементом с индексом элемента индексного массива. Другими словами, если взять матрицу 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

Затем у меня будет функция для выполнения перетасовки, которая выглядит так:

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?

Спасибо!

7
задан Daniel Lyons 22 December 2011 в 00:13
поделиться