Browse Source

bin/darkwikid: expand lcs design

ghassmo 4 years ago
parent
commit
6a292a4d81
2 changed files with 107 additions and 35 deletions
  1. 102 0
      bin/darkwikid/src/lcs.rs
  2. 5 35
      bin/darkwikid/src/main.rs

+ 102 - 0
bin/darkwikid/src/lcs.rs

@@ -0,0 +1,102 @@
+use crate::{patch::OpMethod, str_to_chars};
+
+pub struct Lcs<'a> {
+    a: Vec<&'a str>,
+    b: Vec<&'a str>,
+    lengths: Vec<Vec<u64>>,
+}
+
+impl<'a> Lcs<'a> {
+    pub fn new(a: &'a str, b: &'a str) -> Self {
+        let a: Vec<_> = str_to_chars(a);
+        let b: Vec<_> = str_to_chars(b);
+        let (na, nb) = (a.len(), b.len());
+
+        let mut lengths = vec![vec![0; nb + 1]; na + 1];
+
+        for (i, ci) in a.iter().enumerate() {
+            for (j, cj) in b.iter().enumerate() {
+                lengths[i + 1][j + 1] = if ci == cj {
+                    lengths[i][j] + 1
+                } else {
+                    lengths[i][j + 1].max(lengths[i + 1][j])
+                }
+            }
+        }
+
+        Self { a, b, lengths }
+    }
+
+    fn op(&self, ops: &mut Vec<OpMethod>, i: usize, j: usize) {
+        if i == 0 && j == 0 {
+            return
+        }
+
+        if i == 0 {
+            ops.push(OpMethod::Insert(self.b[j - 1].to_string()));
+            self.op(ops, i, j - 1);
+        } else if j == 0 {
+            ops.push(OpMethod::Delete((1) as _));
+            self.op(ops, i - 1, j);
+        } else if self.a[i - 1] == self.b[j - 1] {
+            ops.push(OpMethod::Retain((1) as _));
+            self.op(ops, i - 1, j - 1);
+        } else if self.lengths[i - 1][j] > self.lengths[i][j - 1] {
+            ops.push(OpMethod::Delete((1) as _));
+            self.op(ops, i - 1, j);
+        } else {
+            ops.push(OpMethod::Insert(self.b[j - 1].to_string()));
+            self.op(ops, i, j - 1);
+        }
+    }
+
+    pub fn ops(&self) -> Vec<OpMethod> {
+        let mut ops = vec![];
+        self.op(&mut ops, self.a.len(), self.b.len());
+        ops.reverse();
+        ops
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_lcs() {
+        let lcs = Lcs::new("hello", "test hello");
+        assert_eq!(
+            lcs.ops(),
+            vec![
+                OpMethod::Insert("t".into()),
+                OpMethod::Insert("e".into()),
+                OpMethod::Insert("s".into()),
+                OpMethod::Insert("t".into()),
+                OpMethod::Insert(" ".into()),
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+            ]
+        );
+
+        let lcs = Lcs::new("hello world", "hello");
+        assert_eq!(
+            lcs.ops(),
+            vec![
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+                OpMethod::Retain(1),
+                OpMethod::Delete(1),
+                OpMethod::Delete(1),
+                OpMethod::Delete(1),
+                OpMethod::Retain(1),
+                OpMethod::Delete(1),
+                OpMethod::Delete(1),
+                OpMethod::Delete(1),
+            ]
+        );
+    }
+}

+ 5 - 35
bin/darkwikid/src/main.rs

@@ -37,9 +37,11 @@ use darkfi::{
 };
 
 mod jsonrpc;
+mod lcs;
 mod patch;
 
 use jsonrpc::JsonRpcInterface;
+use lcs::Lcs;
 use patch::{OpMethod, Patch};
 
 type Patches = (Vec<Patch>, Vec<Patch>, Vec<Patch>, Vec<Patch>);
@@ -119,40 +121,6 @@ fn str_to_chars(s: &str) -> Vec<&str> {
     s.graphemes(true).collect::<Vec<&str>>()
 }
 
-fn lcs(a: &str, b: &str) -> Vec<OpMethod> {
-    let a: Vec<_> = str_to_chars(a);
-    let b: Vec<_> = str_to_chars(b);
-    let (na, nb) = (a.len(), b.len());
-
-    let mut lengths = vec![vec![0; nb + 1]; na + 1];
-
-    for (i, ci) in a.iter().enumerate() {
-        for (j, cj) in b.iter().enumerate() {
-            lengths[i + 1][j + 1] =
-                if ci == cj { lengths[i][j] + 1 } else { lengths[i][j + 1].max(lengths[i + 1][j]) }
-        }
-    }
-
-    let mut result = Vec::new();
-    let (mut i, mut j) = (na, nb);
-    while i > 0 && j > 0 {
-        if a[i - 1] == b[j - 1] {
-            result.push(OpMethod::Retain((1) as _));
-            i -= 1;
-            j -= 1;
-        } else if lengths[i - 1][j] > lengths[i][j - 1] {
-            result.push(OpMethod::Delete((1) as _));
-            i -= 1;
-        } else {
-            result.push(OpMethod::Insert(b[j - 1].to_string()));
-            j -= 1;
-        }
-    }
-
-    result.reverse();
-    result
-}
-
 fn path_to_id(path: &str) -> String {
     let mut hasher = sha2::Sha256::new();
     hasher.update(path);
@@ -383,7 +351,9 @@ impl Darkwiki {
                 }
 
                 // check the differences with LCS algorithm
-                let lcs_ops = lcs(&local_patch.to_string(), edit);
+                let local_patch_str = local_patch.to_string();
+                let lcs = Lcs::new(&local_patch_str, edit);
+                let lcs_ops = lcs.ops();
 
                 // add the change ops to the new patch
                 for op in lcs_ops {