Generate array of unique hashmaps

I want to generate all possible pairs without repeated elements, so that each combination will be represented as the hashmap with each unique value for each key.

An idea is to transform the following inputs:

context: {
       "environment": [
           "dev", 
           "production", 
           "staging"
       ],
       "filename": [
            "info.txt",
            "README.md"
       ],
       "folder": "my_name"
}
variables: {"environment", "filename", "folder"}

Into unique combinations like this:

[
    {"environment": "dev", "filename": "info.txt", "folder": "my_name"},
    {"environment": "dev", "filename": "README", "folder": "my_name"},
    {"environment": "staging", "filename": "info.txt", "folder": "my_name"}
    ...
]

So, I've tried to reach those ideas with the following piece of code:

use std::collections::{HashMap, BTreeSet};

use itertools::iproduct;
use serde_json::{json, Value as SerdeValue};

pub fn generate_subcontexts(
    context: &Box<SerdeValue>,
    variables: &BTreeSet<String>
) -> Vec<SerdeValue> {
    let data: Vec<HashMap<String, SerdeValue>> = variables
        .iter()
        .filter(|variable_name| {
            let value = match context.get(*variable_name) {
                Some(value) => value,
                None => return false,
            };

            match value {
                SerdeValue::String(_) => true,
                SerdeValue::Array(_) => true,
                _ => false,
            }
        })
        .map(|variable_name| {
            let mut entry: HashMap<String, SerdeValue> = HashMap::new();
            entry.insert(variable_name.clone(), context.get(variable_name).unwrap().clone());
            entry
        })
        .collect();

    iproduct!(data)
        .map(|combination| json!(combination))
        .collect()
}

However, instead of it, I'm getting only the serialized into JSON inputs (which are hashmaps). Anyone can guide me how to reorganize/fix the piece with iproduct! macro, so I could get the desired result?

I don't understand this part. What kind of inputs? And if you want hashmaps, why is it a problem that you are getting hashmaps?

BTW regarding the iproduct part, I'm pretty sure that's not how this macro works. The product of a single iterator is just itself. You'll want to expand each desired iterator-to-be-multiplied as a separate argument for the iproduct macro – which makes it practically impossible to generalize this approach to an arbitrary number of keys ("variables").

I don't understand this part. What kind of inputs? And if you want hashmaps, why is it a problem that you are getting hashmaps?

I meant that instead of combinations that must be saved in each hashmap as JSON, I'm getting only the serialized data variable instead.

BTW regarding the iproduct part, I'm pretty sure that's not how this macro works. The product of a single iterator is just itself. You'll want to expand each desired iterator-to-be-multiplied as a separate argument for the iproduct macro – which makes it practically impossible to generalize this approach to an arbitrary number of keys ("variables").

I expected that the iproduct! macro would take the pairs from the data variable and turn them into unique pairs, like it does for when you're working with a multiple vectors of the same type. So... I guess I will need to implement my own generators (recursive?) for this case?

I think yes, because you have a dynamic number of iterators to multiply. I don't know how it could (easily) be implemented without recursion, as it's effectively equivalent to generating a variable number of nested loops.

use std::collections::HashMap;
use std::fmt::Display;

macro_rules! hashmap {
    ( $( $key:tt: $value:expr ),* ) => {{
        let mut _m_ = HashMap::new();
        $(_m_.insert($key.into(),$value.into());)*
        _m_
    }}
}

fn cartesian_product<T: Clone>(a: &[Vec<T>]) -> Vec<Vec<T>> {
    if a.len()==0 {
        return vec![Vec::new()];
    } else {
        let mut v: Vec<Vec<T>> = Vec::new();
        for x in &a[0] {
            for t in cartesian_product(&a[1..]) {
                v.push([x.clone()].iter().cloned().chain(t).collect());
            }
        }
        return v;
    }
}

