Warm tip: This article is reproduced from stackoverflow.com, please click
enums rust

How to select a random enum value with some limitations

发布于 2020-04-09 23:00:56

Context

  • An object is moving in one direction: Up, Right, Down, Left.
  • The next direction will be chosen randomly.
  • The next possible direction cannot be backwards.

Ex:

If it is going Right, could go Up, Right or Down, but not Left.

Current direction and possible next directions

Enum

There is a simple enum for directions:

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

Function signature

I would say that this is the function signature that accomplishes the task:

fn next_direction(current_dir: Direction) -> Direction

Current implementation

This is my current implementation:

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,
    }
}

Question

Could this function be written in a clearer, simpler, more efficient way?

I would say that readability is a plus, so a one liner or code golf implementation could not be optimal.

I have already found this related question: How do I choose a random value from an enum?.

Thank you =)

Questioner
Ignacio Lago
Viewed
163
Boiethios 2020-02-01 22:09

You can write the possible directions for each case by hand:

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()
    }
}

Of course, if your enum has a lot of variants, it's better to have a more generic solution:

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()
    }
}

Note that if you want better performances or flexibility, you may pass the random number generator as a parameter:

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

    *next.unwrap()
}