【问题标题】:How to use a struct's member as its own key when inserting the struct into a map without duplicating it?将结构插入地图时如何使用结构成员作为自己的键而不复制它?
【发布时间】:2016-12-08 09:25:08
【问题描述】:

是否可以将结构插入到映射中,其中键由所插入的值拥有?

在 C 中使用哈希映射时,这是我习惯做的事情。

伪代码示例:

struct MyStruct {
    pub map: BTreeMap<&String, StructThatContainsString>,
    // XXX            ^ Rust wants lifetime specified here!
}

struct StructThatContainsString {
    id: String,
    other_data: u32,
}

fn my_fn() {
    let ms = MyStruct { map: BTreeMap::new() };

    let item = StructThatContainsString {
        id: "Some Key".to_string(),
        other_data: 0,
    }

    ms.insert(&item.id, item);
}

这种情况如何正确处理?


  • 如果这不可能,是否可以反过来做,其中值包含对 String 的键的引用?

  • 另一种方法是使用set 而不是map,然后将整个struct 存储为键,但在比较时仅使用其中一个值会起作用,但如果您想在其他情况下比较 struct,可能会适得其反)。

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    它不适用于普通引用:

    let item = StructThatContainsString {
        id: "Some Key".to_string(),
        other_data: 0,
    }
    
    ms.insert(&item.id, item);
    

    item移动到地图中,因此不会有任何待处理的借用/引用。

    此外,get_mut() 之类的方法会变得危险或不可能,因为它会让您修改具有未完成引用的项目。

    假设想要这样做的原因是为了节省空间,显而易见的选择是:

    • 从值结构中取出键。如果你同时需要它,你要么在 map 中查找 key 时得到它,要么迭代器同时包含 key 和 value:

      struct OnlyKey {
          id: String,
      }
      
      struct OnlyValue {
          other_data: u32,
      }
      

      这可以通过适当的方法来清理,以拆分/重新组合各个部分。

    • 使用Rc 之类的东西作为值的关键部分。 Rc&lt;T&gt; 实现 OrdBTreeMap 需要)如果 T 实现。

      struct StructThatContainsString {
          id: Rc<String>,
          other_data: u32,
      }
      

    【讨论】:

    • 除了将值移动到地图中之外,值可能会在任何时候添加或删除值时移动到地图内部
    【解决方案2】:

    使用结构的单个成员作为映射中的键(原则上)可以通过使用具有零开销包装结构的集合来完成,该结构仅用于覆盖实现。

    • 覆盖Ord, Eq, PartialEq, PartialOrd
      以控制它在集合中的顺序。
    • 覆盖Borrow,因此BTreeSet.get(..) 可以采用用于排序的类型,而不是整个结构。

    • 这种方法的一个缺点是在将结构添加到集合中时需要将其与容器一起包装。

    这是一个工作示例:

    use ::std::collections::BTreeSet;
    
    #[derive(Debug)]
    pub struct MyItem {
        id: String,
        num: i64,
    }
    
    mod my_item_ord {
        use super::MyItem;
    
        #[derive(Debug)]
        pub struct MyItem_Ord(pub MyItem);
    
        use ::std::cmp::{
            PartialEq,
            Eq,
            Ord,
            Ordering,
        };
        use ::std::borrow::Borrow;
    
        impl PartialEq for MyItem_Ord {
            fn eq(&self, other: &Self) -> bool {
                return self.0.id.eq(&other.0.id);
            }
    
        }
        impl PartialOrd for MyItem_Ord {
            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                return self.0.id.partial_cmp(&other.0.id);
            }
        }
        impl Eq for MyItem_Ord {}
        impl Ord for MyItem_Ord {
            fn cmp(&self, other: &Self) -> Ordering {
                return self.0.id.cmp(&other.0.id);
            }
        }
        impl Borrow<str> for MyItem_Ord {
            fn borrow(&self) -> &str {
                return &self.0.id;
            }
        }
    }
    
    
    fn main() {
        use my_item_ord::MyItem_Ord;
    
        let mut c: BTreeSet<MyItem_Ord> = BTreeSet::new();
    
        c.insert(MyItem_Ord(MyItem { id: "Zombie".to_string(), num: 21, }));
        c.insert(MyItem_Ord(MyItem { id: "Hello".to_string(), num: 1, }));
        c.insert(MyItem_Ord(MyItem { id: "World".to_string(), num: 22, }));
        c.insert(MyItem_Ord(MyItem { id: "The".to_string(), num: 11,  }));
        c.insert(MyItem_Ord(MyItem { id: "Brown".to_string(), num: 33, }));
        c.insert(MyItem_Ord(MyItem { id: "Fox".to_string(), num: 99, }));
    
        for i in &c {
            println!("{:?}", i);
        }
    
        // Typical '.get()', too verbose needs an entire struct.
        println!("lookup: {:?}", c.get(&MyItem_Ord(MyItem { id: "Zombie".to_string(), num: -1, })));
        //                                                                            ^^^^^^^ ignored
    
        // Fancy '.get()' using only string, allowed because 'Borrow<str>' is implemented.
        println!("lookup: {:?}", c.get("Zombie"));
    
        println!("done!");
    }
    

    为了避免必须手动定义这些,可以将其包装成一个宏:

    ///
    /// Macro to create a container type to be used in a 'BTreeSet' or ordered types
    /// to behave like a map where a key in the struct is used for the key.
    ///
    /// For example, data in a set may have a unique identifier which
    /// can be used in the struct as well as a key for it's use in the set.
    ///
    ///
    /// ```
    /// // Defines 'MyTypeOrd', a container type for existing struct,
    /// // using MyType.uuid is used as the key.
    /// container_order_by_member_impl(MyTypeOrd, MyType, uuid);
    /// ```
    ///
    /// See: http://stackoverflow.com/questions/41035869
    
    #[macro_export]
    macro_rules! container_type_order_by_member_struct_impl {
        ($t_ord:ident, $t_base:ty, $t_member:ident) => {
            /// Caller must define the struct, see: container_type_order_by_member_impl
            // pub struct $t_ord(pub $t_base);
            impl PartialEq for $t_ord {
                fn eq(&self, other: &Self) -> bool {
                    return (self.0).$t_member.eq(&(other.0).$t_member);
                }
            }
            impl PartialOrd for $t_ord {
                fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
                    return (self.0).$t_member.partial_cmp(&(other.0).$t_member);
                }
            }
            impl Eq for $t_ord {}
            impl Ord for $t_ord {
                fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                    return (self.0).$t_member.cmp(&(other.0).$t_member);
                }
            }
            impl ::std::borrow::Borrow<str> for $t_ord {
                fn borrow(&self) -> &str {
                    return &(self.0).$t_member;
                }
            }
        }
    }
    
    /// Macro that also defines structs.
    #[macro_export]
    macro_rules! container_type_order_by_member_impl {
        (pub $t_ord:ident, $t_base:ty, $t_member:ident) => {
            pub struct $t_ord(pub $t_base);
            container_type_order_by_member_struct_impl!($t_ord, $t_base, $t_member);
        };
        ($t_ord:ident, $t_base:ty, $t_member:ident) => {
            struct $t_ord(pub $t_base);
            container_type_order_by_member_struct_impl!($t_ord, $t_base, $t_member);
        };
    }
    

    【讨论】:

      猜你喜欢
      • 2011-10-30
      • 1970-01-01
      • 1970-01-01
      • 2014-05-28
      • 1970-01-01
      • 2022-06-10
      • 2015-12-20
      • 2017-08-31
      • 2019-04-15
      相关资源
      最近更新 更多