Как выбрать случайное значение enum с некоторыми ограничениями - PullRequest
2 голосов
/ 01 февраля 2020

Контекст

  • Объект движется в одном направлении: Вверх, вправо, вниз, влево .
  • Следующее направление будет выбрано случайным образом.
  • Следующее возможное направление не может быть задом наперед.

Пример:

Если оно идет Вправо , может go Вверх, вправо или Вниз , но не Влево.

Current direction and possible next directions

Enum

Существует простое перечисление для указаний:

#[derive(Debug, PartialEq)]
enum Direction {
  Up,
  Right,
  Down,
  Left,
}

Сигнатура функции

Я бы сказал, что это сигнатура функции, которая выполняет задачу:

fn next_direction(current_dir: Direction) -> Direction

Текущая реализация

Это моя текущая реализация:

use rand::prelude::*;

fn next_direction(current_dir: Direction) -> Direction {
    let mut rng = thread_rng();
    // Possible next direction
    let next_dir_iter = [
        Direction::Up,
        Direction::Down,
        Direction::Left,
        Direction::Right,
    ]
    .iter()
    .filter(|&dir| match (current_dir, dir) {
        (Direction::Up, Direction::Down) => false,
        (Direction::Down, Direction::Up) => false,
        (Direction::Left, Direction::Right) => false,
        (Direction::Right, Direction::Left) => false,
        (_, _) => true,
    });
    // Choose one
    let dir = next_dir_iter.choose(&mut rng).unwrap();
    // Return Direction instead of &Direction
    match dir {
        Direction::Up => Direction::Up,
        Direction::Down => Direction::Down,
        Direction::Left => Direction::Left,
        Direction::Right => Direction::Right,
    }
}

Вопрос

Может ли эта функция быть написана яснее, проще, эффективнее? ?

Я бы сказал, что удобочитаемость - это плюс, поэтому реализация кода с одним вкладышем или кодом не может быть оптимальной.

Я уже нашел этот связанный вопрос: Как Я выбираю случайное значение из перечисления? .

Спасибо =)

1 Ответ

3 голосов
/ 01 февраля 2020

Вы можете написать возможные направления для каждого случая вручную:

use rand::prelude::*;
use Direction::*;

#[derive(Debug, PartialEq, Copy, Clone)]
enum Direction {
    Up,
    Right,
    Down,
    Left,
}

impl Direction {
    fn next_random(self) -> Self {
        match self {
            Up => [Up, Left, Right],
            Down => [Down, Left, Right],
            Left => [Up, Down, Left],
            Right => [Up, Down, Right],
        }
        .choose(&mut thread_rng())
        .copied()
        .unwrap()
    }
}

Конечно, если у вашего enum есть много вариантов, лучше иметь более общее c решение:

impl Direction {
    fn all() -> &'static [Self] {
        &[Up, Down, Left, Right]
    }

    fn opposite(self) -> Self {
        match self {
            Up => Down,
            Down => Up,
            Left => Right,
            Right => Left,
        }
    }

    fn next_random(self) -> Self {
        let next = Self::all()
            .iter()
            .filter(|&&d| d != self.opposite())
            .choose(&mut thread_rng());

        *next.unwrap()
    }
}

Обратите внимание, что если вы хотите повысить производительность или гибкость, вы можете передать генератор случайных чисел в качестве параметра:

fn next_random(self, rng: &mut impl Rng) -> Self {
    let next = Self::all()
        .iter()
        .filter(|&&d| d != self.opposite())
        .choose(rng);

    *next.unwrap()
}
...