Skip to main content

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}