October 31st, 2024

Polyglot Maxxie and Minnie

The blog post details the "Maxxie and Minnie" programming challenge, focusing on finding the largest and smallest numbers from digit swaps, with solutions in various programming languages and reflections on learning experiences.

Read original articleLink Icon
Polyglot Maxxie and Minnie

The blog post discusses a programming challenge called "Maxxie and Minnie," which involves finding the largest and smallest numbers that can be formed by swapping two digits of a given integer. The author, Jonathan Carroll, explores solutions in various programming languages, including R, APL, Julia, Haskell, Python, and Rust. The approach involves splitting the number into its digits, generating all possible pairs of indices for swapping, applying the swaps, and filtering out invalid results (like those starting with zero). Each language's solution is presented, highlighting unique features and challenges faced during implementation. The author emphasizes the learning experience gained from comparing different programming paradigms and syntaxes while solving the same problem.

- The challenge involves finding the largest and smallest numbers from digit swaps.

- Solutions were implemented in multiple programming languages for comparison.

- The approach includes generating all possible swaps and filtering invalid results.

- The author reflects on the learning experience from different programming paradigms.

- Each language's solution showcases its unique features and syntax.

Link Icon 1 comments
By @lifthrasiir - 5 months
As the author has correctly wondered, the Rust solution can be made much simpler especially when itertools are available (playground [1]):

[1] https://play.rust-lang.org/?version=stable&mode=debug&editio...

    fn from_digits(v: Vec<u8>) -> u32 {
        v.into_iter()
            .map(|x| (x + b'0') as char)
            .collect::<String>()
            .parse()
            .unwrap()
    }
    
    fn maxmin(num: u32) -> (u32, u32) {
        let num = num.to_string();
        let n = num.len();
        let mut numv: Vec<u8> = num.chars().map(|c| c as u8 - b'0').collect();
        let mut new: Vec<Vec<u8>> = Vec::new();
        new.push(numv.clone());
        for (a, b) in (0..n).tuple_combinations() {
            numv.swap(a, b);
            new.push(numv.clone());
        }
        let (min, max) = new
            .into_iter()
            .filter(|x| x[0] != 0)
            .minmax()
            .into_option()
            .unwrap();
        (from_digits(min), from_digits(max))
    }
The first and foremost, `Itertools::minmax` is a thing. So the entire min/max thing can be collapsed into a single method call. Since it naturally operates on an iterator, which is inherently lazily evaluated, it makes sense to make the whole thing amenable to be an iterator.

I tried to be faithful enough to the original solution, because there are many corners to cut if you know what you're looking for. It is possible to generate every number on demand without `new`, but I stopped here for the clarity. I intentionally retained the conversion between ASCII digits and actual digits to be faithful to the original approach, but that can be also eliminated easily. I did make use of the fact that all numeric strings contain ASCII digits b'0' through b'9', in order to show that vectors can be lexicographically compared just like strings.