【问题标题】:Inner join two `HashMap`s in Rust在 Rust 中内连接两个 `HashMap`
【发布时间】:2021-06-05 22:36:32
【问题描述】:

假设我们有两个std::collections::HashMap-s,比如HashMap<K, V1>HashMap<K, V2>,还有一个函数Fn(V1, V2) -> R

如何对这些哈希映射执行内部联接,以便在它们的共享密钥上获得HashMap<K, R>?这是一个reference implementation 来说明我的意思:

use std::collections::HashMap;

fn main() {
    let mut map_a = HashMap::<&str, i32>::new();
    map_a.insert("foo", 1);
    map_a.insert("bar", 2);
    map_a.insert("qux", 3);
    map_a.insert("quux", 4);
    
    let mut map_b = HashMap::<&str, i32>::new();
    map_b.insert("foo", 5);
    map_b.insert("bar", 6);
    map_b.insert("quuux", 7);
    map_b.insert("quuuux", 8);
    
    // To keep it simple I'm just combining respective values into a tuple:
    let joined_map = map_a
        .into_iter()
        .filter_map(|(key, value_a)| map_b.remove(key).map(|value_b| (key, (value_a, value_b))))
        .collect::<HashMap<&str, (i32, i32)>>();
        
    println!("{:?}", joined_map);
}

预期的输出是:

{"foo": (1, 5), "bar": (2, 6)}

这很好用,我可以在utils.rs 中使其通用,但我在标准库或crates.io 上找不到它的现有实现感觉不太对劲。此外,不执行sort-merge join 似乎不是最理想的,因为我们已经在HashMap 下对哈希码进行了排序。我在监督什么吗?或者只是太挑剔了,因为无论如何查找都是O(1)

【问题讨论】:

  • HashMap 是无序的,因此要进行排序合并,您必须先对两者进行排序。

标签: join rust hashmap inner-join


【解决方案1】:

正如您链接的答案所提到的,对于没有固有顺序的哈希表,没有办法更有效地做到这一点。 (使用对象的哈希码没有帮助,因为除非您使用了备用哈希,每个 HashMap 都使用不同的哈希函数, 以防止故意提交的拒绝服务攻击冲突的哈希键。)

您可以对算法进行一项高级改进:迭代两个哈希表中较小的,因为这需要最少的查找。


如果你有一个排序的集合,比如BTreeMap,那么itertools::Itertools::merge_join_by可以帮你实现排序数据的合并:

use std::collections::BTreeMap;
use itertools::{Itertools, EitherOrBoth};

fn main() {
    let mut map_a = BTreeMap::<&str, i32>::new();
    map_a.insert("foo", 1);
    map_a.insert("bar", 2);
    map_a.insert("qux", 3);
    map_a.insert("quux", 4);
    
    let mut map_b = BTreeMap::<&str, i32>::new();
    map_b.insert("foo", 5);
    map_b.insert("bar", 6);
    map_b.insert("quuux", 7);
    map_b.insert("quuuux", 8);
    
    let joined_map = map_a
        .into_iter()
        .merge_join_by(map_b, |(key_a, _), (key_b, _)| Ord::cmp(key_a, key_b))
        .filter_map(|elem| match elem {
            // Keep only paired elements, discard all others (making this an inner join).
            EitherOrBoth::Both((k, val_a), (_, val_b)) => Some((k, (val_a, val_b))),
            _ => None,
        })
        .collect::<BTreeMap<&str, (i32, i32)>>();
        
    println!("{:?}", joined_map);
}

这是对单个查找的性能改进,如果映射大小相当,因为BTreeMap 的查找是 O(log(n)),所以整个连接算法将是 O(n log(n) ) 天真,而是使用合并的 O(n),其中 n 是两个映射的 最大 大小。

另一方面,如果其中一个映射要小得多,那么迭代那个较小的映射是一个更好的算法,因为如果 n 小于 m,O(n log(n)) 比 O(m) 好因数 log(n)。


总之,有不止一种可能的算法,哪一种最好取决于集合类型和相对集合大小。而且您可以使用排序来加速连接,但只有当您已经具有排序时才有用,而 hash 映射中没有排序。

【讨论】:

    猜你喜欢
    • 2018-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-10
    相关资源
    最近更新 更多