├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── release.toml ├── src ├── array.rs ├── concurrent.rs ├── lib.rs ├── traits.rs └── tree.rs └── tests └── random_concurrent.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | - 1.21.0 8 | matrix: 9 | allow_failures: 10 | - rust: nightly 11 | 12 | script: 13 | - cargo build --verbose --features "serde" 14 | - cargo test --verbose --features "serde" 15 | 16 | notifications: 17 | email: 18 | on_success: never 19 | 20 | # For Tarpaulin code coverage: 21 | 22 | sudo: required 23 | dist: trusty 24 | addons: 25 | apt: 26 | packages: 27 | - libssl-dev 28 | 29 | after_success: | 30 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 31 | bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) 32 | cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID 33 | fi 34 | 35 | 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog] and this project adheres to 6 | [Semantic Versioning]. 7 | 8 | [Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ 9 | [Semantic Versioning]: http://semver.org/spec/v2.0.0.html 10 | 11 | ## [Unreleased] 12 | 13 | ### Added 14 | - Random testing of `AUnionFind` using `quickcheck`. 15 | 16 | ## [0.4.2] - 2018-05-30 17 | 18 | ### Fixed 19 | - Version metadata. 20 | 21 | ## [0.4.1] - 2018-05-30 22 | 23 | ### Added 24 | - `#![doc(html_root_url = ...)]` annotation. 25 | 26 | ## [0.4.0] - 2018-05-30 27 | 28 | ### Added 29 | - Serde support for `UnionFind` and `AUnionFind`. Pass feature 30 | flag `"serde"` to Cargo to enable `Serializable` and `Deserializable` 31 | impls for both types. 32 | 33 | ### Changed 34 | - Renamed `UnionFind::as_vec` and `AUnionFind::as_vec` to `to_vec`, to 35 | better reflect the cost of the methods. 36 | 37 | ### Fixed 38 | - `AUnionFind::force` now correctly forces all laziness. 39 | - `AUnionFind::equiv` now checks that nothing has become equivalent before 40 | returning `false`. 41 | 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "disjoint-sets" 3 | version = "0.4.3-alpha.0" 4 | authors = ["Jesse A. Tov "] 5 | description = "Three union-find implementations" 6 | repository = "https://github.com/tov/disjoint-sets-rs" 7 | readme = "README.md" 8 | license = "MIT/Apache-2.0" 9 | keywords = ["union-find", "Tarjan"] 10 | categories = ["data-structures"] 11 | 12 | [badges] 13 | travis-ci = { repository = "tov/disjoint-sets-rs" } 14 | 15 | [dependencies] 16 | serde = { version = "1.0", optional = true, features = ["derive"] } 17 | 18 | [dev-dependencies] 19 | serde_json = "1.0" 20 | quickcheck = "0.6" 21 | 22 | [package.metadata.docs.rs] 23 | features = ["serde"] 24 | 25 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016-2018 Jesse A. Tov 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2018 Jesse A. Tov 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # disjoint-sets: three union-find implementations 2 | 3 | [![Build Status](https://travis-ci.org/tov/disjoint-sets-rs.svg?branch=master)](https://travis-ci.org/tov/disjoint-sets-rs) 4 | [![Crates.io](https://img.shields.io/crates/v/disjoint-sets.svg?maxAge=2592000)](https://crates.io/crates/disjoint-sets) 5 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT) 6 | [![License: Apache 2.0](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE-APACHE) 7 | 8 | The variants are: 9 | 10 | | | structure | element type | data? | concurrent? | 11 | | :-------- | :-------: | :----------: | :---: | :---------: | 12 | | `UnionFind` | vector | small integer | no | no | 13 | | `UnionFindNode` | tree | tree node | yes | no | 14 | | `AUnionFind` | array | `usize` | no | yes | 15 | 16 | All three perform rank-balanced path compression à la Tarjan, 17 | using interior mutability. 18 | 19 | 20 | ## Usage 21 | 22 | It’s [on crates.io](https://crates.io/crates/disjoint-sets), so add 23 | this to your `Cargo.toml`: 24 | 25 | ```toml 26 | [dependencies] 27 | disjoint-sets = "0.4.2" 28 | ``` 29 | 30 | And add this to your crate root: 31 | 32 | ```rust 33 | extern crate disjoint_sets; 34 | ``` 35 | 36 | This crate supports Rust version 1.21 and later. 37 | 38 | ## Examples 39 | 40 | Kruskal’s algorithm to find the minimum spanning tree of a graph: 41 | 42 | ```rust 43 | use disjoint_sets::UnionFind; 44 | use std::collections::HashSet; 45 | 46 | type Node = usize; 47 | type Weight = usize; 48 | 49 | struct Neighbor { 50 | dst: Node, 51 | weight: Weight, 52 | } 53 | 54 | type Graph = Vec>; 55 | 56 | fn edges_by_weight(graph: &Graph) -> Vec<(Node, Node, Weight)> { 57 | let mut edges = vec![]; 58 | 59 | for (src, dsts) in graph.iter().enumerate() { 60 | for edge in dsts { 61 | edges.push((src, edge.dst, edge.weight)); 62 | } 63 | } 64 | 65 | edges.sort_by_key(|&(_, _, weight)| weight); 66 | edges 67 | } 68 | 69 | fn mst(graph: &Graph) -> HashSet<(Node, Node)> { 70 | let mut result = HashSet::new(); 71 | let mut uf = UnionFind::new(graph.len()); 72 | 73 | for (src, dst, _) in edges_by_weight(graph) { 74 | if uf.union(src, dst) { 75 | result.insert((src, dst)); 76 | } 77 | } 78 | 79 | result 80 | } 81 | 82 | fn main() { 83 | // Graph to use: 84 | // 85 | // 0 ------ 1 ------ 2 86 | // | 6 | 5 | 87 | // | 8 | 1 | 4 88 | // | | | 89 | // 3 ------ 4 ------ 5 90 | // | 7 | 2 | 91 | // | 3 | 12 | 11 92 | // | | | 93 | // 6 ------ 7 ------ 8 94 | // 9 10 95 | let graph = vec![ 96 | // Node 0 97 | vec![ Neighbor { dst: 1, weight: 6 }, 98 | Neighbor { dst: 3, weight: 8 }, ], 99 | // Node 1 100 | vec![ Neighbor { dst: 2, weight: 5 }, 101 | Neighbor { dst: 4, weight: 1 }, ], 102 | // Node 2 103 | vec![ Neighbor { dst: 5, weight: 4 }, ], 104 | // Node 3 105 | vec![ Neighbor { dst: 4, weight: 7 }, 106 | Neighbor { dst: 6, weight: 3 }, ], 107 | // Node 4 108 | vec![ Neighbor { dst: 5, weight: 2 }, 109 | Neighbor { dst: 7, weight: 12 }, ], 110 | // Node 5 111 | vec![ Neighbor { dst: 8, weight: 11 }, ], 112 | // Node 6 113 | vec![ Neighbor { dst: 7, weight: 9 }, ], 114 | // Node 7 115 | vec![ Neighbor { dst: 8, weight: 10 }, ], 116 | // Node 8 117 | vec![ ], 118 | ]; 119 | 120 | assert_eq! { 121 | mst(&graph), 122 | vec![ (1, 4), (4, 5), (3, 6), (2, 5), 123 | (0, 1), (3, 4), (6, 7), (7, 8), ] 124 | .into_iter().collect::>() 125 | }; 126 | } 127 | ``` 128 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | { file="README.md", search="disjoint-sets = \"[0-9.]*\"", replace="disjoint-sets = \"{{version}}\"" }, 3 | { file="src/lib.rs", search="disjoint-sets = \"[0-9.]*\"", replace="disjoint-sets = \"{{version}}\"" }, 4 | { file="src/lib.rs", search="https://docs[.]rs/disjoint-sets/[0-9.]*", replace="https://docs.rs/disjoint-sets/{{version}}" }, 5 | { file="CHANGELOG.md", search="\\[Unreleased\\]", replace="[{{version}}] - {{date}}" } 6 | ] 7 | -------------------------------------------------------------------------------- /src/array.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::fmt::{self, Debug}; 3 | 4 | use super::ElementType; 5 | 6 | /// Vector-based union-find representing a set of disjoint sets. 7 | /// 8 | /// If configured with Cargo feature `"serde"`, impls for `Serialize` 9 | /// and `Deserialize` will be defined. 10 | #[derive(Clone)] 11 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 12 | pub struct UnionFind { 13 | elements: Vec>, 14 | ranks: Vec, 15 | } 16 | // Invariant: self.elements.len() == self.ranks.len() 17 | 18 | impl Debug for UnionFind { 19 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 20 | write!(formatter, "UnionFind({:?})", self.elements) 21 | } 22 | } 23 | 24 | impl Default for UnionFind { 25 | fn default() -> Self { 26 | UnionFind::new(0) 27 | } 28 | } 29 | 30 | impl UnionFind { 31 | /// Creates a new union-find of `size` elements. 32 | /// 33 | /// # Panics 34 | /// 35 | /// If `size` elements would overflow the element type `Element`. 36 | pub fn new(size: usize) -> Self { 37 | UnionFind { 38 | elements: (0..size).map(|i| { 39 | let e = Element::from_usize(i).expect("UnionFind::new: overflow"); 40 | Cell::new(e) 41 | }).collect(), 42 | ranks: vec![0; size], 43 | } 44 | } 45 | 46 | /// The number of elements in all the sets. 47 | pub fn len(&self) -> usize { 48 | self.elements.len() 49 | } 50 | 51 | /// Is the union-find devoid of elements? 52 | /// 53 | /// It is possible to create an empty `UnionFind` and then add 54 | /// elements with [`alloc`](#method.alloc). 55 | pub fn is_empty(&self) -> bool { 56 | self.elements.is_empty() 57 | } 58 | 59 | /// Creates a new element in a singleton set. 60 | /// 61 | /// # Panics 62 | /// 63 | /// If allocating another element would overflow the element type 64 | /// `Element`. 65 | pub fn alloc(&mut self) -> Element { 66 | let result = Element::from_usize(self.elements.len()) 67 | .expect("UnionFind::alloc: overflow"); 68 | self.elements.push(Cell::new(result)); 69 | self.ranks.push(0); 70 | result 71 | } 72 | 73 | /// Joins the sets of the two given elements. 74 | /// 75 | /// Returns whether anything changed. That is, if the sets were 76 | /// different, it returns `true`, but if they were already the same 77 | /// then it returns `false`. 78 | pub fn union(&mut self, a: Element, b: Element) -> bool { 79 | let a = self.find(a); 80 | let b = self.find(b); 81 | 82 | if a == b { return false; } 83 | 84 | let rank_a = self.rank(a); 85 | let rank_b = self.rank(b); 86 | 87 | if rank_a > rank_b { 88 | self.set_parent(b, a); 89 | } else if rank_b > rank_a { 90 | self.set_parent(a, b); 91 | } else { 92 | self.set_parent(a, b); 93 | self.increment_rank(b); 94 | } 95 | 96 | true 97 | } 98 | 99 | /// Finds the representative element for the given element’s set. 100 | pub fn find(&self, mut element: Element) -> Element { 101 | let mut parent = self.parent(element); 102 | 103 | while element != parent { 104 | let grandparent = self.parent(parent); 105 | self.set_parent(element, grandparent); 106 | element = parent; 107 | parent = grandparent; 108 | } 109 | 110 | element 111 | } 112 | 113 | /// Determines whether two elements are in the same set. 114 | pub fn equiv(&self, a: Element, b: Element) -> bool { 115 | self.find(a) == self.find(b) 116 | } 117 | 118 | /// Forces all laziness, so that each element points directly to its 119 | /// set’s representative. 120 | pub fn force(&self) { 121 | for i in 0 .. self.len() { 122 | let element = Element::from_usize(i).unwrap(); 123 | let root = self.find(element); 124 | self.set_parent(element, root); 125 | } 126 | } 127 | 128 | /// Returns a vector of set representatives. 129 | pub fn to_vec(&self) -> Vec { 130 | self.force(); 131 | self.elements.iter().map(Cell::get).collect() 132 | } 133 | 134 | // HELPERS 135 | 136 | fn rank(&self, element: Element) -> u8 { 137 | self.ranks[element.to_usize()] 138 | } 139 | 140 | fn increment_rank(&mut self, element: Element) { 141 | let i = element.to_usize(); 142 | self.ranks[i] = self.ranks[i].saturating_add(1); 143 | } 144 | 145 | fn parent(&self, element: Element) -> Element { 146 | self.elements[element.to_usize()].get() 147 | } 148 | 149 | fn set_parent(&self, element: Element, parent: Element) { 150 | self.elements[element.to_usize()].set(parent); 151 | } 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use super::*; 157 | 158 | #[test] 159 | fn len() { 160 | assert_eq!(5, UnionFind::::new(5).len()); 161 | } 162 | 163 | #[test] 164 | fn union() { 165 | let mut uf = UnionFind::::new(8); 166 | assert!(!uf.equiv(0, 1)); 167 | uf.union(0, 1); 168 | assert!(uf.equiv(0, 1)); 169 | } 170 | 171 | #[test] 172 | fn unions() { 173 | let mut uf = UnionFind::::new(8); 174 | assert!(uf.union(0, 1)); 175 | assert!(uf.union(1, 2)); 176 | assert!(uf.union(4, 3)); 177 | assert!(uf.union(3, 2)); 178 | assert!(! uf.union(0, 3)); 179 | 180 | assert!(uf.equiv(0, 1)); 181 | assert!(uf.equiv(0, 2)); 182 | assert!(uf.equiv(0, 3)); 183 | assert!(uf.equiv(0, 4)); 184 | assert!(!uf.equiv(0, 5)); 185 | 186 | uf.union(5, 3); 187 | assert!(uf.equiv(0, 5)); 188 | 189 | uf.union(6, 7); 190 | assert!(uf.equiv(6, 7)); 191 | assert!(!uf.equiv(5, 7)); 192 | 193 | uf.union(0, 7); 194 | assert!(uf.equiv(5, 7)); 195 | } 196 | 197 | #[cfg(feature = "serde")] 198 | #[test] 199 | fn serde_round_trip() { 200 | extern crate serde_json; 201 | 202 | let mut uf0: UnionFind = UnionFind::new(8); 203 | uf0.union(0, 1); 204 | uf0.union(2, 3); 205 | assert!( uf0.equiv(0, 1)); 206 | assert!(!uf0.equiv(1, 2)); 207 | assert!( uf0.equiv(2, 3)); 208 | 209 | let json = serde_json::to_string(&uf0).unwrap(); 210 | let uf1: UnionFind = serde_json::from_str(&json).unwrap(); 211 | assert!( uf1.equiv(0, 1)); 212 | assert!(!uf1.equiv(1, 2)); 213 | assert!( uf1.equiv(2, 3)); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/concurrent.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug}; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | #[cfg(feature = "serde")] 5 | use serde::{Serialize, Serializer, Deserialize, Deserializer}; 6 | 7 | /// Lock-free, concurrent union-find representing a set of disjoint sets. 8 | /// 9 | /// If configured with Cargo feature `"serde"`, impls for `Serialize` 10 | /// and `Deserialize` will be defined. Note that if the union-find is 11 | /// modified while being serialized, the view of the structure 12 | /// preserved by may not correspond to any particular moment in time. 13 | /// 14 | /// # Warning 15 | /// 16 | /// This should always produce correct answers, but the expected complexity 17 | /// guarantees may not hold. 18 | #[derive(Clone)] 19 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 20 | pub struct AUnionFind(Box<[Entry]>); 21 | 22 | struct Entry { 23 | id: AtomicUsize, 24 | rank: AtomicUsize, 25 | } 26 | 27 | impl Clone for Entry { 28 | fn clone(&self) -> Self { 29 | Entry::with_rank(self.id.load(Ordering::SeqCst), 30 | self.rank.load(Ordering::SeqCst)) 31 | } 32 | } 33 | 34 | impl Debug for AUnionFind { 35 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 36 | write!(formatter, "AUnionFind(")?; 37 | formatter.debug_list() 38 | .entries(self.0.iter().map(|entry| &entry.id)).finish()?; 39 | write!(formatter, ")") 40 | } 41 | } 42 | 43 | impl Default for AUnionFind { 44 | fn default() -> Self { 45 | AUnionFind::new(0) 46 | } 47 | } 48 | 49 | impl Entry { 50 | fn new(id: usize) -> Self { 51 | Self::with_rank(id, 0) 52 | } 53 | 54 | fn with_rank(id: usize, rank: usize) -> Self { 55 | Entry { 56 | id: AtomicUsize::new(id), 57 | rank: AtomicUsize::new(rank), 58 | } 59 | } 60 | } 61 | 62 | impl AUnionFind { 63 | /// Creates a new asynchronous union-find of `size` elements. 64 | pub fn new(size: usize) -> Self { 65 | AUnionFind((0..size) 66 | .map(Entry::new) 67 | .collect::>() 68 | .into_boxed_slice()) 69 | } 70 | 71 | /// The number of elements in all the sets. 72 | pub fn len(&self) -> usize { 73 | self.0.len() 74 | } 75 | 76 | /// Is the union-find devoid of elements? 77 | /// 78 | /// It is possible to create an empty `AUnionFind`, but unlike with 79 | /// [`UnionFind`](struct.UnionFind.html) it is not possible to add 80 | /// elements. 81 | pub fn is_empty(&self) -> bool { 82 | self.0.is_empty() 83 | } 84 | 85 | /// Joins the sets of the two given elements. 86 | /// 87 | /// Returns whether anything changed. That is, if the sets were 88 | /// different, it returns `true`, but if they were already the same 89 | /// then it returns `false`. 90 | pub fn union(&self, mut a: usize, mut b: usize) -> bool { 91 | loop { 92 | a = self.find(a); 93 | b = self.find(b); 94 | 95 | if a == b { return false; } 96 | 97 | let rank_a = self.rank(a); 98 | let rank_b = self.rank(b); 99 | 100 | if rank_a > rank_b { 101 | if self.change_parent(b, b, a) { return true; } 102 | } else if rank_b > rank_a { 103 | if self.change_parent(a, a, b) { return true; } 104 | } else if self.change_parent(a, a, b) { 105 | self.increment_rank(b); 106 | return true; 107 | } 108 | } 109 | } 110 | 111 | /// Finds the representative element for the given element’s set. 112 | pub fn find(&self, mut element: usize) -> usize { 113 | let mut parent = self.parent(element); 114 | 115 | while element != parent { 116 | let grandparent = self.parent(parent); 117 | self.change_parent(element, parent, grandparent); 118 | element = parent; 119 | parent = grandparent; 120 | } 121 | 122 | element 123 | } 124 | 125 | /// Determines whether two elements are in the same set. 126 | pub fn equiv(&self, mut a: usize, mut b: usize) -> bool { 127 | loop { 128 | a = self.find(a); 129 | b = self.find(b); 130 | 131 | if a == b { return true; } 132 | if self.parent(a) == a { return false; } 133 | } 134 | } 135 | 136 | /// Forces all laziness, so that each element points directly to its 137 | /// set’s representative. 138 | pub fn force(&self) { 139 | for i in 0 .. self.len() { 140 | loop { 141 | let parent = self.parent(i); 142 | if i == parent { 143 | break 144 | } else { 145 | let root = self.find(parent); 146 | if parent == root || self.change_parent(i, parent, root) { 147 | break; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | /// Returns a vector of set representatives. 155 | pub fn to_vec(&self) -> Vec { 156 | self.force(); 157 | self.0.iter().map(|entry| entry.id.load(Ordering::SeqCst)).collect() 158 | } 159 | 160 | // HELPERS 161 | 162 | fn rank(&self, element: usize) -> usize { 163 | self.0[element].rank.load(Ordering::SeqCst) 164 | } 165 | 166 | fn increment_rank(&self, element: usize) { 167 | self.0[element].rank.fetch_add(1, Ordering::SeqCst); 168 | } 169 | 170 | fn parent(&self, element: usize) -> usize { 171 | self.0[element].id.load(Ordering::SeqCst) 172 | } 173 | 174 | fn change_parent(&self, 175 | element: usize, 176 | old_parent: usize, 177 | new_parent: usize) 178 | -> bool { 179 | self.0[element].id.compare_and_swap(old_parent, 180 | new_parent, 181 | Ordering::SeqCst) 182 | == old_parent 183 | } 184 | } 185 | 186 | #[cfg(feature = "serde")] 187 | impl Serialize for Entry { 188 | fn serialize(&self, serializer: S) 189 | -> Result<::Ok, ::Error> 190 | { 191 | use serde::ser::SerializeStruct; 192 | 193 | let mut tuple = serializer.serialize_struct("Entry", 2)?; 194 | tuple.serialize_field("id", &self.id.load(Ordering::Relaxed))?; 195 | tuple.serialize_field("rank", &self.rank.load(Ordering::Relaxed))?; 196 | tuple.end() 197 | } 198 | } 199 | 200 | #[cfg(feature = "serde")] 201 | impl<'de> Deserialize<'de> for Entry { 202 | fn deserialize>(deserializer: D) -> Result { 203 | use serde::de::{self, Visitor, SeqAccess, MapAccess}; 204 | 205 | #[derive(Deserialize)] 206 | #[serde(field_identifier, rename_all = "lowercase")] 207 | enum Field { Id, Rank, } 208 | 209 | struct EntryVisitor; 210 | 211 | impl<'de> Visitor<'de> for EntryVisitor { 212 | type Value = Entry; 213 | 214 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 215 | formatter.write_str("struct Entry") 216 | } 217 | 218 | fn visit_seq>(self, mut seq: V) -> Result { 219 | let id = seq.next_element()? 220 | .ok_or_else(|| de::Error::invalid_length(0, &self))?; 221 | let rank = seq.next_element()? 222 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 223 | Ok(Entry::with_rank(id, rank)) 224 | } 225 | 226 | fn visit_map>(self, mut map: V) -> Result { 227 | let mut id = None; 228 | let mut rank = None; 229 | 230 | while let Some(key) = map.next_key()? { 231 | match key { 232 | Field::Id => { 233 | if id.is_some() { 234 | return Err(de::Error::duplicate_field("id")); 235 | } 236 | id = Some(map.next_value()?); 237 | } 238 | Field::Rank => { 239 | if rank.is_some() { 240 | return Err(de::Error::duplicate_field("rank")); 241 | } 242 | rank = Some(map.next_value()?); 243 | } 244 | } 245 | } 246 | 247 | let id = id.ok_or_else(|| de::Error::missing_field("id"))?; 248 | let rank = rank.ok_or_else(|| de::Error::missing_field("rank"))?; 249 | 250 | Ok(Entry::with_rank(id, rank)) 251 | } 252 | } 253 | 254 | const FIELDS: &'static [&'static str] = &["id", "rank"]; 255 | deserializer.deserialize_struct("Entry", FIELDS, EntryVisitor) 256 | } 257 | } 258 | 259 | #[cfg(test)] 260 | mod tests { 261 | use super::*; 262 | 263 | #[test] 264 | fn len() { 265 | assert_eq!(5, AUnionFind::new(5).len()); 266 | } 267 | 268 | #[test] 269 | fn union() { 270 | let uf = AUnionFind::new(8); 271 | assert!(!uf.equiv(0, 1)); 272 | uf.union(0, 1); 273 | assert!(uf.equiv(0, 1)); 274 | } 275 | 276 | #[test] 277 | fn unions() { 278 | let uf = AUnionFind::new(8); 279 | assert!(uf.union(0, 1)); 280 | assert!(uf.union(1, 2)); 281 | 282 | assert!(uf.union(4, 3)); 283 | assert!(uf.union(3, 2)); 284 | assert!(! uf.union(0, 3)); 285 | 286 | assert!(uf.equiv(0, 1)); 287 | assert!(uf.equiv(0, 2)); 288 | assert!(uf.equiv(0, 3)); 289 | assert!(uf.equiv(0, 4)); 290 | assert!(!uf.equiv(0, 5)); 291 | 292 | assert!(uf.union(5, 3)); 293 | assert!(uf.equiv(0, 5)); 294 | 295 | assert!(uf.union(6, 7)); 296 | assert!(uf.equiv(6, 7)); 297 | assert!(!uf.equiv(5, 7)); 298 | 299 | assert!(uf.union(0, 7)); 300 | assert!(uf.equiv(5, 7)); 301 | } 302 | 303 | #[test] 304 | fn changed() { 305 | let uf = AUnionFind::new(8); 306 | assert!(uf.union(2, 3)); 307 | assert!(uf.union(0, 1)); 308 | assert!(uf.union(1, 3)); 309 | assert!(!uf.union(0, 2)) 310 | } 311 | 312 | // This assumes that for equal-ranked roots, the first argument 313 | // to union is pointed to the second. 314 | #[test] 315 | fn to_vec() { 316 | let uf = AUnionFind::new(6); 317 | assert_eq!(uf.to_vec(), vec![0, 1, 2, 3, 4, 5]); 318 | uf.union(0, 1); 319 | assert_eq!(uf.to_vec(), vec![1, 1, 2, 3, 4, 5]); 320 | uf.union(2, 3); 321 | assert_eq!(uf.to_vec(), vec![1, 1, 3, 3, 4, 5]); 322 | uf.union(1, 3); 323 | assert_eq!(uf.to_vec(), vec![3, 3, 3, 3, 4, 5]); 324 | } 325 | 326 | #[cfg(feature = "serde")] 327 | #[test] 328 | fn serde_round_trip() { 329 | extern crate serde_json; 330 | 331 | let uf0 = AUnionFind::new(8); 332 | uf0.union(0, 1); 333 | uf0.union(2, 3); 334 | assert!( uf0.equiv(0, 1)); 335 | assert!(!uf0.equiv(1, 2)); 336 | assert!( uf0.equiv(2, 3)); 337 | 338 | let json = serde_json::to_string(&uf0).unwrap(); 339 | let uf1: AUnionFind = serde_json::from_str(&json).unwrap(); 340 | assert!( uf1.equiv(0, 1)); 341 | assert!(!uf1.equiv(1, 2)); 342 | assert!( uf1.equiv(2, 3)); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc(html_root_url = "https://docs.rs/disjoint-sets/0.4.2")] 2 | //! Three union-find implementations. 3 | //! 4 | //! The variants are: 5 | //! 6 | //! | | structure | element type | data? | concurrent? | 7 | //! | :-------- | :-------- | :----------- | :---- | :---------- | 8 | //! | [`UnionFind`](struct.UnionFind.html) | vector | small integer | no | no | 9 | //! | [`UnionFindNode`](struct.UnionFindNode.html) | tree | tree node | yes | no | 10 | //! | [`AUnionFind`](struct.AUnionFind.html) | array | `usize` | no | yes | 11 | //! 12 | //! All three perform rank-balanced path halving à la Tarjan, 13 | //! using interior mutability. 14 | //! 15 | //! # Usage 16 | //! 17 | //! It’s [on crates.io](https://crates.io/crates/disjoint-sets), so add 18 | //! this to your `Cargo.toml`: 19 | //! 20 | //! ```toml 21 | //! [dependencies] 22 | //! disjoint-sets = "0.4.2" 23 | //! ``` 24 | //! 25 | //! And add this to your crate root: 26 | //! 27 | //! ``` 28 | //! extern crate disjoint_sets; 29 | //! ``` 30 | //! 31 | //! This crate supports Rust version 1.21 and later. 32 | //! 33 | //! Pass Cargo feature `"serde"` to enable serialization and deserialization 34 | //! for `UnionFind` and `AUnionFind`. 35 | //! 36 | //! # Examples 37 | //! 38 | //! Kruskal’s algorithm to find the minimum spanning tree of a graph: 39 | //! 40 | //! ``` 41 | //! use disjoint_sets::UnionFind; 42 | //! use std::collections::HashSet; 43 | //! 44 | //! type Node = usize; 45 | //! type Weight = usize; 46 | //! 47 | //! struct Neighbor { 48 | //! dst: Node, 49 | //! weight: Weight, 50 | //! } 51 | //! 52 | //! type Graph = Vec>; 53 | //! 54 | //! fn edges_by_weight(graph: &Graph) -> Vec<(Node, Node, Weight)> { 55 | //! let mut edges = vec![]; 56 | //! 57 | //! for (src, dsts) in graph.iter().enumerate() { 58 | //! for edge in dsts { 59 | //! edges.push((src, edge.dst, edge.weight)); 60 | //! } 61 | //! } 62 | //! 63 | //! edges.sort_by_key(|&(_, _, weight)| weight); 64 | //! edges 65 | //! } 66 | //! 67 | //! fn mst(graph: &Graph) -> HashSet<(Node, Node)> { 68 | //! let mut result = HashSet::new(); 69 | //! let mut uf = UnionFind::new(graph.len()); 70 | //! 71 | //! for (src, dst, _) in edges_by_weight(graph) { 72 | //! if uf.union(src, dst) { 73 | //! result.insert((src, dst)); 74 | //! } 75 | //! } 76 | //! 77 | //! result 78 | //! } 79 | //! 80 | //! fn main() { 81 | //! // Graph to use: 82 | //! // 83 | //! // 0 ------ 1 ------ 2 84 | //! // | 6 | 5 | 85 | //! // | 8 | 1 | 4 86 | //! // | | | 87 | //! // 3 ------ 4 ------ 5 88 | //! // | 7 | 2 | 89 | //! // | 3 | 12 | 11 90 | //! // | | | 91 | //! // 6 ------ 7 ------ 8 92 | //! // 9 10 93 | //! let graph = vec![ 94 | //! // Node 0 95 | //! vec![ Neighbor { dst: 1, weight: 6 }, 96 | //! Neighbor { dst: 3, weight: 8 }, ], 97 | //! // Node 1 98 | //! vec![ Neighbor { dst: 2, weight: 5 }, 99 | //! Neighbor { dst: 4, weight: 1 }, ], 100 | //! // Node 2 101 | //! vec![ Neighbor { dst: 5, weight: 4 }, ], 102 | //! // Node 3 103 | //! vec![ Neighbor { dst: 4, weight: 7 }, 104 | //! Neighbor { dst: 6, weight: 3 }, ], 105 | //! // Node 4 106 | //! vec![ Neighbor { dst: 5, weight: 2 }, 107 | //! Neighbor { dst: 7, weight: 12 }, ], 108 | //! // Node 5 109 | //! vec![ Neighbor { dst: 8, weight: 11 }, ], 110 | //! // Node 6 111 | //! vec![ Neighbor { dst: 7, weight: 9 }, ], 112 | //! // Node 7 113 | //! vec![ Neighbor { dst: 8, weight: 10 }, ], 114 | //! // Node 8 115 | //! vec![ ], 116 | //! ]; 117 | //! 118 | //! assert_eq! { 119 | //! mst(&graph), 120 | //! vec![ (1, 4), (4, 5), (3, 6), (2, 5), 121 | //! (0, 1), (3, 4), (6, 7), (7, 8), ] 122 | //! .into_iter().collect::>() 123 | //! }; 124 | //! } 125 | //! ``` 126 | 127 | #![warn(missing_docs)] 128 | 129 | #[cfg(feature = "serde")] 130 | #[macro_use] 131 | extern crate serde; 132 | 133 | mod traits; 134 | mod array; 135 | mod tree; 136 | mod concurrent; 137 | 138 | pub use traits::ElementType; 139 | pub use array::UnionFind; 140 | pub use tree::UnionFindNode; 141 | pub use concurrent::AUnionFind; 142 | 143 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | /// A type that can be used as a [`UnionFind`](struct.UnionFind.html) 4 | /// element. 5 | /// 6 | /// It must be safely convertible to and from `usize`. 7 | /// 8 | /// The two methods must be well-behaved partial inverses as follows: 9 | /// 10 | /// - For all `n: usize`, if `Self::from_usize(n)` = `Some(t)` then 11 | /// `t.to_usize()` = `n`. 12 | /// - For all `t: Self`, if `t.to_usize()` = `n` then 13 | /// `Self::from_usize(n)` = `Some(t)`. 14 | /// - For all `n: usize`, if `Self::from_usize(n)` = `None` then for all 15 | /// `m: usize` such that `m > n`, `Self::from_usize(m)` = `None`. 16 | /// 17 | /// In other words, `ElementType` sets up a bijection between the first 18 | /// *k* `usize` values and some *k* values of the `Self` type. 19 | pub trait ElementType : Copy + Debug + Eq { 20 | /// Converts from `usize` to the element type. 21 | /// 22 | /// Returns `None` if the argument won’t fit in `Self`. 23 | fn from_usize(n: usize) -> Option; 24 | 25 | /// Converts from the element type to `usize`. 26 | fn to_usize(self) -> usize; 27 | } 28 | 29 | impl ElementType for usize { 30 | #[inline] 31 | fn from_usize(n: usize) -> Option { Some(n) } 32 | #[inline] 33 | fn to_usize(self) -> usize { self } 34 | } 35 | 36 | macro_rules! element_type_impl { 37 | ($type_:ident) => { 38 | impl ElementType for $type_ { 39 | #[inline] 40 | fn from_usize(u: usize) -> Option { 41 | let result = u as $type_; 42 | if result as usize == u { Some(result) } else { None } 43 | } 44 | 45 | #[inline] 46 | fn to_usize(self) -> usize { 47 | self as usize 48 | } 49 | } 50 | } 51 | } 52 | 53 | element_type_impl!(u8); 54 | element_type_impl!(u16); 55 | element_type_impl!(u32); 56 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | //! Tree-based union-find with associated data. 2 | 3 | use std::cell::RefCell; 4 | use std::cmp::Ordering; 5 | use std::fmt::{self, Debug}; 6 | use std::hash::{Hash, Hasher}; 7 | use std::rc::Rc; 8 | use std::mem; 9 | 10 | /// Pointer-based union-find representing disjoint sets with associated data. 11 | /// 12 | /// This union-find implementation uses nodes to represent set elements 13 | /// in a parent-pointer tree. Each set has associated with it an object 14 | /// of type `Data`, which can be looked up and modified via any 15 | /// representative of the set. 16 | /// 17 | /// Construct a new singleton set with [`UnionFindNode::new`](#method.new). 18 | /// 19 | /// # Examples 20 | /// 21 | /// As an example, we perform first-order unification using 22 | /// [`UnionFindNode`](struct.UnionFindNode.html)s to represent 23 | /// unification variables. 24 | /// 25 | /// ``` 26 | /// use disjoint_sets::UnionFindNode; 27 | /// 28 | /// // A term is either a variable or a function symbol applied to some 29 | /// // terms. 30 | /// #[derive(Clone, Debug, PartialEq, Eq)] 31 | /// enum Term { 32 | /// Variable(String), 33 | /// Constructor { 34 | /// symbol: String, 35 | /// params: Vec, 36 | /// } 37 | /// } 38 | /// 39 | /// // Syntactic sugar for terms — write them LISP-style: 40 | /// // 41 | /// // A a variable 42 | /// // 43 | /// // (f) a nullary function symbol 44 | /// // 45 | /// // (f A B (g)) function symbol applied to two variables and a 46 | /// // function symbol 47 | /// // 48 | /// // (arrow (tuple (vector A) (int)) A) 49 | /// // type scheme of a polymorphic vector index function 50 | /// // 51 | /// macro_rules! term { 52 | /// ( ( $symbol:ident $($args:tt)* ) ) 53 | /// => 54 | /// { 55 | /// Term::Constructor { 56 | /// symbol: stringify!($symbol).to_owned(), 57 | /// params: vec![ $(term!($args)),* ], 58 | /// } 59 | /// }; 60 | /// 61 | /// ( $symbol:ident ) 62 | /// => 63 | /// { 64 | /// Term::Variable(stringify!($symbol).to_owned()) 65 | /// }; 66 | /// } 67 | /// 68 | /// // Internally we break terms down into variables about which we have 69 | /// // no information, and variables that have unified with a function 70 | /// // symbol applied to other variables. 71 | /// #[derive(Clone, Debug)] 72 | /// enum Term_ { 73 | /// Indeterminate, 74 | /// Fixed { 75 | /// symbol: String, 76 | /// params: Vec, 77 | /// }, 78 | /// } 79 | /// type Variable = UnionFindNode; 80 | /// 81 | /// // To convert from external `Term`s to internal `Term_`s we use an 82 | /// // environment mapping variable names to their internal 83 | /// // representations as union-find nodes. 84 | /// use std::collections::HashMap; 85 | /// #[derive(Debug)] 86 | /// struct Environment(HashMap); 87 | /// 88 | /// // The environment can get Rc-cycles in it (because we don’t do an 89 | /// // occurs check, hence terms can be recursive). To avoid leaking, we 90 | /// // need to clear the references out of it. 91 | /// impl Drop for Environment { 92 | /// fn drop(&mut self) { 93 | /// for (_, v) in self.0.drain() { 94 | /// v.replace_data(Term_::Indeterminate); 95 | /// } 96 | /// } 97 | /// } 98 | /// 99 | /// impl Term { 100 | /// // Analyzes an external `Term`, converting it to internal 101 | /// // `Term_`s and returning a variable mapped to it. 102 | /// fn intern(self, env: &mut Environment) -> Variable { 103 | /// match self { 104 | /// Term::Variable(v) => { 105 | /// env.0.entry(v).or_insert_with(|| { 106 | /// UnionFindNode::new(Term_::Indeterminate) 107 | /// }).clone() 108 | /// } 109 | /// 110 | /// Term::Constructor { symbol, params } => { 111 | /// let params = params.into_iter() 112 | /// .map(|term| Term::intern(term, env)) 113 | /// .collect::>(); 114 | /// UnionFindNode::new(Term_::Fixed { 115 | /// symbol: symbol, 116 | /// params: params, 117 | /// }) 118 | /// }, 119 | /// } 120 | /// } 121 | /// } 122 | /// 123 | /// // A constraint is a collection of variables that need to unify, 124 | /// // along with an environment mapping names to variables. 125 | /// struct Constraint { 126 | /// eqs: Vec<(Variable, Variable)>, 127 | /// env: Environment, 128 | /// } 129 | /// 130 | /// impl Default for Constraint { 131 | /// // Returns the empty (fully solved) constraint. 132 | /// fn default() -> Self { 133 | /// Constraint { 134 | /// env: Environment(HashMap::new()), 135 | /// eqs: Vec::new(), 136 | /// } 137 | /// } 138 | /// } 139 | /// 140 | /// impl Constraint { 141 | /// // Creates a constraint that unifies two terms. 142 | /// fn new(t1: Term, t2: Term) -> Self { 143 | /// let mut new: Constraint = Default::default(); 144 | /// new.push(t1, t2); 145 | /// new 146 | /// } 147 | /// 148 | /// // Adds two additional terms to unify. 149 | /// fn push(&mut self, t1: Term, t2: Term) { 150 | /// let v1 = t1.intern(&mut self.env); 151 | /// let v2 = t2.intern(&mut self.env); 152 | /// self.eqs.push((v1, v2)) 153 | /// } 154 | /// 155 | /// // Performs a single unification step on a pair of variables. 156 | /// // This may result in more equalities to add to the constraint. 157 | /// fn unify(&mut self, mut v1: Variable, mut v2: Variable) 158 | /// -> Result<(), String> { 159 | /// 160 | /// match (v1.clone_data(), v2.clone_data()) { 161 | /// (Term_::Indeterminate, _) => { 162 | /// v1.union_with(&mut v2, |_, t2| t2); 163 | /// Ok(()) 164 | /// }, 165 | /// 166 | /// (_, Term_::Indeterminate) => { 167 | /// v1.union_with(&mut v2, |t1, _| t1); 168 | /// Ok(()) 169 | /// }, 170 | /// 171 | /// (Term_::Fixed { symbol: symbol1, params: params1 }, 172 | /// Term_::Fixed { symbol: symbol2, params: params2 }) => { 173 | /// if symbol1 != symbol2 { 174 | /// let msg = format!( 175 | /// "Could not unify symbols: {} and {}", 176 | /// symbol1, symbol2); 177 | /// return Err(msg); 178 | /// } 179 | /// 180 | /// if params1.len() != params2.len() { 181 | /// let msg = format!( 182 | /// "Arity mismatch: {}: {} != {}", 183 | /// symbol1, params1.len(), params2.len()); 184 | /// return Err(msg); 185 | /// } 186 | /// 187 | /// for (u1, u2) in params1.into_iter() 188 | /// .zip(params2.into_iter()) { 189 | /// self.eqs.push((u1, u2)); 190 | /// } 191 | /// 192 | /// v1.union(&mut v2); 193 | /// 194 | /// Ok(()) 195 | /// } 196 | /// } 197 | /// } 198 | /// 199 | /// // Unifies equalities until there’s nothing left to do. 200 | /// fn solve(mut self) -> Result { 201 | /// while let Some((v1, v2)) = self.eqs.pop() { 202 | /// try!(self.unify(v1, v2)); 203 | /// } 204 | /// 205 | /// Ok(self.env) 206 | /// } 207 | /// } 208 | /// 209 | /// // Returns whether a pair of terms is unifiable. 210 | /// fn unifiable(t1: Term, t2: Term) -> bool { 211 | /// Constraint::new(t1, t2).solve().is_ok() 212 | /// } 213 | /// 214 | /// fn main() { 215 | /// assert!(unifiable(term![ A ], term![ A ])); 216 | /// assert!(unifiable(term![ A ], term![ B ])); 217 | /// 218 | /// assert!( unifiable(term![ (a) ], term![ (a) ])); 219 | /// assert!(! unifiable(term![ (a) ], term![ (b) ])); 220 | /// 221 | /// assert!( unifiable(term![ (a A) ], term![ (a A) ])); 222 | /// assert!( unifiable(term![ (a A) ], term![ (a B) ])); 223 | /// assert!(! unifiable(term![ (a A) ], term![ (b A) ])); 224 | /// assert!( unifiable(term![ (a A B) ], term![ (a B A) ])); 225 | /// assert!(! unifiable(term![ (a A B C) ], term![ (a B A) ])); 226 | /// 227 | /// assert!( unifiable(term![ (a (b)) ], term![ (a (b)) ])); 228 | /// assert!(! unifiable(term![ (a (b)) ], term![ (a (c)) ])); 229 | /// assert!( unifiable(term![ (a A A) ], term![ (a (b) (b)) ])); 230 | /// assert!(! unifiable(term![ (a A A) ], term![ (a (b) (c)) ])); 231 | /// assert!( unifiable(term![ (a (f) A B C ) ], 232 | /// term![ (a A B C (f)) ])); 233 | /// assert!(! unifiable(term![ (a (f) A B C ) ], 234 | /// term![ (a A B C (g)) ])); 235 | /// assert!( unifiable(term![ (a (f) A B C ) ], 236 | /// term![ (a A D C (g)) ])); 237 | /// } 238 | /// ``` 239 | #[derive(Default)] 240 | pub struct UnionFindNode(Rc>>); 241 | 242 | enum NodeImpl { 243 | Root { 244 | data: Data, 245 | rank: u8, 246 | }, 247 | Link(UnionFindNode), 248 | Dummy, 249 | } 250 | 251 | use self::NodeImpl::*; 252 | 253 | impl UnionFindNode { 254 | fn addr(&self) -> usize { 255 | &*self.0 as *const _ as usize 256 | } 257 | } 258 | 259 | impl Clone for UnionFindNode { 260 | fn clone(&self) -> Self { 261 | UnionFindNode(Rc::clone(&self.0)) 262 | } 263 | } 264 | 265 | impl Debug for UnionFindNode { 266 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 267 | write!(formatter, "UnionFindNode({:p})", self.0) 268 | } 269 | } 270 | 271 | impl PartialEq for UnionFindNode { 272 | fn eq(&self, other: &UnionFindNode) -> bool { 273 | self.addr() == other.addr() 274 | } 275 | } 276 | 277 | impl Eq for UnionFindNode { } 278 | 279 | impl PartialOrd for UnionFindNode { 280 | fn partial_cmp(&self, other: &UnionFindNode) -> Option { 281 | Some(self.cmp(other)) 282 | } 283 | } 284 | 285 | impl Ord for UnionFindNode { 286 | fn cmp(&self, other: &UnionFindNode) -> Ordering { 287 | self.addr().cmp(&other.addr()) 288 | } 289 | } 290 | 291 | impl Hash for UnionFindNode { 292 | fn hash(&self, state: &mut H) { 293 | self.addr().hash(state) 294 | } 295 | } 296 | 297 | impl Default for NodeImpl { 298 | fn default() -> Self { 299 | Self::new(Data::default()) 300 | } 301 | } 302 | 303 | impl NodeImpl { 304 | fn new(data: Data) -> Self { 305 | Root { 306 | data: data, 307 | rank: 0, 308 | } 309 | } 310 | } 311 | 312 | impl UnionFindNode { 313 | /// Creates a new singleton set with associated data. 314 | /// 315 | /// Initially this set is disjoint from all other sets, but can 316 | /// be joined with other sets using [`union`](#method.union). 317 | pub fn new(data: Data) -> Self { 318 | UnionFindNode(Rc::new(RefCell::new(NodeImpl::new(data)))) 319 | } 320 | 321 | /// Unions two sets, combining their data as specified. 322 | /// 323 | /// To determine the data associated with the set resulting from a 324 | /// union, we pass a closure `f`, which will be passed `self`’s data 325 | /// and `other`’s data (in that order). Then `f` must return the data to 326 | /// associate with the unioned set. 327 | pub fn union_with(&mut self, other: &mut Self, f: F) -> bool 328 | where F: FnOnce(Data, Data) -> Data { 329 | 330 | let (a, rank_a) = self.find_with_rank(); 331 | let (b, rank_b) = other.find_with_rank(); 332 | 333 | if a == b { 334 | return false; 335 | } 336 | 337 | if rank_a > rank_b { 338 | b.set_parent_with(&a, |b_data, a_data| f(a_data, b_data)) 339 | } else if rank_b > rank_a { 340 | a.set_parent_with(&b, f) 341 | } else { 342 | b.increment_rank(); 343 | a.set_parent_with(&b, f) 344 | } 345 | 346 | true 347 | } 348 | 349 | /// Unions two sets. 350 | /// 351 | /// Retains the data associated with an arbitrary set, returning the 352 | /// data of the other. Returns `None` if `self` and `other` are 353 | /// already elements of the same set. 354 | pub fn union(&mut self, other: &mut Self) -> Option { 355 | let (a, rank_a) = self.find_with_rank(); 356 | let (b, rank_b) = other.find_with_rank(); 357 | 358 | if a == b { 359 | None 360 | } else if rank_a > rank_b { 361 | Some(b.set_parent(a)) 362 | } else if rank_b > rank_a { 363 | Some(a.set_parent(b)) 364 | } else { 365 | b.increment_rank(); 366 | Some(a.set_parent(b)) 367 | } 368 | } 369 | 370 | // Can we do find iteratively? 371 | 372 | /// Finds a node representing the set of a given node. 373 | /// 374 | /// For two nodes in the same set, `find` returns the same node. 375 | pub fn find(&self) -> Self { 376 | match *self.0.borrow_mut() { 377 | Root { .. } => self.clone(), 378 | Link(ref mut parent) => { 379 | let root = parent.find(); 380 | *parent = root.clone(); 381 | root 382 | } 383 | Dummy => panic!("find: got dummy"), 384 | } 385 | } 386 | 387 | fn find_with_rank(&self) -> (Self, u8) { 388 | match *self.0.borrow_mut() { 389 | Root { rank, .. } => (self.clone(), rank), 390 | Link(ref mut parent) => { 391 | let (root, rank) = parent.find_with_rank(); 392 | *parent = root.clone(); 393 | (root, rank) 394 | } 395 | Dummy => panic!("find: got dummy"), 396 | } 397 | } 398 | 399 | /// Are the two nodes representatives of the same set? 400 | pub fn equiv(&self, other: &Self) -> bool { 401 | self.find() == other.find() 402 | } 403 | 404 | /// Replaces the data associated with the set. 405 | pub fn replace_data(&self, new: Data) -> Data { 406 | use std::mem::replace; 407 | self.with_data(|data| replace(data, new)) 408 | } 409 | 410 | /// Returns a clone of the data associated with the set. 411 | pub fn clone_data(&self) -> Data 412 | where Data: Clone { 413 | self.with_data(|data| data.clone()) 414 | } 415 | 416 | /// Allows modifying the data associated with a set. 417 | pub fn with_data(&self, f: F) -> R 418 | where F: FnOnce(&mut Data) -> R { 419 | self.find().root_with_data(f) 420 | } 421 | 422 | // HELPERS 423 | 424 | fn root_with_data(&self, f: F) -> R 425 | where F: FnOnce(&mut Data) -> R { 426 | 427 | match *self.0.borrow_mut() { 428 | Root { ref mut data, .. } => f(data), 429 | _ => panic!("with_data: non-root") 430 | } 431 | } 432 | 433 | fn increment_rank(&self) { 434 | match *self.0.borrow_mut() { 435 | Root { ref mut rank, .. } => { 436 | *rank += 1; 437 | } 438 | _ => panic!("increment_rank: non-root") 439 | } 440 | } 441 | 442 | fn set_parent(&self, new_parent: Self) -> Data { 443 | match mem::replace(&mut *self.0.borrow_mut(), Link(new_parent)) { 444 | Root { data, .. } => data, 445 | _ => panic!("set_parent: non-root"), 446 | } 447 | } 448 | 449 | // PRECONDITION: 450 | // - self != parent 451 | // - self and parent are both root nodes 452 | fn set_parent_with(&self, parent: &Self, f: F) 453 | where F: FnOnce(Data, Data) -> Data { 454 | let mut guard_self = self.0.borrow_mut(); 455 | let mut guard_parent = parent.0.borrow_mut(); 456 | 457 | let contents_self = mem::replace(&mut *guard_self, 458 | Link(parent.clone())); 459 | let contents_parent = mem::replace(&mut *guard_parent, Dummy); 460 | 461 | match (contents_self, contents_parent) { 462 | (Root { data: data_self, .. }, 463 | Root { data: data_parent, rank }) => { 464 | let new_data = f(data_self, data_parent); 465 | mem::replace(&mut *guard_parent, Root { 466 | data: new_data, 467 | rank: rank, 468 | }); 469 | } 470 | _ => panic!("set_parent_with: non-root"), 471 | } 472 | } 473 | } 474 | 475 | #[cfg(test)] 476 | mod tests { 477 | use super::*; 478 | 479 | #[test] 480 | fn union() { 481 | let mut uf0 = UnionFindNode::new(()); 482 | let mut uf1 = UnionFindNode::new(()); 483 | assert!(!uf0.equiv(&uf1)); 484 | uf0.union(&mut uf1); 485 | assert!(uf0.equiv(&uf1)); 486 | } 487 | 488 | #[test] 489 | fn unions() { 490 | let mut uf0 = UnionFindNode::new(()); 491 | let mut uf1 = UnionFindNode::new(()); 492 | let mut uf2 = UnionFindNode::new(()); 493 | let mut uf3 = UnionFindNode::new(()); 494 | let mut uf4 = UnionFindNode::new(()); 495 | let mut uf5 = UnionFindNode::new(()); 496 | let mut uf6 = UnionFindNode::new(()); 497 | let mut uf7 = UnionFindNode::new(()); 498 | 499 | uf0.union(&mut uf1); 500 | uf1.union(&mut uf2); 501 | uf4.union(&mut uf3); 502 | uf3.union(&mut uf2); 503 | assert!(uf0.equiv(&uf1)); 504 | assert!(uf0.equiv(&uf2)); 505 | assert!(uf0.equiv(&uf3)); 506 | assert!(uf0.equiv(&uf4)); 507 | assert!(!uf0.equiv(&uf5)); 508 | 509 | uf3.union(&mut uf5); 510 | assert!(uf0.equiv(&uf5)); 511 | 512 | uf7.union(&mut uf6); 513 | assert!(uf6.equiv(&uf7)); 514 | assert!(!uf5.equiv(&uf7)); 515 | 516 | uf0.union(&mut uf7); 517 | assert!(uf5.equiv(&uf7)); 518 | } 519 | 520 | // 521 | // Unification example 522 | // 523 | 524 | #[derive(Clone, Debug, PartialEq, Eq)] 525 | enum Term { 526 | Variable(String), 527 | Constructor { 528 | symbol: String, 529 | params: Vec, 530 | } 531 | } 532 | 533 | macro_rules! term { 534 | ( ( $symbol:ident $($args:tt)* ) ) 535 | => 536 | { 537 | Term::Constructor { 538 | symbol: stringify!($symbol).to_owned(), 539 | params: vec![ $(term!($args)),* ], 540 | } 541 | }; 542 | 543 | ( $symbol:ident ) 544 | => 545 | { 546 | Term::Variable(stringify!($symbol).to_owned()) 547 | }; 548 | } 549 | 550 | #[derive(Clone, Debug)] 551 | enum Term_ { 552 | Indeterminate, 553 | Fixed { 554 | symbol: String, 555 | params: Vec, 556 | }, 557 | } 558 | type Variable = UnionFindNode; 559 | 560 | use std::collections::HashMap; 561 | #[derive(Debug)] 562 | struct Environment(HashMap); 563 | 564 | // The environment can get Rc-cycles in it (because we don’t do an 565 | // occurs check, hence terms can be recursive). To avoid leaking, we 566 | // need to clear the references out of it. 567 | impl Drop for Environment { 568 | fn drop(&mut self) { 569 | for (_, v) in self.0.drain() { 570 | v.replace_data(Term_::Indeterminate); 571 | } 572 | } 573 | } 574 | 575 | impl Term { 576 | fn intern(self, env: &mut Environment) -> Variable { 577 | match self { 578 | Term::Variable(v) => { 579 | env.0.entry(v).or_insert_with(|| { 580 | UnionFindNode::new(Term_::Indeterminate) 581 | }).clone() 582 | } 583 | 584 | Term::Constructor { symbol, params } => { 585 | let params = params.into_iter() 586 | .map(|term| Term::intern(term, env)) 587 | .collect::>(); 588 | UnionFindNode::new(Term_::Fixed { 589 | symbol: symbol, 590 | params: params, 591 | }) 592 | }, 593 | } 594 | } 595 | } 596 | 597 | struct Constraint { 598 | env: Environment, 599 | eqs: Vec<(Variable, Variable)>, 600 | } 601 | 602 | impl Default for Constraint { 603 | fn default() -> Self { 604 | Constraint { 605 | env: Environment(HashMap::new()), 606 | eqs: Vec::new(), 607 | } 608 | } 609 | } 610 | 611 | impl Constraint { 612 | fn new(t1: Term, t2: Term) -> Self { 613 | let mut new: Constraint = Default::default(); 614 | new.push(t1, t2); 615 | new 616 | } 617 | 618 | fn push(&mut self, t1: Term, t2: Term) { 619 | let v1 = t1.intern(&mut self.env); 620 | let v2 = t2.intern(&mut self.env); 621 | self.eqs.push((v1, v2)) 622 | } 623 | 624 | fn unify(&mut self, mut v1: Variable, mut v2: Variable) 625 | -> Result<(), String> { 626 | 627 | match (v1.clone_data(), v2.clone_data()) { 628 | (Term_::Indeterminate, _) => { 629 | v1.union_with(&mut v2, |_, t2| t2); 630 | Ok(()) 631 | }, 632 | 633 | (_, Term_::Indeterminate) => { 634 | v1.union_with(&mut v2, |t1, _| t1); 635 | Ok(()) 636 | }, 637 | 638 | (Term_::Fixed { symbol: symbol1, params: params1 }, 639 | Term_::Fixed { symbol: symbol2, params: params2 }) => { 640 | if symbol1 != symbol2 { 641 | let msg = format!( 642 | "Could not unify symbols: {} and {}", 643 | symbol1, symbol2); 644 | return Err(msg); 645 | } 646 | 647 | if params1.len() != params2.len() { 648 | let msg = format!( 649 | "Arity mismatch: {}: {} != {}", 650 | symbol1, params1.len(), params2.len()); 651 | return Err(msg); 652 | } 653 | 654 | for (u1, u2) in params1.into_iter() 655 | .zip(params2.into_iter()) { 656 | self.eqs.push((u1, u2)); 657 | } 658 | 659 | v1.union(&mut v2); 660 | 661 | Ok(()) 662 | } 663 | } 664 | } 665 | 666 | fn solve(mut self) -> Result { 667 | while let Some((v1, v2)) = self.eqs.pop() { 668 | try!(self.unify(v1, v2)); 669 | } 670 | 671 | Ok(self.env) 672 | } 673 | } 674 | 675 | fn unifiable(t1: Term, t2: Term) -> bool { 676 | Constraint::new(t1, t2).solve().is_ok() 677 | } 678 | 679 | #[test] 680 | fn unify_vars() { 681 | assert!(unifiable(term![ A ], term![ A ])); 682 | assert!(unifiable(term![ A ], term![ B ])); 683 | } 684 | 685 | #[test] 686 | fn unify_symbols() { 687 | assert!( unifiable(term![ (a) ], term![ (a) ])); 688 | assert!(! unifiable(term![ (a) ], term![ (b) ])); 689 | } 690 | 691 | #[test] 692 | fn unify_flat() { 693 | assert!( unifiable(term![ (a A) ], term![ (a A) ])); 694 | assert!( unifiable(term![ (a A) ], term![ (a B) ])); 695 | assert!(! unifiable(term![ (a A) ], term![ (b A) ])); 696 | assert!( unifiable(term![ (a A B) ], term![ (a B A) ])); 697 | assert!(! unifiable(term![ (a A B C) ], term![ (a B A) ])); 698 | } 699 | 700 | #[test] 701 | fn unify_deeper() { 702 | assert!( unifiable(term![ (a (b)) ], term![ (a (b)) ])); 703 | assert!(! unifiable(term![ (a (b)) ], term![ (a (c)) ])); 704 | assert!( unifiable(term![ (a A A) ], term![ (a (b) (b)) ])); 705 | assert!(! unifiable(term![ (a A A) ], term![ (a (b) (c)) ])); 706 | assert!( unifiable(term![ (a (f) A B C ) ], 707 | term![ (a A B C (f)) ])); 708 | assert!(! unifiable(term![ (a (f) A B C ) ], 709 | term![ (a A B C (g)) ])); 710 | assert!( unifiable(term![ (a (f) A B C ) ], 711 | term![ (a A D C (g)) ])); 712 | } 713 | } 714 | -------------------------------------------------------------------------------- /tests/random_concurrent.rs: -------------------------------------------------------------------------------- 1 | extern crate disjoint_sets; 2 | 3 | #[macro_use] 4 | extern crate quickcheck; 5 | 6 | use disjoint_sets::{UnionFind, AUnionFind}; 7 | use quickcheck::{Arbitrary, Gen}; 8 | use std::sync::Arc; 9 | use std::thread; 10 | 11 | // The length of the union-find we'll test on. 12 | const UF_LEN: usize = 100; 13 | 14 | // The percentage of commands that should be finds; the rest are unions. 15 | const FIND_PCT: usize = 80; 16 | 17 | // The maximum length of each generated script. 18 | const MAX_SCRIPT_LEN: usize = 200; 19 | 20 | // The number of threads to start. 21 | const CONCURRENCY: usize = 10; 22 | 23 | quickcheck! { 24 | fn prop_a_union_find_simulates_union_find(multi_script: MultiScript) -> bool { 25 | let mut tester = Tester::new(); 26 | tester.execute(&multi_script); 27 | tester.check() 28 | } 29 | } 30 | 31 | // We will run the same operations on an `AUnionFind` and a `UnionFind`, 32 | // and then check that the results are equivalent. 33 | struct Tester { 34 | concurrent: Arc, 35 | sequential: UnionFind, 36 | set_count: usize, 37 | } 38 | 39 | impl Tester { 40 | // Creates a fresh tester. 41 | fn new() -> Self { 42 | Tester { 43 | concurrent: Arc::new(AUnionFind::new(UF_LEN)), 44 | sequential: UnionFind::new(UF_LEN), 45 | set_count: UF_LEN, 46 | } 47 | } 48 | 49 | // Checks that the two union-finds in the tester are equivalent. 50 | fn check(&self) -> bool { 51 | // eprintln!("set_count: {}", self.set_count); 52 | 53 | for i in 0 .. UF_LEN { 54 | for j in 0 .. UF_LEN { 55 | if self.concurrent.equiv(i, j) != self.sequential.equiv(i, j) { 56 | return false; 57 | } 58 | } 59 | } 60 | 61 | true 62 | } 63 | 64 | // Executes the given multi-script on both union-finds. 65 | fn execute(&mut self, multi_script: &MultiScript) { 66 | // First execute the scripts sequentially: 67 | for script in &multi_script.0 { 68 | for cmd in &script.0 { 69 | match *cmd { 70 | Cmd::Union(i, j) => 71 | if self.sequential.union(i, j) { 72 | self.set_count -= 1; 73 | } 74 | Cmd::Find(i) => { self.sequential.find(i); } 75 | } 76 | } 77 | } 78 | 79 | // Next we'll do the concurrent version. We clone the multi-script 80 | // so we can hand off ownership of a script to each thread. 81 | let mut handles = Vec::with_capacity(multi_script.0.len()); 82 | for script in multi_script.clone().0 { 83 | let uf = self.concurrent.clone(); 84 | handles.push(thread::spawn(move || { 85 | for cmd in script.0 { 86 | match cmd { 87 | Cmd::Union(i, j) => { uf.union(i, j); } 88 | Cmd::Find(i) => { uf.find(i); } 89 | } 90 | } 91 | })); 92 | } 93 | 94 | for handle in handles { 95 | handle.join().unwrap(); 96 | } 97 | } 98 | } 99 | 100 | // Multiple scripts, one for each thread. 101 | #[derive(Clone, Debug)] 102 | struct MultiScript(Vec