dyck/
lib.rs

1//! A library for running Dyck algorithms over languages of generic <Token> string slice types.
2//!
3//! This module provides functionalities to create and manipulate languages
4//! based on user-defined tokens, allowing for the evaluation and transformation
5//! of sequences of tokens (words) according to the rules of Dyck languages.
6//!
7//! ## Usage
8//!
9//! Add `dyck` to your `Cargo.toml`:
10//!
11//! ```toml
12//! [dependencies]
13//! dyck = "0.1"
14//! ```
15//! ## Example: Creating a Dyck language and checking if a word is valid.
16//!
17//! ```rust
18//! use dyck::{Language, Word};
19//!
20//! // define pairs of tokens for the language
21//! let pairs = vec![("(", ")"), ("[", "]"), ("{", "}")];
22//! let language = Language::new_from_vec(&pairs).expect("Failed to create language");
23//!
24//! // define a word to check
25//! let word: Word<&str> = vec!["(", "[", "]", "(", ")", ")"];
26//!
27//! // check if the word is a valid Dyck word
28//! if language.is_valid(&word) {
29//!         println!("The word is a valid Dyck word.");
30//! } else {
31//!     println!("The word is not a valid Dyck word.");
32//! }
33//! ```
34//!
35//! See /examples in the repository root for more examples.
36
37use std::collections::HashMap;
38
39#[cfg(feature = "derive")]
40pub use dyck_derive::DyckToken;
41
42/// A trait implementable by user-defined enums to gain compatiblity with all Dyck functions.
43/// This trait alias is available as a derive macro via the derive feature.
44pub trait DyckToken: Clone + Copy + Eq + std::hash::Hash {}
45
46impl DyckToken for &str {}
47
48/// A word is a sequence of tokens to be evaluated relative to a Dyck language.
49/// It is not necessarily a dyck word, just a dyck candidate.
50pub type Word<T> = Vec<T>;
51
52/// Stores the context to be used to run algorithms on potential dyck words.
53pub struct Language<T>
54where
55    T: DyckToken,
56{
57    alphabet: Vec<T>,
58    open_to_close: HashMap<T, T>,
59    close_to_open: HashMap<T, T>,
60}
61
62impl<T> Language<T>
63where
64    T: DyckToken,
65{
66    /// Creates a new Dyck context from a vector of pairs.
67    pub fn new_from_vec(pairs: &Vec<(T, T)>) -> Result<Self, &'static str> {
68        let mut alphabet = Vec::new();
69        let mut open_to_close = HashMap::new();
70        let mut close_to_open = HashMap::new();
71        for &(open, close) in pairs {
72            if alphabet.contains(&open) || alphabet.contains(&close) {
73                return Err("Duplicate token in the alphabet.");
74            }
75            alphabet.push(open);
76            alphabet.push(close);
77            open_to_close.insert(open, close);
78            close_to_open.insert(close, open);
79        }
80        Ok(Self {
81            alphabet,
82            open_to_close,
83            close_to_open,
84        })
85    }
86
87    /// Creates a new Dyck context from a slice of pairs.
88    pub fn new_from_arr(pairs: &[(T, T)]) -> Result<Self, &'static str> {
89        Self::new_from_vec(&pairs.to_vec())
90    }
91
92    // Returns k, the number of parenthesis types in the alphabet.
93    pub fn get_k(&self) -> usize {
94        self.alphabet.len() / 2
95    }
96
97    /// Checks if a token is an opening token.
98    pub fn is_open(&self, token: T) -> bool {
99        self.open_to_close.contains_key(&token)
100    }
101
102    /// Checks if a token is a closing token.
103    pub fn is_close(&self, token: T) -> bool {
104        self.close_to_open.contains_key(&token)
105    }
106
107    /// Checks if a word is a valid Dyck word.
108    pub fn is_valid(&self, word: &Word<T>) -> bool {
109        let mut stack = Vec::new();
110        for &token in word {
111            if self.is_open(token) {
112                stack.push(token);
113            } else if self.is_close(token) {
114                if let Some(&last) = stack.last() {
115                    if self.open_to_close[&last] == token {
116                        stack.pop();
117                    } else {
118                        return false;
119                    }
120                } else {
121                    return false;
122                }
123            }
124        }
125        stack.is_empty()
126    }
127
128    /// Checks whether a word has balanced open and close tokens, but not necessarily in the
129    /// correct order.
130    pub fn is_balanced(&self, word: &Word<T>) -> bool {
131        let mut count = 0;
132        for &token in word {
133            if self.is_open(token) {
134                count += 1;
135            } else if self.is_close(token) {
136                count -= 1;
137            }
138            if count < 0 {
139                return false;
140            }
141        }
142        count == 0
143    }
144
145    /// Finds the length of the longest valid prefix of a word.
146    pub fn longest_valid_prefix(&self, word: &Word<T>) -> usize {
147        let mut stack = Vec::new();
148        let mut length = 0;
149
150        for (i, &token) in word.iter().enumerate() {
151            if self.is_open(token) {
152                stack.push(token);
153            } else if self.is_close(token) {
154                if let Some(&last) = stack.last() {
155                    if self.open_to_close[&last] == token {
156                        stack.pop();
157                        if stack.is_empty() {
158                            length = i + 1;
159                        }
160                    } else {
161                        break;
162                    }
163                } else {
164                    break;
165                }
166            }
167        }
168
169        length
170    }
171
172    /// Finds the shortest completion of a word to make it a valid Dyck word.
173    /// Returns the validating shortest appendage, or None if no such appendage exists.
174    pub fn shortest_validating_appendage(&self, word: &Word<T>) -> Option<Word<T>> {
175        let mut stack = Vec::new();
176        let mut completed_word = word.clone();
177
178        for &token in word {
179            if self.is_open(token) {
180                stack.push(token);
181            } else if self.is_close(token) {
182                if let Some(&last) = stack.last() {
183                    if self.open_to_close[&last] == token {
184                        stack.pop();
185                    }
186                } else if stack.is_empty() {
187                    return None;
188                }
189            }
190        }
191
192        for token in stack.into_iter().rev() {
193            if let Some(close_token) = self.get_close(token) {
194                completed_word.push(close_token);
195            }
196        }
197
198        Some(completed_word)
199    }
200
201    /// Finds the closing token corresponding to an open token.
202    pub fn get_close(&self, open: T) -> Option<T> {
203        self.open_to_close.get(&open).copied()
204    }
205
206    /// Finds the opening token corresponding to a closing token.
207    pub fn get_open(&self, close: T) -> Option<T> {
208        self.close_to_open.get(&close).copied()
209    }
210}