文字列 - 検索 - Shift-And アルゴリズム

概要

文字列 text, pattern について, text 中で pattern が(連続)部分文字列として初めて出現する位置を返す. 各ビット演算が \(O(1)\) だとするとき, これは text の長さ \(n\) に対して \(O(n)\) で計算完了する.

入出力

pattern の長さは 128 以下 (u128 を使っている為) であることを制約とする.

/// String Serach - Shift-And Algorithm
pub fn shift_and(text: &str, pattern: &str) -> Option<usize> {
    let n = text.len();
    let m = pattern.len();
    assert!(m <= 128);
    let text_chars: Vec<usize> = text.chars().map(|c| c as usize).collect();
    let pattern_chars: Vec<usize> = pattern.chars().map(|c| c as usize).collect();
    let g = 1 << (m - 1);
    // DP
    let mut d = [0_u128; 300];
    for i in 0..m {
        d[pattern_chars[i]] ^= 1 << i
    }
    let mut x = 0;
    for i in 0..n {
        x = (1 | (x << 1)) & d[text_chars[i]];
        if x & g > 0 {
            return Some(i + 1 - m);
        }
    }
    return None;
}

#[cfg(test)]
mod test_shiftand {
    use crate::string::shiftand::*;

    #[test]
    fn it_works() {
        assert_eq!(shift_and(&"abcdefghijklmnopqrstuvwxyz", &"abc"), Some(0));
        assert_eq!(shift_and(&"abcdefghijklmnopqrstuvwxyz", &"z"), Some(25));
        assert_eq!(shift_and(&"abcdefghijklmnopqrstuvwxyz", &"zz"), None);
        assert_eq!(
            shift_and(&"abcdefghijklmnopqrstuvwxyz", &"abcdefghijklmnopqrstuvwxyz"),
            Some(0)
        );
        assert_eq!(
            shift_and(
                &"abcdefghijklmnopqrstuvwxyz",
                &"abcdefghijklmnopqrstuvwxyzz"
            ),
            None
        );
    }

    #[test]
    fn long_text_long_pattern() {
        let mut text = String::new();
        let mut pattern = String::new();
        for _ in 0..200 {
            text.push('a');
        }
        for _ in 0..128 {
            pattern.push('a');
        }
        assert_eq!(shift_and(&text, &pattern), Some(0));
    }
}