Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
weighted graph
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaletski committed Oct 31, 2023
1 parent ecab623 commit e51aa14
Show file tree
Hide file tree
Showing 9 changed files with 405 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,42 @@ use std::collections::{HashMap, HashSet};

pub trait UnionFind {
fn join(&mut self, item1: usize, item2: usize) -> usize;
fn connected(&mut self, item1: &usize, item2: &usize) -> bool;
fn connected(&mut self, item1: usize, item2: usize) -> bool;
}

#[derive(Debug, Default)]
pub struct DisjointHashSet {
pub struct HashMapDSU {
mapping: HashMap<usize, usize>,
}

impl DisjointHashSet {
impl HashMapDSU {
/// create a new empty set
pub fn new() -> Self {
DisjointHashSet::default()
HashMapDSU::default()
}

/// get the number of items in the set
pub fn len(&self) -> usize {
self.mapping.len()
}

/// check if the set is empty
pub fn is_empty(&self) -> bool {
self.mapping.is_empty()
}

pub fn contains(&self, item: &usize) -> bool {
self.mapping.contains_key(item)
/// check if the set contains the item
pub fn contains(&self, item: usize) -> bool {
self.mapping.contains_key(&item)
}

/// get the id of the item's component if it's inserted
/// otherwise return None
pub fn id_of(&self, item: &usize) -> Option<usize> {
pub fn component_id_of(&self, item: usize) -> Option<usize> {
let mut curr_item = item;
while let Some(next_item) = self.mapping.get(curr_item) {
while let Some(&next_item) = self.mapping.get(&curr_item) {
if next_item == curr_item {
return Some(*curr_item);
return Some(curr_item);
} else {
curr_item = next_item;
}
Expand All @@ -38,17 +46,17 @@ impl DisjointHashSet {
}

/// get the id of the item's component and optimize the whole ids chain while doing it
fn get_id_and_optimize(&mut self, item: &usize) -> Option<usize> {
let chain_of_ids = |item: &usize| {
fn get_id_and_optimize(&mut self, item: usize) -> Option<usize> {
let chain_of_ids = |item: usize| {
let mut chain = vec![];

let mut curr_item = item;
while let Some(next_item) = self.mapping.get(curr_item) {
while let Some(&next_item) = self.mapping.get(&curr_item) {
if next_item == curr_item {
chain.push(*curr_item);
chain.push(curr_item);
return chain;
} else {
chain.push(*curr_item);
chain.push(curr_item);
curr_item = next_item;
}
}
Expand All @@ -67,28 +75,30 @@ impl DisjointHashSet {
}

/// insert the item if it isn't present
/// otherwise return it's component id
/// return it's component id
pub fn insert(&mut self, item: usize) -> usize {
if let Some(id) = self.id_of(&item) {
if let Some(id) = self.component_id_of(item) {
id
} else {
self.mapping.insert(item, item);
item
}
}

/// get all connected components of the set
pub fn components(&self) -> HashMap<usize, Vec<usize>> {
self.mapping.keys().fold(HashMap::new(), |mut map, item| {
let id = self.id_of(item).unwrap();
map.entry(id).or_insert(vec![]).push(*item);
self.mapping.keys().fold(HashMap::new(), |mut map, &item| {
let id = self.component_id_of(item).unwrap();
map.entry(id).or_insert(vec![]).push(item);
map
})
}

/// get the number of connected components in the set
pub fn components_count(&self) -> usize {
self.mapping
.keys()
.map(|k| self.id_of(k).unwrap())
.map(|&k| self.component_id_of(k).unwrap())
.fold(HashSet::new(), |mut set, item| {
set.insert(item);
set
Expand All @@ -97,13 +107,13 @@ impl DisjointHashSet {
}
}

impl UnionFind for DisjointHashSet {
impl UnionFind for HashMapDSU {
/// join two items and return id of the connected component they are in after joining
/// if some (or both) of the items wasn't present, it's inserted before joining
fn join(&mut self, item1: usize, item2: usize) -> usize {
match (
self.get_id_and_optimize(&item1),
self.get_id_and_optimize(&item2),
self.get_id_and_optimize(item1),
self.get_id_and_optimize(item2),
) {
(None, None) => {
self.mapping.insert(item1, item1);
Expand All @@ -128,8 +138,11 @@ impl UnionFind for DisjointHashSet {
}
}

fn connected(&mut self, item1: &usize, item2: &usize) -> bool {
self.get_id_and_optimize(item1) == self.get_id_and_optimize(item2)
fn connected(&mut self, item1: usize, item2: usize) -> bool {
let id1 = self.get_id_and_optimize(item1);
let id2 = self.get_id_and_optimize(item2);

id1 == id2 && id1.is_some()
}
}

Expand All @@ -147,12 +160,12 @@ mod tests {
use rstest::{fixture, rstest};

#[fixture]
fn empty_set() -> DisjointHashSet {
DisjointHashSet::new()
fn empty_set() -> HashMapDSU {
HashMapDSU::new()
}

#[fixture]
fn set_100_orphans(mut empty_set: DisjointHashSet) -> DisjointHashSet {
fn set_100_orphans(mut empty_set: HashMapDSU) -> HashMapDSU {
for i in 0..100 {
empty_set.insert(i);
}
Expand All @@ -161,7 +174,7 @@ mod tests {
}

#[fixture]
fn set_1to5_linear(mut empty_set: DisjointHashSet) -> DisjointHashSet {
fn set_1to5_linear(mut empty_set: HashMapDSU) -> HashMapDSU {
for i in 1..5 {
empty_set.join(i, i + 1);
}
Expand Down Expand Up @@ -202,7 +215,7 @@ mod tests {
}

#[fixture]
fn set_10by10(set_100_orphans: DisjointHashSet) -> DisjointHashSet {
fn set_10by10(set_100_orphans: HashMapDSU) -> HashMapDSU {
let mut set = set_100_orphans;

for component_idx in 0..10 {
Expand All @@ -223,58 +236,73 @@ mod tests {
/////////////////////////////////////

#[rstest]
fn inserting_new_item_increments_id(empty_set: DisjointHashSet) {
fn inserting_new_item_increments_id(empty_set: HashMapDSU) {
let mut set = empty_set;

for i in 0..1000 {
assert_returns!(i as usize, DisjointHashSet::insert, &mut set, i);
assert_returns!(i as usize, HashMapDSU::insert, &mut set, i);
}
}

#[rstest]
fn inserted_items_are_have_unique_ids(set_100_orphans: DisjointHashSet) {
fn inserted_items_are_have_unique_ids(set_100_orphans: HashMapDSU) {
for i in 0..100 {
for j in (i + 1)..100 {
assert_ne!(set_100_orphans.id_of(&i), set_100_orphans.id_of(&j));
assert_ne!(set_100_orphans.component_id_of(i), set_100_orphans.component_id_of(j));
}
}
}

#[rstest]
fn items_in_empty_set_are_disconnected(mut empty_set: HashMapDSU) {
for i in 0..10 {
for j in (i + 1)..10 {
assert_returns!(
false,
HashMapDSU::connected,
&mut empty_set,
i,
j
);
}
}
}

#[rstest]
fn inserted_items_are_disconnected(mut set_100_orphans: DisjointHashSet) {
fn inserted_items_are_disconnected(mut set_100_orphans: HashMapDSU) {
for i in 0..100 {
for j in (i + 1)..100 {
assert_returns!(
false,
DisjointHashSet::connected,
HashMapDSU::connected,
&mut set_100_orphans,
&i,
&j
i,
j
);
}
}
assert_returns!(100, DisjointHashSet::components_count, &set_100_orphans);
assert_returns!(100, HashMapDSU::components_count, &set_100_orphans);
}

#[rstest]
fn components_count_is_correct(set_10by10: DisjointHashSet) {
assert_returns!(10, DisjointHashSet::components_count, &set_10by10);
fn components_count_is_correct(set_10by10: HashMapDSU) {
assert_returns!(10, HashMapDSU::components_count, &set_10by10);
}

#[rstest]
fn components_content_is_correct(set_10by10: DisjointHashSet) {
fn components_content_is_correct(set_10by10: HashMapDSU) {
let mut components: Vec<Vec<usize>> = set_10by10.components().values().cloned().collect();
components.sort_by_key(|values| values[0]);

for i in 0..10 {
let expected_content: Vec<usize> = ((i * 10)..((i + 1) * 10)).collect();
assert_that(&components[i as usize]).contains_all_of(&expected_content.iter());
}
assert_returns!(10, DisjointHashSet::components_count, &set_10by10);
assert_returns!(10, HashMapDSU::components_count, &set_10by10);
}

#[rstest]
fn joining_n_components_makes_single_component(mut set_10by10: DisjointHashSet) {
fn joining_n_components_makes_single_component(mut set_10by10: HashMapDSU) {
let mut rng = thread_rng();

let some_node_for_each_component: Vec<usize> = (0..10)
Expand All @@ -288,11 +316,11 @@ mod tests {
set_10by10.join(from, to);
}

assert_returns!(1, DisjointHashSet::components_count, &set_10by10);
assert_returns!(1, HashMapDSU::components_count, &set_10by10);
}

#[rstest]
fn joining_items_makes_their_ids_equal(mut set_100_orphans: DisjointHashSet) {
fn joining_items_makes_their_ids_equal(mut set_100_orphans: HashMapDSU) {
let mut rng = thread_rng();
let id_distr1 = rand::distributions::Uniform::from(0..100);
let id_distr2 = rand::distributions::Uniform::from(0..100);
Expand All @@ -302,38 +330,38 @@ mod tests {
let item2 = id_distr2.sample(&mut rng);

set_100_orphans.join(item1, item2);
assert_eq!(set_100_orphans.id_of(&item1), set_100_orphans.id_of(&item2));
assert_eq!(set_100_orphans.component_id_of(item1), set_100_orphans.component_id_of(item2));
}
}

#[rstest]
fn item_has_id_of_its_terminal_link(set_1to5_linear: DisjointHashSet) {
fn item_has_id_of_its_terminal_link(set_1to5_linear: HashMapDSU) {
for i in 1..=5 {
assert_returns!(Some(1), DisjointHashSet::id_of, &set_1to5_linear, &i);
assert_returns!(Some(1), HashMapDSU::component_id_of, &set_1to5_linear, i);
}
}

#[rstest]
fn components_are_valid_manual() {
let mut set = DisjointHashSet::new();
let mut set = HashMapDSU::new();

set.join(1, 2);
assert_returns!(1, DisjointHashSet::components_count, &set);
assert_returns!(1, HashMapDSU::components_count, &set);
set.join(3, 4);
assert_returns!(2, DisjointHashSet::components_count, &set);
assert_returns!(2, HashMapDSU::components_count, &set);

set.join(5, 6);
assert_returns!(3, DisjointHashSet::components_count, &set);
assert_returns!(3, HashMapDSU::components_count, &set);
set.join(7, 8);
assert_returns!(4, DisjointHashSet::components_count, &set);
assert_returns!(4, HashMapDSU::components_count, &set);

set.join(1, 4);
assert_returns!(3, DisjointHashSet::components_count, &set);
assert_returns!(3, HashMapDSU::components_count, &set);
set.join(7, 6);
assert_returns!(2, DisjointHashSet::components_count, &set);
assert_returns!(2, HashMapDSU::components_count, &set);

set.join(6, 4);
assert_returns!(1, DisjointHashSet::components_count, &set);
assert_returns!(1, HashMapDSU::components_count, &set);

let component = set.components().values().next().cloned().unwrap();

Expand All @@ -344,16 +372,16 @@ mod tests {
proptest! {
#[test]
fn new_set_contains_nothing(num in 0..1000) {
let set = DisjointHashSet::new();
let set = HashMapDSU::new();

prop_assert!(!set.contains(&(num as usize)));
prop_assert!(!set.contains(num as usize));
}

#[test]
fn id_of_returns_none_if_item_isnt_inserted(num in 0..1000) {
let set = DisjointHashSet::new();
let set = HashMapDSU::new();

prop_assert!(set.id_of(&(num as usize)).is_none());
prop_assert!(set.component_id_of(num as usize).is_none());
}
}
}
3 changes: 2 additions & 1 deletion rust/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub mod binary_tree;
pub mod disjoint_set;
pub mod disjoint_sets_union;
pub mod linked_list;
pub mod trie;
pub mod weighted_graph;

#[macro_export]
macro_rules! format_expr_kv {
Expand Down
7 changes: 2 additions & 5 deletions rust/src/common/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ impl From<Vec<&str>> for CharTrie {
mod test {
use crate::assert_returns;
use rstest::{fixture, rstest};
use std::path::PathBuf;

use lazy_static::lazy_static;
use proptest::proptest;
Expand Down Expand Up @@ -280,11 +279,9 @@ mod test {
random_words
};
static ref WORDS_100: Vec<String> = {
let words_file_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/words100.txt");
let words_file_content = include_str!("testdata/words100.txt");

std::fs::read_to_string(words_file_path)
.unwrap()
words_file_content
.trim()
.split("\n")
.map(|s| s.to_owned())
Expand Down
Loading

0 comments on commit e51aa14

Please sign in to comment.