英文:
How to shuffle a list i agda?
问题
I would like to randomly shuffle a list in agda.
I thought about taking random n elements from the beginning and adding them to the end, but I couldn't get the random part to work from this approach.
Is this possible with pattern matching or do I really have to figure out random number generation?
Current state:
shuffle : {A : Set} -> List A -> List A
shuffle [] = []
shuffle (x ∷ []) = x ∷ []
shuffle (x ∷ x₁ ∷ xs) = ?
我想在Agda中对一个列表进行随机洗牌。
我考虑过从开头随机选择 n 个元素并将它们添加到末尾,但是我无法使这种方法中的随机部分工作。
这是否可以使用模式匹配来实现,还是我真的必须解决随机数生成的问题?
当前状态:
shuffle : {A : Set} -> List A -> List A
shuffle [] = []
shuffle (x ∷ []) = x ∷ []
shuffle (x ∷ x₁ ∷ xs) = ?
英文:
I would like to randomly shuffle a list in agda.
I thought about taking random n elements from the beginning and adding them to the end, but I couldn't get the random part to work from this approach.
Is this possible with pattern matching or do I really have to figure out random number generation?
Current state:
shuffle : {A : Set} -> List A -> List A
shuffle [] = []
shuffle (x ∷ []) = x ∷ []
shuffle (x ∷ x₁ ∷ xs) = ?
答案1
得分: 1
我认为实现Fisher-Yates洗牌算法的直接方式是使用一个monadic效果来生成随机数:
open import Data.Nat
open import Data.Fin
open import Data.Vec
variable
A : Set _
open import Data.Product hiding (swap)
swap : ∀ {n} → Fin (suc n) → Vec A (suc n) → (A × Vec A n)
swap zero (x₀ ∷ xs) = x₀ , xs
swap {A = A} (suc i) (x₀ ∷ xs) = go i xs
where
go : ∀ {n} → Fin n → Vec A n → A × Vec A n
go zero (x ∷ xs) = x , x₀ ∷ xs
go (suc i) (x ∷ xs) with go i xs
... | (x′ , xs′) = x′ , x ∷ xs′
open import Category.Monad
module Shuffle {M} (Mon : RawMonad M) (randomFin : ∀ {n} → M (Fin (suc n))) where
open RawMonad Mon
shuffle : ∀ {n} → Vec A n → M (Vec A n)
shuffle [] = pure []
shuffle {n = suc n} xs@(_ ∷ _) = do
k ← randomFin
let (x′ , xs′) = swap k xs
xs″ ← shuffle xs′
pure (x′ ∷ xs″)
那么我们如何生成随机的Fin n
?@mudri的评论指向了Data.Nat.PseudoRandom.LCG
,它建议使用带有类型ℕ
的step
,并使用n
的简单余数来生成Fin n
:
module ShuffleState (step : ℕ → ℕ) where
open import Category.Monad.State
open RawMonadState (StateMonadState ℕ)
randomℕ : State ℕ ℕ
randomℕ = modify step ≫= get
open import Data.Nat.DivMod
randomFin : ∀ {n} → State ℕ (Fin (suc n))
randomFin = (_mod _) <$> randomℕ
open Shuffle monad randomFin public
示例用法:
module _ where
open import Data.Nat.PseudoRandom.LCG
open ShuffleState (step glibc)
test : Vec (Fin 10) 10
test = proj₁ (shuffle (allFin 10) 0)
请注意,这并不证明输出确实是输入的排列。如果您需要这一点,我认为更容易改变shuffle
和swap
以产生一个排列证明(例如,像这里那样),而不是直接操作输入向量并在外部进行证明。
编辑以添加:
至少在Idris 2中,我们可以通过在适当的地方使用线性类型来证明shuffle
在被洗牌的向量中是线性的:
import Data.Vect
import Data.Fin
swap : Fin (S n) -> (1 xs : Vect (S n) a) -> Vect (S n) a
swap FZ (x0 :: xs) = (x0 :: xs)
swap (FS i) (x0 :: xs) = go i xs
where
go : Fin k -> Vect k a -> Vect (S k) a
go FZ (x :: xs) = x :: x0 :: xs
go (FS i) (x :: xs) =
let (x' :: xs') = go i xs in
x' :: x :: xs';
shuffle: Monad m => (randomFin : {k : _} -> m (Fin (S k))) -> {n : _} -> (1 xs : Vect n a) -> m (Vect n a)
shuffle [] = pure []
shuffle (x::xs) = do
k <- randomFin
let (x' :: xs') = swap k (x :: xs)
xs'' <- shuffle xs'
pure (x' :: xs'')
请注意shuffle
签名中xs
的类型。我认为这里的参数化和线性类型应该足够说服您shuffle
确实对其输入进行了洗牌。
英文:
I think the straightforward way to implement Fisher-Yates shuffle is to use a monadic effect for generating random numbers:
open import Data.Nat
open import Data.Fin
open import Data.Vec
variable
A : Set _
open import Data.Product hiding (swap)
swap : ∀ {n} → Fin (suc n) → Vec A (suc n) → (A × Vec A n)
swap zero (x₀ ∷ xs) = x₀ , xs
swap {A = A} (suc i) (x₀ ∷ xs) = go i xs
where
go : ∀ {n} → Fin n → Vec A n → A × Vec A n
go zero (x ∷ xs) = x , x₀ ∷ xs
go (suc i) (x ∷ xs) with go i xs
... | (x′ , xs′) = x′ , x ∷ xs′
open import Category.Monad
module Shuffle {M} (Mon : RawMonad M) (randomFin : ∀ {n} → M (Fin (suc n))) where
open RawMonad Mon
shuffle : ∀ {n} → Vec A n → M (Vec A n)
shuffle [] = pure []
shuffle {n = suc n} xs@(_ ∷ _) = do
k ← randomFin
let (x′ , xs′) = swap k xs
xs″ ← shuffle xs′
pure (x′ ∷ xs″)
So how do we produce random Fin n
s? @mudri's comment points us to Data.Nat.PseudoRandom.LCG
which suggests using step
with a state of type ℕ
, and using simple remainder by n
to produce a Fin n
:
module ShuffleState (step : ℕ → ℕ) where
open import Category.Monad.State
open RawMonadState (StateMonadState ℕ)
randomℕ : State ℕ ℕ
randomℕ = modify step ⊛> get
open import Data.Nat.DivMod
randomFin : ∀ {n} → State ℕ (Fin (suc n))
randomFin = (_mod _) <$> randomℕ
open Shuffle monad randomFin public
Example usage:
module _ where
open import Data.Nat.PseudoRandom.LCG
open ShuffleState (step glibc)
test : Vec (Fin 10) 10
test = proj₁ (shuffle (allFin 10) 0)
Note that this doesn't prove that the output is indeed a permutation of the input. If you need that, I think it's easier to change shuffle
and swap
to produce a permutation witness (e.g. like this) instead of operating on the input vector directly and proving it externally.
Edited to add:
In Idris 2 at least, we can prove that shuffle
is linear in the shuffled vector by just using linear types where they matter:
import Data.Vect
import Data.Fin
swap : Fin (S n) -> (1 xs : Vect (S n) a) -> Vect (S n) a
swap FZ (x0 :: xs) = (x0 :: xs)
swap (FS i) (x0 :: xs) = go i xs
where
go : Fin k -> Vect k a -> Vect (S k) a
go FZ (x :: xs) = x :: x0 :: xs
go (FS i) (x :: xs) =
let (x' :: xs') = go i xs in
x' :: x :: xs'
shuffle: Monad m => (randomFin : {k : _} -> m (Fin (S k))) => {n : _} -> (1 xs : Vect n a) -> m (Vect n a)
shuffle [] = pure []
shuffle (x::xs) = do
k <- randomFin
let (x' :: xs') = swap k (x :: xs)
xs'' <- shuffle xs'
pure (x' :: xs'')
Note the type of xs
in the signature of shuffle
. I think parametricity + linearity together here should be sufficiently convincing that shuffle
indeed shuffles its input.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论