rodrigocfd
rodrigocfd

Reputation: 8048

How to know if all slice elements are equal, and if so, return a reference to the first one?

Given any slice, for example:

let words = &["one", "one", "one", "two"];

How to know if all elements are the same?

Even further, if all elements are the same, how to return a reference to the first one?

Essentially, I'm trying to write a function like:

fn are_all_elements_equal<T>(elems: &[T]) -> Option<&T> {
    // ... ?
}

Upvotes: 7

Views: 4786

Answers (5)

Sven Marnach
Sven Marnach

Reputation: 602315

I think this is a nice use case for subslice patterns:

pub fn are_all_elements_equal<T: PartialEq>(elems: &[T]) -> Option<&T> {
    match elems {
        [head, tail @ ..] => tail.iter().all(|x| x == head).then(|| head),
        [] => None,
    }
}

Upvotes: 11

Filipe Rodrigues
Filipe Rodrigues

Reputation: 2197

As an extension to the answers already posted, you can also make it generic over anything that can be iterated:

pub fn iter_all_eq<T: PartialEq>(iter: impl IntoIterator<Item = T>) -> Option<T> {
    let mut iter = iter.into_iter();
    let first = iter.next()?;
    iter.all(|elem| elem == first).then(|| first)
}

fn main() {
    println!("{:?}", iter_all_eq(&[1, 1, 1]));
    println!("{:?}", iter_all_eq(&[1, 2, 1]));
    println!("{:?}", iter_all_eq(&["abc", "abc", "abc", "abc"]));
}

Playground

Upvotes: 4

Herohtar
Herohtar

Reputation: 5613

It's fairly simple to do using the available built-in functions:

fn check_all<T: Eq>(items: &[T]) -> Option<&T> {
    match items.is_empty() {
        true => None,
        false => items.windows(2).all(|a| a[0] == a[1]).then(|| &items[0])
    }
}

Playground link

  • .windows(2) gives you an iterator containing overlapping pairs of elements.
  • .all(|a| a[0] == a[1]) compares the two elements from each window
  • .then(|| &items[0]) returns an Option containing a reference to the first element if the previous .all() returns true, otherwise it returns None
  • The match items.is_empty() is required because .all() will also return true if the slice is empty, which will result in a panic at items[0]

Note that since the comparison used in .all() could result in the same value being compared to itself, you need to constrain T to Eq, per this answer.

Upvotes: 1

user4815162342
user4815162342

Reputation: 155286

An elegant way to do this is using tuple_windows from the itertools crate:

use itertools::Itertools;

pub fn are_all_elements_equal<T: Eq>(elems: &[T]) -> Option<&T> {
    elems.iter().tuple_windows().all(|(a, b)| a == b).then(|| &elems[0])
}

Note that this will panic on an empty slice. To handle the empty slice, you need to explicitly return None if elems.is_empty().

Upvotes: 0

cadolphs
cadolphs

Reputation: 9647

I'd use .all: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all

First, if the slice is empty, just return None.

Then grab yourself an iterator over the rest of the slice, and use the .all function to check that the element equals the first element that you just grabbed. If that returns true, return your Some(first_element)

Upvotes: 6

Related Questions