Is this the FP way to do this?

I wrote this code for the ETL exercise on exercism.io. The basic idea is to take a BTreeMap with u32 keys and Vec<char> values and transform it into a BTreeMap with char keys and u32 values.

Original data:

  • 1 point: "A", "E", "I", "O", "U", "L", "N", "R", "S", "T",
  • 2 points: "D", "G",
  • 3 points: "B", "C", "M", "P",
  • 4 points: "F", "H", "V", "W", "Y",
  • 5 points: "K",
  • 8 points: "J", "X",
  • 10 points: "Q", "Z",

What I want after transformation:

  • "a" is worth 1 point.
  • "b" is worth 3 points.
  • "c" is worth 3 points.
  • "d" is worth 2 points.
  • Etc.

I first did it in an imperative way like so: Playground

use std::collections::BTreeMap;

pub fn transform(h: &BTreeMap<i32, Vec<char>>) -> BTreeMap<char, i32> {
    let mut result = BTreeMap::new();
    
    for (&key, values) in h {
        for v in values {
            result.insert(v.to_ascii_lowercase(), key);
        }
    }
    result
}

Then I translated it to a more functional way: Playground

use std::collections::BTreeMap;

pub fn transform(h: &BTreeMap<i32, Vec<char>>) -> BTreeMap<char, i32> {
    h.iter().fold(BTreeMap::new(), |mut acc, (&key, values)| {
        values.iter().map(char::to_ascii_lowercase).for_each(|v| { acc.insert(v, key); });
        acc
    })
}

Now, my question is, is the functional version the clearest I could've done, or is there a clearer functional way to do this?

I would use flat_map.

pub fn transform(h: &BTreeMap<i32, Vec<char>>) -> BTreeMap<char, i32> {
    h.iter().flat_map(|(&key, values)| {
        values.iter().map(move |c| (c.to_ascii_lowercase(), key))
    }).collect()
}
3 Likes

Thanks, looks much better!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.