2

Context

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

Example

If it is going Right, it 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,
    }
}

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 found How do I choose a random value from an enum?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ignacio Lago
  • 2,432
  • 1
  • 27
  • 33

1 Answers1

3

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()
}
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • 1
    All RNGs are assumed to deliver random bits, and the `choose()` method will always select a random element uniformly. Passing in the RNG allows to use a different RNG, e.g. a faster one that isn't cryptographically secure, or one with some know random state so you can reproduce your results. It also allows to reuse the RNG across many calls of the method, so you don't need to call `thread_rng()` every time. – Sven Marnach Feb 01 '20 at 12:44
  • @SvenMarnach You comment desserves to be in a separate Q&A – Boiethios Feb 01 '20 at 14:09
  • I know I got a bit off topic. :) I just couldn't help replying to your side note about the non-uniform distribution. – Sven Marnach Feb 01 '20 at 17:17