// Copyright 2013 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! An unordered map and set type implemented as hash tables //! //! The tables use a keyed hash with new random keys generated for each container, so the ordering //! of a set of keys in a hash table is randomized. use container::{Container, Mutable, Map, Set}; use cmp::{Eq, Equiv}; use hash::Hash; use old_iter::BaseIter; use old_iter; use option::{None, Option, Some}; use rand::RngUtil; use rand; use uint; use vec; use kinds::Copy; use util::{replace, unreachable}; static INITIAL_CAPACITY: uint = 32u; // 2^5 struct Bucket { hash: uint, key: K, value: V, } pub struct HashMap { priv k0: u64, priv k1: u64, priv resize_at: uint, priv size: uint, priv buckets: ~[Option>], } // We could rewrite FoundEntry to have type Option<&Bucket> // which would be nifty enum SearchResult { FoundEntry(uint), FoundHole(uint), TableFull } #[inline(always)] fn resize_at(capacity: uint) -> uint { ((capacity as float) * 3. / 4.) as uint } pub fn linear_map_with_capacity( initial_capacity: uint) -> HashMap { let mut r = rand::task_rng(); linear_map_with_capacity_and_keys(r.gen(), r.gen(), initial_capacity) } fn linear_map_with_capacity_and_keys( k0: u64, k1: u64, initial_capacity: uint) -> HashMap { HashMap { k0: k0, k1: k1, resize_at: resize_at(initial_capacity), size: 0, buckets: vec::from_fn(initial_capacity, |_| None) } } priv impl HashMap { #[inline(always)] fn to_bucket(&self, h: uint) -> uint { // A good hash function with entropy spread over all of the // bits is assumed. SipHash is more than good enough. h % self.buckets.len() } #[inline(always)] fn next_bucket(&self, idx: uint, len_buckets: uint) -> uint { let n = (idx + 1) % len_buckets; debug!("next_bucket(%?, %?) = %?", idx, len_buckets, n); n } #[inline(always)] fn bucket_sequence(&self, hash: uint, op: &fn(uint) -> bool) -> bool { let start_idx = self.to_bucket(hash); let len_buckets = self.buckets.len(); let mut idx = start_idx; loop { if !op(idx) { return false; } idx = self.next_bucket(idx, len_buckets); if idx == start_idx { return true; } } } #[inline(always)] fn bucket_for_key(&self, k: &K) -> SearchResult { let hash = k.hash_keyed(self.k0, self.k1) as uint; self.bucket_for_key_with_hash(hash, k) } #[inline(always)] fn bucket_for_key_equiv>(&self, k: &Q) -> SearchResult { let hash = k.hash_keyed(self.k0, self.k1) as uint; self.bucket_for_key_with_hash_equiv(hash, k) } #[inline(always)] fn bucket_for_key_with_hash(&self, hash: uint, k: &K) -> SearchResult { for self.bucket_sequence(hash) |i| { match self.buckets[i] { Some(ref bkt) => if bkt.hash == hash && *k == bkt.key { return FoundEntry(i); }, None => return FoundHole(i) } } TableFull } #[inline(always)] fn bucket_for_key_with_hash_equiv>(&self, hash: uint, k: &Q) -> SearchResult { for self.bucket_sequence(hash) |i| { match self.buckets[i] { Some(ref bkt) => { if bkt.hash == hash && k.equiv(&bkt.key) { return FoundEntry(i); } }, None => return FoundHole(i) } } TableFull } /// Expand the capacity of the array to the next power of two /// and re-insert each of the existing buckets. #[inline(always)] fn expand(&mut self) { let new_capacity = self.buckets.len() * 2; self.resize(new_capacity); } /// Expands the capacity of the array and re-insert each of the /// existing buckets. fn resize(&mut self, new_capacity: uint) { self.resize_at = resize_at(new_capacity); let old_buckets = replace(&mut self.buckets, vec::from_fn(new_capacity, |_| None)); self.size = 0; do vec::consume(old_buckets) |_, bucket| { self.insert_opt_bucket(bucket); } } fn insert_opt_bucket(&mut self, bucket: Option>) { match bucket { Some(Bucket{hash: hash, key: key, value: value}) => { self.insert_internal(hash, key, value); } None => {} } } #[inline(always)] fn value_for_bucket<'a>(&'a self, idx: uint) -> &'a V { match self.buckets[idx] { Some(ref bkt) => &bkt.value, None => fail!("HashMap::find: internal logic error"), } } #[inline(always)] fn mut_value_for_bucket<'a>(&'a mut self, idx: uint) -> &'a mut V { match self.buckets[idx] { Some(ref mut bkt) => &mut bkt.value, None => unreachable() } } /// Inserts the key value pair into the buckets. /// Assumes that there will be a bucket. /// True if there was no previous entry with that key fn insert_internal(&mut self, hash: uint, k: K, v: V) -> Option { match self.bucket_for_key_with_hash(hash, &k) { TableFull => { fail!("Internal logic error"); } FoundHole(idx) => { debug!("insert fresh (%?->%?) at idx %?, hash %?", k, v, idx, hash); self.buckets[idx] = Some(Bucket{hash: hash, key: k, value: v}); self.size += 1; None } FoundEntry(idx) => { debug!("insert overwrite (%?->%?) at idx %?, hash %?", k, v, idx, hash); match self.buckets[idx] { None => { fail!("insert_internal: Internal logic error") } Some(ref mut b) => { b.hash = hash; b.key = k; Some(replace(&mut b.value, v)) } } } } } fn pop_internal(&mut self, hash: uint, k: &K) -> Option { // Removing from an open-addressed hashtable // is, well, painful. The problem is that // the entry may lie on the probe path for other // entries, so removing it would make you think that // those probe paths are empty. // // To address this we basically have to keep walking, // re-inserting entries we find until we reach an empty // bucket. We know we will eventually reach one because // we insert one ourselves at the beginning (the removed // entry). // // I found this explanation elucidating: // http://www.maths.lse.ac.uk/Courses/MA407/del-hash.pdf let mut idx = match self.bucket_for_key_with_hash(hash, k) { TableFull | FoundHole(_) => return None, FoundEntry(idx) => idx }; let len_buckets = self.buckets.len(); let bucket = replace(&mut self.buckets[idx], None); let value = match bucket { None => None, Some(Bucket{value, _}) => { Some(value) }, }; /* re-inserting buckets may cause changes in size, so remember what our new size is ahead of time before we start insertions */ let size = self.size - 1; idx = self.next_bucket(idx, len_buckets); while self.buckets[idx].is_some() { let bucket = replace(&mut self.buckets[idx], None); self.insert_opt_bucket(bucket); idx = self.next_bucket(idx, len_buckets); } self.size = size; value } fn search(&self, hash: uint, op: &fn(x: &Option>) -> bool) { let _ = self.bucket_sequence(hash, |i| op(&self.buckets[i])); } } impl Container for HashMap { /// Return the number of elements in the map fn len(&const self) -> uint { self.size } /// Return true if the map contains no elements fn is_empty(&const self) -> bool { self.len() == 0 } } impl Mutable for HashMap { /// Clear the map, removing all key-value pairs. fn clear(&mut self) { for uint::range(0, self.buckets.len()) |idx| { self.buckets[idx] = None; } self.size = 0; } } impl Map for HashMap { /// Return true if the map contains a value for the specified key fn contains_key(&self, k: &K) -> bool { match self.bucket_for_key(k) { FoundEntry(_) => {true} TableFull | FoundHole(_) => {false} } } /// Visit all key-value pairs fn each<'a>(&'a self, blk: &fn(&K, &'a V) -> bool) -> bool { for self.buckets.each |bucket| { for bucket.each |pair| { if !blk(&pair.key, &pair.value) { return false; } } } return true; } /// Visit all keys fn each_key(&self, blk: &fn(k: &K) -> bool) -> bool { self.each(|k, _| blk(k)) } /// Visit all values fn each_value<'a>(&'a self, blk: &fn(v: &'a V) -> bool) -> bool { self.each(|_, v| blk(v)) } /// Iterate over the map and mutate the contained values fn mutate_values(&mut self, blk: &fn(&K, &mut V) -> bool) -> bool { for uint::range(0, self.buckets.len()) |i| { match self.buckets[i] { Some(Bucket{key: ref key, value: ref mut value, _}) => { if !blk(key, value) { return false; } } None => () } } return true; } /// Return a reference to the value corresponding to the key fn find<'a>(&'a self, k: &K) -> Option<&'a V> { match self.bucket_for_key(k) { FoundEntry(idx) => Some(self.value_for_bucket(idx)), TableFull | FoundHole(_) => None, } } /// Return a mutable reference to the value corresponding to the key fn find_mut<'a>(&'a mut self, k: &K) -> Option<&'a mut V> { let idx = match self.bucket_for_key(k) { FoundEntry(idx) => idx, TableFull | FoundHole(_) => return None }; Some(self.mut_value_for_bucket(idx)) } /// Insert a key-value pair into the map. An existing value for a /// key is replaced by the new value. Return true if the key did /// not already exist in the map. fn insert(&mut self, k: K, v: V) -> bool { self.swap(k, v).is_none() } /// Remove a key-value pair from the map. Return true if the key /// was present in the map, otherwise false. fn remove(&mut self, k: &K) -> bool { self.pop(k).is_some() } /// Insert a key-value pair from the map. If the key already had a value /// present in the map, that value is returned. Otherwise None is returned. fn swap(&mut self, k: K, v: V) -> Option { // this could be faster. if self.size >= self.resize_at { // n.b.: We could also do this after searching, so // that we do not resize if this call to insert is // simply going to update a key in place. My sense // though is that it's worse to have to search through // buckets to find the right spot twice than to just // resize in this corner case. self.expand(); } let hash = k.hash_keyed(self.k0, self.k1) as uint; self.insert_internal(hash, k, v) } /// Removes a key from the map, returning the value at the key if the key /// was previously in the map. fn pop(&mut self, k: &K) -> Option { let hash = k.hash_keyed(self.k0, self.k1) as uint; self.pop_internal(hash, k) } } pub impl HashMap { /// Create an empty HashMap fn new() -> HashMap { HashMap::with_capacity(INITIAL_CAPACITY) } /// Create an empty HashMap with space for at least `n` elements in /// the hash table. fn with_capacity(capacity: uint) -> HashMap { linear_map_with_capacity(capacity) } /// Reserve space for at least `n` elements in the hash table. fn reserve_at_least(&mut self, n: uint) { if n > self.buckets.len() { let buckets = n * 4 / 3 + 1; self.resize(uint::next_power_of_two(buckets)); } } /// Return the value corresponding to the key in the map, or insert /// and return the value if it doesn't exist. fn find_or_insert<'a>(&'a mut self, k: K, v: V) -> &'a V { if self.size >= self.resize_at { // n.b.: We could also do this after searching, so // that we do not resize if this call to insert is // simply going to update a key in place. My sense // though is that it's worse to have to search through // buckets to find the right spot twice than to just // resize in this corner case. self.expand(); } let hash = k.hash_keyed(self.k0, self.k1) as uint; let idx = match self.bucket_for_key_with_hash(hash, &k) { TableFull => fail!("Internal logic error"), FoundEntry(idx) => idx, FoundHole(idx) => { self.buckets[idx] = Some(Bucket{hash: hash, key: k, value: v}); self.size += 1; idx }, }; self.value_for_bucket(idx) } /// Return the value corresponding to the key in the map, or create, /// insert, and return a new value if it doesn't exist. fn find_or_insert_with<'a>(&'a mut self, k: K, f: &fn(&K) -> V) -> &'a V { if self.size >= self.resize_at { // n.b.: We could also do this after searching, so // that we do not resize if this call to insert is // simply going to update a key in place. My sense // though is that it's worse to have to search through // buckets to find the right spot twice than to just // resize in this corner case. self.expand(); } let hash = k.hash_keyed(self.k0, self.k1) as uint; let idx = match self.bucket_for_key_with_hash(hash, &k) { TableFull => fail!("Internal logic error"), FoundEntry(idx) => idx, FoundHole(idx) => { let v = f(&k); self.buckets[idx] = Some(Bucket{hash: hash, key: k, value: v}); self.size += 1; idx }, }; self.value_for_bucket(idx) } fn consume(&mut self, f: &fn(K, V)) { let buckets = replace(&mut self.buckets, ~[]); self.size = 0; do vec::consume(buckets) |_, bucket| { match bucket { None => {}, Some(Bucket{key, value, _}) => { f(key, value) } } } } fn get<'a>(&'a self, k: &K) -> &'a V { match self.find(k) { Some(v) => v, None => fail!("No entry found for key: %?", k), } } /// Return true if the map contains a value for the specified key, /// using equivalence fn contains_key_equiv>(&self, key: &Q) -> bool { match self.bucket_for_key_equiv(key) { FoundEntry(_) => {true} TableFull | FoundHole(_) => {false} } } /// Return the value corresponding to the key in the map, using /// equivalence fn find_equiv<'a, Q:Hash + Equiv>(&'a self, k: &Q) -> Option<&'a V> { match self.bucket_for_key_equiv(k) { FoundEntry(idx) => Some(self.value_for_bucket(idx)), TableFull | FoundHole(_) => None, } } } pub impl HashMap { /// Like `find`, but returns a copy of the value. fn find_copy(&self, k: &K) -> Option { self.find(k).map_consume(|v| copy *v) } /// Like `get`, but returns a copy of the value. fn get_copy(&self, k: &K) -> V { copy *self.get(k) } } impl Eq for HashMap { fn eq(&self, other: &HashMap) -> bool { if self.len() != other.len() { return false; } for self.each |key, value| { match other.find(key) { None => return false, Some(v) => if value != v { return false }, } } true } fn ne(&self, other: &HashMap) -> bool { !self.eq(other) } } pub struct HashSet { priv map: HashMap } impl BaseIter for HashSet { /// Visit all values in order fn each(&self, f: &fn(&T) -> bool) -> bool { self.map.each_key(f) } fn size_hint(&self) -> Option { Some(self.len()) } } impl Eq for HashSet { fn eq(&self, other: &HashSet) -> bool { self.map == other.map } fn ne(&self, other: &HashSet) -> bool { self.map != other.map } } impl Container for HashSet { /// Return the number of elements in the set fn len(&const self) -> uint { self.map.len() } /// Return true if the set contains no elements fn is_empty(&const self) -> bool { self.map.is_empty() } } impl Mutable for HashSet { /// Clear the set, removing all values. fn clear(&mut self) { self.map.clear() } } impl Set for HashSet { /// Return true if the set contains a value fn contains(&self, value: &T) -> bool { self.map.contains_key(value) } /// Add a value to the set. Return true if the value was not already /// present in the set. fn insert(&mut self, value: T) -> bool { self.map.insert(value, ()) } /// Remove a value from the set. Return true if the value was /// present in the set. fn remove(&mut self, value: &T) -> bool { self.map.remove(value) } /// Return true if the set has no elements in common with `other`. /// This is equivalent to checking for an empty intersection. fn is_disjoint(&self, other: &HashSet) -> bool { old_iter::all(self, |v| !other.contains(v)) } /// Return true if the set is a subset of another fn is_subset(&self, other: &HashSet) -> bool { old_iter::all(self, |v| other.contains(v)) } /// Return true if the set is a superset of another fn is_superset(&self, other: &HashSet) -> bool { other.is_subset(self) } /// Visit the values representing the difference fn difference(&self, other: &HashSet, f: &fn(&T) -> bool) -> bool { self.each(|v| other.contains(v) || f(v)) } /// Visit the values representing the symmetric difference fn symmetric_difference(&self, other: &HashSet, f: &fn(&T) -> bool) -> bool { self.difference(other, f) && other.difference(self, f) } /// Visit the values representing the intersection fn intersection(&self, other: &HashSet, f: &fn(&T) -> bool) -> bool { self.each(|v| !other.contains(v) || f(v)) } /// Visit the values representing the union fn union(&self, other: &HashSet, f: &fn(&T) -> bool) -> bool { self.each(f) && other.each(|v| self.contains(v) || f(v)) } } pub impl HashSet { /// Create an empty HashSet fn new() -> HashSet { HashSet::with_capacity(INITIAL_CAPACITY) } /// Create an empty HashSet with space for at least `n` elements in /// the hash table. fn with_capacity(capacity: uint) -> HashSet { HashSet { map: HashMap::with_capacity(capacity) } } /// Reserve space for at least `n` elements in the hash table. fn reserve_at_least(&mut self, n: uint) { self.map.reserve_at_least(n) } /// Consumes all of the elements in the set, emptying it out fn consume(&mut self, f: &fn(T)) { self.map.consume(|k, _| f(k)) } fn contains_equiv>(&self, value: &Q) -> bool { self.map.contains_key_equiv(value) } } #[cfg(test)] mod test_map { use container::{Container, Map, Set}; use option::{None, Some}; use super::*; use uint; #[test] fn test_insert() { let mut m = HashMap::new(); assert!(m.insert(1, 2)); assert!(m.insert(2, 4)); assert_eq!(*m.get(&1), 2); assert_eq!(*m.get(&2), 4); } #[test] fn test_find_mut() { let mut m = HashMap::new(); assert!(m.insert(1, 12)); assert!(m.insert(2, 8)); assert!(m.insert(5, 14)); let new = 100; match m.find_mut(&5) { None => fail!(), Some(x) => *x = new } assert_eq!(m.find(&5), Some(&new)); } #[test] fn test_insert_overwrite() { let mut m = HashMap::new(); assert!(m.insert(1, 2)); assert_eq!(*m.get(&1), 2); assert!(!m.insert(1, 3)); assert_eq!(*m.get(&1), 3); } #[test] fn test_insert_conflicts() { let mut m = linear_map_with_capacity(4); assert!(m.insert(1, 2)); assert!(m.insert(5, 3)); assert!(m.insert(9, 4)); assert_eq!(*m.get(&9), 4); assert_eq!(*m.get(&5), 3); assert_eq!(*m.get(&1), 2); } #[test] fn test_conflict_remove() { let mut m = linear_map_with_capacity(4); assert!(m.insert(1, 2)); assert!(m.insert(5, 3)); assert!(m.insert(9, 4)); assert!(m.remove(&1)); assert_eq!(*m.get(&9), 4); assert_eq!(*m.get(&5), 3); } #[test] fn test_is_empty() { let mut m = linear_map_with_capacity(4); assert!(m.insert(1, 2)); assert!(!m.is_empty()); assert!(m.remove(&1)); assert!(m.is_empty()); } #[test] fn test_pop() { let mut m = HashMap::new(); m.insert(1, 2); assert_eq!(m.pop(&1), Some(2)); assert_eq!(m.pop(&1), None); } #[test] fn test_swap() { let mut m = HashMap::new(); assert_eq!(m.swap(1, 2), None); assert_eq!(m.swap(1, 3), Some(2)); assert_eq!(m.swap(1, 4), Some(3)); } #[test] fn test_find_or_insert() { let mut m = HashMap::new::(); assert_eq!(m.find_or_insert(1, 2), &2); assert_eq!(m.find_or_insert(1, 3), &2); } #[test] fn test_find_or_insert_with() { let mut m = HashMap::new::(); assert_eq!(m.find_or_insert_with(1, |_| 2), &2); assert_eq!(m.find_or_insert_with(1, |_| 3), &2); } #[test] fn test_consume() { let mut m = HashMap::new(); assert!(m.insert(1, 2)); assert!(m.insert(2, 3)); let mut m2 = HashMap::new(); do m.consume |k, v| { m2.insert(k, v); } assert_eq!(m.len(), 0); assert_eq!(m2.len(), 2); assert_eq!(m2.get(&1), &2); assert_eq!(m2.get(&2), &3); } #[test] fn test_iterate() { let mut m = linear_map_with_capacity(4); for uint::range(0, 32) |i| { assert!(m.insert(i, i*2)); } let mut observed = 0; for m.each |k, v| { assert_eq!(*v, *k * 2); observed |= (1 << *k); } assert_eq!(observed, 0xFFFF_FFFF); } #[test] fn test_find() { let mut m = HashMap::new(); assert!(m.find(&1).is_none()); m.insert(1, 2); match m.find(&1) { None => fail!(), Some(v) => assert!(*v == 2) } } #[test] fn test_eq() { let mut m1 = HashMap::new(); m1.insert(1, 2); m1.insert(2, 3); m1.insert(3, 4); let mut m2 = HashMap::new(); m2.insert(1, 2); m2.insert(2, 3); assert!(m1 != m2); m2.insert(3, 4); assert_eq!(m1, m2); } #[test] fn test_expand() { let mut m = HashMap::new(); assert_eq!(m.len(), 0); assert!(m.is_empty()); let mut i = 0u; let old_resize_at = m.resize_at; while old_resize_at == m.resize_at { m.insert(i, i); i += 1; } assert_eq!(m.len(), i); assert!(!m.is_empty()); } } #[cfg(test)] mod test_set { use super::*; use container::{Container, Map, Set}; use vec; #[test] fn test_disjoint() { let mut xs = HashSet::new(); let mut ys = HashSet::new(); assert!(xs.is_disjoint(&ys)); assert!(ys.is_disjoint(&xs)); assert!(xs.insert(5)); assert!(ys.insert(11)); assert!(xs.is_disjoint(&ys)); assert!(ys.is_disjoint(&xs)); assert!(xs.insert(7)); assert!(xs.insert(19)); assert!(xs.insert(4)); assert!(ys.insert(2)); assert!(ys.insert(-11)); assert!(xs.is_disjoint(&ys)); assert!(ys.is_disjoint(&xs)); assert!(ys.insert(7)); assert!(!xs.is_disjoint(&ys)); assert!(!ys.is_disjoint(&xs)); } #[test] fn test_subset_and_superset() { let mut a = HashSet::new(); assert!(a.insert(0)); assert!(a.insert(5)); assert!(a.insert(11)); assert!(a.insert(7)); let mut b = HashSet::new(); assert!(b.insert(0)); assert!(b.insert(7)); assert!(b.insert(19)); assert!(b.insert(250)); assert!(b.insert(11)); assert!(b.insert(200)); assert!(!a.is_subset(&b)); assert!(!a.is_superset(&b)); assert!(!b.is_subset(&a)); assert!(!b.is_superset(&a)); assert!(b.insert(5)); assert!(a.is_subset(&b)); assert!(!a.is_superset(&b)); assert!(!b.is_subset(&a)); assert!(b.is_superset(&a)); } #[test] fn test_intersection() { let mut a = HashSet::new(); let mut b = HashSet::new(); assert!(a.insert(11)); assert!(a.insert(1)); assert!(a.insert(3)); assert!(a.insert(77)); assert!(a.insert(103)); assert!(a.insert(5)); assert!(a.insert(-5)); assert!(b.insert(2)); assert!(b.insert(11)); assert!(b.insert(77)); assert!(b.insert(-9)); assert!(b.insert(-42)); assert!(b.insert(5)); assert!(b.insert(3)); let mut i = 0; let expected = [3, 5, 11, 77]; for a.intersection(&b) |x| { assert!(vec::contains(expected, x)); i += 1 } assert_eq!(i, expected.len()); } #[test] fn test_difference() { let mut a = HashSet::new(); let mut b = HashSet::new(); assert!(a.insert(1)); assert!(a.insert(3)); assert!(a.insert(5)); assert!(a.insert(9)); assert!(a.insert(11)); assert!(b.insert(3)); assert!(b.insert(9)); let mut i = 0; let expected = [1, 5, 11]; for a.difference(&b) |x| { assert!(vec::contains(expected, x)); i += 1 } assert_eq!(i, expected.len()); } #[test] fn test_symmetric_difference() { let mut a = HashSet::new(); let mut b = HashSet::new(); assert!(a.insert(1)); assert!(a.insert(3)); assert!(a.insert(5)); assert!(a.insert(9)); assert!(a.insert(11)); assert!(b.insert(-2)); assert!(b.insert(3)); assert!(b.insert(9)); assert!(b.insert(14)); assert!(b.insert(22)); let mut i = 0; let expected = [-2, 1, 5, 11, 14, 22]; for a.symmetric_difference(&b) |x| { assert!(vec::contains(expected, x)); i += 1 } assert_eq!(i, expected.len()); } #[test] fn test_union() { let mut a = HashSet::new(); let mut b = HashSet::new(); assert!(a.insert(1)); assert!(a.insert(3)); assert!(a.insert(5)); assert!(a.insert(9)); assert!(a.insert(11)); assert!(a.insert(16)); assert!(a.insert(19)); assert!(a.insert(24)); assert!(b.insert(-2)); assert!(b.insert(1)); assert!(b.insert(5)); assert!(b.insert(9)); assert!(b.insert(13)); assert!(b.insert(19)); let mut i = 0; let expected = [-2, 1, 3, 5, 9, 11, 13, 16, 19, 24]; for a.union(&b) |x| { assert!(vec::contains(expected, x)); i += 1 } assert_eq!(i, expected.len()); } }