///|
/// Rebuild the exact union of two static sets. The output has one MPHF, which
/// is generally cheaper to query than retaining two independent segments.
pub fn StaticSet::union(
  self : StaticSet,
  other : StaticSet,
) -> Result[StaticSet, MphfError] {
  let keys = self.keys_by_slot()
  keys.append(other.keys_by_slot())
  keys.sort()
  keys.dedup()
  StaticSet::from_keys(keys)
}

///|
/// Rebuild keys common to both static sets. An empty intersection returns
/// `EmptyInput`, because an MPHF has no valid zero-key representation.
pub fn StaticSet::intersection(
  self : StaticSet,
  other : StaticSet,
) -> Result[StaticSet, MphfError] {
  let keys : Array[Int] = []
  for key in self.keys_by_slot {
    if other.contains(key) {
      keys.push(key)
    }
  }
  if keys.length() == 0 {
    return Err(EmptyInput)
  }
  StaticSet::from_keys(keys)
}

///|
/// Rebuild keys owned by `self` but absent from `other`.
pub fn StaticSet::difference(
  self : StaticSet,
  other : StaticSet,
) -> Result[StaticSet, MphfError] {
  let keys : Array[Int] = []
  for key in self.keys_by_slot {
    if !other.contains(key) {
      keys.push(key)
    }
  }
  if keys.length() == 0 {
    return Err(EmptyInput)
  }
  StaticSet::from_keys(keys)
}

///|
/// Rebuild keys appearing in exactly one source set.
pub fn StaticSet::symmetric_difference(
  self : StaticSet,
  other : StaticSet,
) -> Result[StaticSet, MphfError] {
  let keys : Array[Int] = []
  for key in self.keys_by_slot {
    if !other.contains(key) {
      keys.push(key)
    }
  }
  for key in other.keys_by_slot {
    if !self.contains(key) {
      keys.push(key)
    }
  }
  if keys.length() == 0 {
    return Err(EmptyInput)
  }
  StaticSet::from_keys(keys)
}

///|
/// Return whether at least one key belongs to both exact static sets.
pub fn StaticSet::overlaps(self : StaticSet, other : StaticSet) -> Bool {
  let (smaller, larger) = if self.len() <= other.len() {
    (self, other)
  } else {
    (other, self)
  }
  for key in smaller.keys_by_slot {
    if larger.contains(key) {
      return true
    }
  }
  false
}

///|
/// Return whether every key in `self` is also present in `other`.
pub fn StaticSet::is_subset_of(self : StaticSet, other : StaticSet) -> Bool {
  if self.len() > other.len() {
    return false
  }
  other.contains_all(self.keys_by_slot())
}

///|
/// Return all map keys in deterministic MPHF slot order.
pub fn StaticIntMap::keys_by_slot(self : StaticIntMap) -> Array[Int] {
  self.keys_by_slot.copy()
}

///|
/// Merge two maps into a new static map. Values in `other` take precedence for
/// equal keys; this is the one-segment equivalent of a two-layer map lookup.
pub fn StaticIntMap::merge_prefer_right(
  self : StaticIntMap,
  other : StaticIntMap,
) -> Result[StaticIntMap, MphfError] {
  let entries : Array[IntEntry] = []
  for entry in self.entries_by_slot() {
    if !other.contains_key(entry.key) {
      entries.push(entry)
    }
  }
  entries.append(other.entries_by_slot())
  StaticIntMap::from_entries(entries)
}

///|
/// Return whether both maps contain the same key/value pairs, independent of
/// the different MPHF layouts they may have selected during construction.
pub fn StaticIntMap::content_equals(
  self : StaticIntMap,
  other : StaticIntMap,
) -> Bool {
  if self.len() != other.len() {
    return false
  }
  for entry in self.entries_by_slot() {
    if other.get(entry.key) != Some(entry.value) {
      return false
    }
  }
  true
}

///|
/// Compare set contents without observing construction seeds or slot order.
pub fn StaticSet::content_equals(self : StaticSet, other : StaticSet) -> Bool {
  self.len() == other.len() && self.is_subset_of(other)
}