fn format_sorted<T: Display>(m: &HashMap<&str,T>) -> String {
    let mut a: Vec<_> = m.iter().collect();
    a.sort_by_key(|t| t.0);
    let mut buffer = String::from("{");
    let mut first = true;
    for (key, value) in a {
        if first {first = false;} else {buffer.push_str(", ");}
        buffer.push_str(&format!("{}: {}",key,value));
    }
    buffer.push_str("}");
    return buffer;
}

fn generate_subcontexts<'a, T: Clone>(context: &HashMap<&'a str,Vec<T>>)
-> Vec<HashMap<&'a str,T>>
{
    let mut items: Vec<_> = context.iter().collect();
    items.sort_by_key(|t| t.0);
    let keys: Vec<&str> = items.iter().map(|t| *t.0).collect();
    let values: Vec<Vec<T>> = items.iter().map(|t| t.1.clone()).collect();
    let mut subcontexts: Vec<HashMap<&str,T>> = Vec::new();
    for t in cartesian_product(&values[..]) {
        subcontexts.push(keys.iter().cloned().zip(t).collect());
    }
    return subcontexts;
}

fn main() {
    let context: HashMap<&str,Vec<&str>> = hashmap!{
        "environment": vec!["dev", "production", "staging"],
        "filename": vec!["info.txt", "README.md"],
        "folder": vec!["my_name"]
    };
    for x in &generate_subcontexts(&context) {
        println!("{}",format_sorted(x));
    }
}

I solved this problem with the following piece of code and it would be great if someone could help with making it much more readable or in the rustacean way:

pub fn generate_subcontexts(
    context: &Box<SerdeValue>,
    variables: &BTreeSet<String>
) -> Vec<SerdeValue> {
    let mut data: Vec<HashMap<String, SerdeValue>> = variables
        .iter()
        .filter(|variable_name| {
            let value = match context.get(*variable_name) {
                Some(value) => value,
                None => return false,
            };

            match value {
                SerdeValue::String(_) => true,
                SerdeValue::Array(_) => true,
                _ => false,
            }
        })
        .map(|variable_name| {
            let mut entry: HashMap<String, SerdeValue> = HashMap::new();
            entry.insert(variable_name.clone(), context.get(variable_name).unwrap().clone());
            entry
        })
        .collect();

    get_combinations(&mut data)
        .iter()
        .map(|combination| json!(combination))
        .collect()
}

fn get_combinations(
    data: &mut Vec<HashMap<String, SerdeValue>>
) -> Vec<HashMap<String, String>> {
    if data.is_empty() {
        return Vec::new()
    }

    let mut combinations: Vec<HashMap<String, String>> = Vec::new();
    while !data.is_empty() {
        let variable_data = data.pop().unwrap();
        let mut pairs: Vec<HashMap<String, String>> = Vec::new();
        for (key, value) in variable_data {
            match value {
                SerdeValue::String(value) => {
                    let mut hashmap = HashMap::new();
                    hashmap.insert(key.clone(), value.clone());
                    pairs.push(hashmap);
                    ()
                },
                SerdeValue::Array(values) => {
                    for value in values {
                        let stringified_value = match value {
                            SerdeValue::String(val) => val.clone(),
                            _ => String::from("unsupported type")
                        };

                        let mut hashmap = HashMap::new();
                        hashmap.insert(key.clone(), stringified_value);
                        pairs.push(hashmap);
                    }
                    ()
                },
                _ => ()
            }
        }

        combinations = cartesian_hashmap_product(&combinations, &pairs);
    }

    combinations
}

fn cartesian_hashmap_product(
    current_pairs: &Vec<HashMap<String, String>>,
    pairs: &Vec<HashMap<String, String>>,
) -> Vec<HashMap<String, String>>{
    if current_pairs.is_empty() {
        return pairs.clone()
    }

    let mut cartesian_product = Vec::new();
    for current_pair in current_pairs {
        for addition in pairs {
            let new_pair = current_pair.clone()
                .into_iter()
                .chain(addition.clone())
                .collect();
            cartesian_product.push(new_pair)
        }
    }
    cartesian_product
}

One of thoughts is somehow to reorganize the code with extracting and converting SerdeValue into strings. The string/number/bool/array types are acceptable, where's the others has to be skipped.

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