rust_algorithms/other/strings.rs
1//! Satrlar bilan ishlash algoritmlari.
2//!
3//! > **Rust eslatmasi:** `String` — UTF-8. `s[0]` ishlamaydi, chunki bitta belgi
4//! > 1–4 bayt bo'lishi mumkin. Shu sababli bu yerdagi funksiyalar `Vec<char>` bilan
5//! > ishlaydi — o'zbekcha `oʻ`, `gʻ` kabi belgilar ham to'g'ri hisoblanadi.
6
7use std::collections::HashMap;
8
9/// Sodda (naive) qidiruv: har bir pozitsiyadan boshlab solishtirish.
10///
11/// - **Time:** eng yomon holatda O(n·m), **Space:** O(1).
12///
13/// # Misol
14/// ```
15/// use rust_algorithms::other::strings::naive_search;
16///
17/// assert_eq!(naive_search("abcabcabd", "abcabd"), vec![3]);
18/// assert_eq!(naive_search("aaaa", "aa"), vec![0, 1, 2]);
19/// ```
20pub fn naive_search(text: &str, pattern: &str) -> Vec<usize> {
21 let t: Vec<char> = text.chars().collect();
22 let p: Vec<char> = pattern.chars().collect();
23 if p.is_empty() || p.len() > t.len() {
24 return Vec::new();
25 }
26 (0..=(t.len() - p.len()))
27 .filter(|&i| t[i..i + p.len()] == p[..])
28 .collect()
29}
30
31/// **KMP** (Knuth–Morris–Pratt) — matndan namunani **O(n + m)** da qidirish.
32///
33/// **Muammo:** sodda qidiruvda moslik buzilganda biz boshiga qaytamiz va
34/// allaqachon o'qigan belgilarni qayta o'qiymiz.
35///
36/// **KMP yechimi:** namunaning o'zidan **prefiks funksiyasi** (LPS jadvali) tuzamiz:
37/// `lps[i]` = `pattern[..=i]` ning ham prefiks, ham suffiks bo'la oladigan eng uzun
38/// qismining uzunligi. Moslik buzilganda shu jadval bizga "qayerdan davom etish"ni
39/// aytadi — matn bo'yicha **hech qachon orqaga qaytmaymiz**.
40///
41/// ```text
42/// pattern: a b a b a c a
43/// lps: 0 0 1 2 3 0 1
44/// ↑ "aba" ham bosh, ham oxir
45/// ```
46///
47/// - **Time:** O(n + m), **Space:** O(m).
48///
49/// Qaytadi: namunaning barcha boshlanish indekslari (belgilar bo'yicha).
50///
51/// # Misol
52/// ```
53/// use rust_algorithms::other::strings::kmp_search;
54///
55/// assert_eq!(kmp_search("ABABDABACDABABCABAB", "ABABCABAB"), vec![10]);
56/// assert_eq!(kmp_search("aaaaa", "aa"), vec![0, 1, 2, 3]);
57/// assert_eq!(kmp_search("abc", "xyz"), Vec::<usize>::new());
58/// ```
59pub fn kmp_search(text: &str, pattern: &str) -> Vec<usize> {
60 let t: Vec<char> = text.chars().collect();
61 let p: Vec<char> = pattern.chars().collect();
62 if p.is_empty() || p.len() > t.len() {
63 return Vec::new();
64 }
65
66 let lps = prefix_function(&p);
67 let mut natija = Vec::new();
68 let mut j = 0usize; // namunada nechta belgi mos keldi
69
70 for (i, &ch) in t.iter().enumerate() {
71 while j > 0 && ch != p[j] {
72 j = lps[j - 1]; // orqaga qaytmasdan "sakraymiz"
73 }
74 if ch == p[j] {
75 j += 1;
76 }
77 if j == p.len() {
78 natija.push(i + 1 - p.len());
79 j = lps[j - 1]; // keyingi mosliklarni ham qidiramiz
80 }
81 }
82 natija
83}
84
85/// KMP uchun prefiks funksiyasi (LPS jadvali).
86fn prefix_function(p: &[char]) -> Vec<usize> {
87 let mut lps = vec![0usize; p.len()];
88 let mut k = 0usize;
89 for i in 1..p.len() {
90 while k > 0 && p[i] != p[k] {
91 k = lps[k - 1];
92 }
93 if p[i] == p[k] {
94 k += 1;
95 }
96 lps[i] = k;
97 }
98 lps
99}
100
101/// **Rabin-Karp** — xesh yordamida qidirish (rolling hash).
102///
103/// **G'oya:** namunaning xeshini hisoblaymiz va matn bo'ylab siljuvchi oynaning
104/// xeshini **O(1)** da yangilaymiz (chiqqanini ayirib, kirganini qo'shib).
105/// Xeshlar teng bo'lsa — haqiqiy tenglikni tekshiramiz (soxta moslik bo'lishi mumkin).
106///
107/// - **Time:** o'rtacha O(n + m), eng yomon holatda O(n·m).
108/// - **Kuchli tomoni:** bir vaqtda **ko'p namunani** qidirishga oson kengayadi
109/// (plagiat tekshirgichlar shu tamoyilda ishlaydi).
110///
111/// # Misol
112/// ```
113/// use rust_algorithms::other::strings::{kmp_search, rabin_karp};
114///
115/// let matn = "the quick brown fox jumps over the lazy dog";
116/// assert_eq!(rabin_karp(matn, "the"), kmp_search(matn, "the"));
117/// assert_eq!(rabin_karp(matn, "fox"), vec![16]);
118/// ```
119pub fn rabin_karp(text: &str, pattern: &str) -> Vec<usize> {
120 const ASOS: u64 = 256;
121 const MOD: u64 = 1_000_000_007;
122
123 let t: Vec<char> = text.chars().collect();
124 let p: Vec<char> = pattern.chars().collect();
125 if p.is_empty() || p.len() > t.len() {
126 return Vec::new();
127 }
128
129 // eng yuqori razryad koeffitsienti: ASOS^(m-1) mod MOD
130 let mut yuqori = 1u64;
131 for _ in 1..p.len() {
132 yuqori = yuqori * ASOS % MOD;
133 }
134
135 let xesh = |belgilar: &[char]| -> u64 {
136 belgilar
137 .iter()
138 .fold(0u64, |acc, &c| (acc * ASOS + c as u64) % MOD)
139 };
140
141 let p_xesh = xesh(&p);
142 let mut oyna = xesh(&t[..p.len()]);
143 let mut natija = Vec::new();
144
145 for i in 0..=(t.len() - p.len()) {
146 if oyna == p_xesh && t[i..i + p.len()] == p[..] {
147 natija.push(i);
148 }
149 if i + p.len() < t.len() {
150 // chiqqan belgini ayiramiz, yangisini qo'shamiz
151 let chiqdi = t[i] as u64 % MOD * yuqori % MOD;
152 oyna = (oyna + MOD - chiqdi) % MOD;
153 oyna = (oyna * ASOS + t[i + p.len()] as u64) % MOD;
154 }
155 }
156 natija
157}
158
159/// Satr palindrommi? (harf-raqamlardan boshqasi e'tiborga olinmaydi, registr muhim emas)
160///
161/// - **Time:** O(n), **Space:** O(n).
162///
163/// # Misol
164/// ```
165/// use rust_algorithms::other::strings::is_palindrome;
166///
167/// assert!(is_palindrome("kavak"));
168/// assert!(is_palindrome("A man, a plan, a canal: Panama"));
169/// assert!(!is_palindrome("salom"));
170/// assert!(is_palindrome(""));
171/// ```
172pub fn is_palindrome(s: &str) -> bool {
173 let tozalangan: Vec<char> = s
174 .chars()
175 .filter(|c| c.is_alphanumeric())
176 .flat_map(|c| c.to_lowercase())
177 .collect();
178
179 let (mut chap, mut ong) = (0usize, tozalangan.len());
180 while chap + 1 < ong {
181 if tozalangan[chap] != tozalangan[ong - 1] {
182 return false;
183 }
184 chap += 1;
185 ong -= 1;
186 }
187 true
188}
189
190/// Ikki satr anagrammi? (bir xil harflardan tuzilganmi)
191///
192/// - **Time:** O(n + m), **Space:** O(alifbo).
193///
194/// # Misol
195/// ```
196/// use rust_algorithms::other::strings::is_anagram;
197///
198/// assert!(is_anagram("listen", "silent"));
199/// assert!(is_anagram("olma", "malo"));
200/// assert!(!is_anagram("salom", "salomat"));
201/// ```
202pub fn is_anagram(a: &str, b: &str) -> bool {
203 fn sanoq(s: &str) -> HashMap<char, usize> {
204 let mut m = HashMap::new();
205 for c in s.chars().flat_map(|c| c.to_lowercase()) {
206 *m.entry(c).or_insert(0) += 1;
207 }
208 m
209 }
210 sanoq(a) == sanoq(b)
211}
212
213/// Satrlar to'plamining eng uzun umumiy prefiksi.
214///
215/// - **Time:** O(jami belgilar soni).
216///
217/// # Misol
218/// ```
219/// use rust_algorithms::other::strings::longest_common_prefix;
220///
221/// assert_eq!(longest_common_prefix(&["flower", "flow", "flight"]), "fl");
222/// assert_eq!(longest_common_prefix(&["dog", "car"]), "");
223/// assert_eq!(longest_common_prefix(&[]), "");
224/// ```
225pub fn longest_common_prefix(words: &[&str]) -> String {
226 let Some(birinchi) = words.first() else {
227 return String::new();
228 };
229 let mut prefiks: Vec<char> = birinchi.chars().collect();
230
231 for so_z in &words[1..] {
232 let belgilar: Vec<char> = so_z.chars().collect();
233 let mut i = 0;
234 while i < prefiks.len() && i < belgilar.len() && prefiks[i] == belgilar[i] {
235 i += 1;
236 }
237 prefiks.truncate(i);
238 if prefiks.is_empty() {
239 break;
240 }
241 }
242 prefiks.into_iter().collect()
243}
244
245/// So'zlar tartibini teskari qiladi ("Men Rust yaxshi ko'raman" → "ko'raman yaxshi Rust Men").
246///
247/// # Misol
248/// ```
249/// use rust_algorithms::other::strings::reverse_words;
250///
251/// assert_eq!(reverse_words("Men Rustni yaxshi ko'raman"), "ko'raman yaxshi Rustni Men");
252/// assert_eq!(reverse_words(" bir ikki "), "ikki bir");
253/// ```
254pub fn reverse_words(s: &str) -> String {
255 s.split_whitespace().rev().collect::<Vec<_>>().join(" ")
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::util::Rng;
262
263 #[test]
264 fn uchta_qidiruv_bir_xil_natija() {
265 let mut rng = Rng::new(1201);
266 let alifbo = ['a', 'b', 'c'];
267 for _ in 0..100 {
268 let matn: String = (0..60).map(|_| alifbo[rng.below(3)]).collect();
269 let namuna: String = (0..(1 + rng.below(4)))
270 .map(|_| alifbo[rng.below(3)])
271 .collect();
272
273 let n = naive_search(&matn, &namuna);
274 assert_eq!(kmp_search(&matn, &namuna), n, "matn={matn} namuna={namuna}");
275 assert_eq!(rabin_karp(&matn, &namuna), n);
276 }
277 }
278
279 #[test]
280 fn chegaraviy_holatlar() {
281 assert!(kmp_search("", "a").is_empty());
282 assert!(kmp_search("a", "").is_empty());
283 assert!(kmp_search("ab", "abc").is_empty());
284 assert_eq!(kmp_search("abc", "abc"), vec![0]);
285 }
286
287 #[test]
288 fn prefiks_funksiyasi() {
289 let p: Vec<char> = "ababaca".chars().collect();
290 assert_eq!(prefix_function(&p), vec![0, 0, 1, 2, 3, 0, 1]);
291 }
292
293 #[test]
294 fn palindromlar() {
295 assert!(is_palindrome("a"));
296 assert!(is_palindrome("aa"));
297 assert!(is_palindrome("aba"));
298 assert!(!is_palindrome("ab"));
299 assert!(is_palindrome("No 'x' in Nixon"));
300 }
301
302 #[test]
303 fn anagrammalar() {
304 assert!(is_anagram("", ""));
305 assert!(is_anagram("Aabb", "bBAa"));
306 assert!(!is_anagram("aab", "abb"));
307 }
308
309 #[test]
310 fn umumiy_prefiks() {
311 assert_eq!(longest_common_prefix(&["a"]), "a");
312 assert_eq!(longest_common_prefix(&["", "abc"]), "");
313 assert_eq!(longest_common_prefix(&["abc", "abc"]), "abc");
314 }
315
316 #[test]
317 fn sozlarni_teskarilash() {
318 assert_eq!(reverse_words(""), "");
319 assert_eq!(reverse_words("bitta"), "bitta");
320 }
321}