問題 24

(初級 🌟) ロト: 1からMまでの集合からN個の異なるランダムな数を抽出せよ。

import Lean

/-- 1からnまでの自然数のリストを生成する -/
def List.nrange (n : Nat) : List Nat :=
  match n with
  | 0 => []
  | 1 => [1]
  | n + 1 => nrange n ++ [n + 1]

#guard List.nrange 5 == [1, 2, 3, 4, 5]


def diffSelect (count range : Nat) : IO (List Nat) := do
  if count > range then
    IO.println s!"can't draw {count} different numbers from 1..{range}"
    return []

  let mut univ := List.nrange range
  let mut result : List Nat := []
  for _ in [0 : count] do
    let (element, rest) ← extractOne univ
    if let some e := element then
      univ := rest
      result := e :: result

  return result
where
  extractOne (univ : List Nat) : IO (Option Nat × List Nat) := do
    if univ == [] then
      return (none, [])

    let index ← IO.rand 0 (univ.length - 1)
    let element := univ[index]!
    let rest := univ.take index ++ univ.drop (index + 1)
    return (element, rest)

-- テスト例(#eval で確認)
def runTest (count range : Nat) : IO Unit := do
  let result ← diffSelect count range
  let check := result.eraseDups.length == count
    |> (· && result.all (List.nrange range).contains)
  if check then
    IO.println "ok!"
  else
    throw <| .userError s!"failed: diffSelect {count} {range} = {result}"

#eval runTest 3 3
#eval runTest 1 1
#eval runTest 2 2
#eval runTest 6 49
#eval runTest 1998 1999
#eval runTest 5668 5998