From 7aac9beed3e73f77b354c45d3ac006cc463fb503 Mon Sep 17 00:00:00 2001 From: Ifaz Kabir Date: Thu, 9 Dec 2021 23:09:02 -0700 Subject: [PATCH] Preparing release 0.1.2 --- CHANGES.md | 6 +- dune-project | 2 +- lib/internal/DFA.ml | 175 ++++++++++++++++++----------------- lib/internal/DemarauBVNFA.ml | 63 +++++++++++++ lib/internal/DemarauNFA.ml | 2 +- lib/internal/LevBVNFA.ml | 51 ++++++++++ lib/internal/LevNFA.ml | 2 +- lib/internal/NFA.ml | 18 ++++ lib/internal/bitVec.ml | 12 +++ lib/internal/matcher.ml | 91 ++++++++++++++++++ lib/match.ml | 90 +----------------- test/dem_tests.ml | 22 +++++ test/lev_tests.ml | 4 + 13 files changed, 359 insertions(+), 179 deletions(-) create mode 100644 lib/internal/DemarauBVNFA.ml create mode 100644 lib/internal/LevBVNFA.ml create mode 100644 lib/internal/NFA.ml create mode 100644 lib/internal/matcher.ml diff --git a/CHANGES.md b/CHANGES.md index 5a058d4..031f1a6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## 0.1.2 - 2021-12-10 ### Added - Main page for documentation now explain basic concepts and functionality and contains examples of using the library. +### Changed +- Optimizations + - Now using an NFA that uses bitwise operations for transitions and requires less branching. + ## 0.1.1 - 2021-06-23 ### Added - `ppx_inline_test` is now a test dependency, instead of a full dependency. diff --git a/dune-project b/dune-project index e5ea7da..d9282f9 100644 --- a/dune-project +++ b/dune-project @@ -7,7 +7,7 @@ (maintainers "Ifaz Kabir") (source (github ifazk/mula)) (documentation https://ifazk.github.io/mula/) -(version unreleased) +(version 0.1.2) (package (name mula) diff --git a/lib/internal/DFA.ml b/lib/internal/DFA.ml index e04edd0..c0da655 100644 --- a/lib/internal/DFA.ml +++ b/lib/internal/DFA.ml @@ -1,104 +1,105 @@ -module NFA = LevNFA +module Make (NFA : NFA.NFA_t) = struct -module NFAStateSetSet = struct - include Set.Make(NFA.StateSet) + module NFAStateSetSet = struct + include Set.Make(NFA.StateSet) - let pp_states_set ppf s = - Format.fprintf ppf "{@[%a@]}" - (Format.pp_print_list ~pp_sep:NFA.StateSet.pp_comma NFA.StateSet.pp_states) (to_seq s |> List.of_seq) -end + let pp_states_set ppf s = + Format.fprintf ppf "{@[%a@]}" + (Format.pp_print_list ~pp_sep:NFA.StateSet.pp_comma NFA.StateSet.pp_states) (to_seq s |> List.of_seq) + end -module NFAStateSetMap = struct - include Map.Make(NFA.StateSet) + module NFAStateSetMap = struct + include Map.Make(NFA.StateSet) - let pp_kv_pair ~pp_val ppf (k,v) = - Format.fprintf ppf "@[%a ->@ %a@]" NFA.StateSet.pp_states k pp_val v + let pp_kv_pair ~pp_val ppf (k,v) = + Format.fprintf ppf "@[%a ->@ %a@]" NFA.StateSet.pp_states k pp_val v - let pp_map ~pp_val ppf map = - Format.fprintf ppf "{@[%a@]}" - (Format.pp_print_list ~pp_sep:NFA.StateSet.pp_comma (pp_kv_pair ~pp_val)) (to_seq map |> List.of_seq) -end + let pp_map ~pp_val ppf map = + Format.fprintf ppf "{@[%a@]}" + (Format.pp_print_list ~pp_sep:NFA.StateSet.pp_comma (pp_kv_pair ~pp_val)) (to_seq map |> List.of_seq) + end -module Transitions = struct - include Map.Make(BitVec) + module Transitions = struct + include Map.Make(BitVec) - let pp_kv_pair ~pp_val ppf (k,v) = - Format.fprintf ppf "@[%a ->@ %a@]" BitVec.pp_bv k pp_val v + let pp_kv_pair ~pp_val ppf (k,v) = + Format.fprintf ppf "@[%a ->@ %a@]" BitVec.pp_bv k pp_val v - let pp_map ~pp_val ppf map = - Format.fprintf ppf "[@[%a@]]" - (Format.pp_print_list ~pp_sep:NFA.StateSet.pp_comma (pp_kv_pair ~pp_val)) (to_seq map |> List.of_seq) -end + let pp_map ~pp_val ppf map = + Format.fprintf ppf "[@[%a@]]" + (Format.pp_print_list ~pp_sep:NFA.StateSet.pp_comma (pp_kv_pair ~pp_val)) (to_seq map |> List.of_seq) + end -module DFA = struct + module DFA = struct - type dfa = (NFA.StateSet.t Transitions.t) NFAStateSetMap.t + type dfa = (NFA.StateSet.t Transitions.t) NFAStateSetMap.t - let add_key ~from:(from : NFA.StateSet.t) ~dfa:(dfa : dfa) : dfa = - match NFAStateSetMap.find_opt from dfa with - | None -> NFAStateSetMap.add from (Transitions.empty) dfa - | Some _ -> dfa - - let add_transition ~from:(from : NFA.StateSet.t) (bv : BitVec.t) ~to_:(to_ : NFA.StateSet.t) ~dfa:(dfa : dfa) : dfa = - let dfa = + let add_key ~from:(from : NFA.StateSet.t) ~dfa:(dfa : dfa) : dfa = match NFAStateSetMap.find_opt from dfa with - | None -> NFAStateSetMap.add from (Transitions.singleton bv to_) dfa - | Some trans -> NFAStateSetMap.add from (Transitions.add bv to_ trans) dfa - in - (* make sure the to_ is in the set of keys*) - add_key ~from:to_ ~dfa - - type dula_build = - { marked : NFAStateSetSet.t - ; unmarked : NFAStateSetSet.t - ; k : int - ; dfa : dfa - } - - let build_transitions (dula : dula_build) ~t = - let rec build_transitions ({marked; unmarked; k; dfa} as dula) t n max = - let from = t in - let bv = (BitVec.Bits n) in - let transition : NFA.StateSet.t = NFA.Transitions.all_transitions t bv ~k in - let dfa = add_transition ~from bv ~to_:transition ~dfa in - let unmarked = - if NFAStateSetSet.mem transition marked || NFAStateSetSet.mem transition unmarked then - unmarked + | None -> NFAStateSetMap.add from (Transitions.empty) dfa + | Some _ -> dfa + + let add_transition ~from:(from : NFA.StateSet.t) (bv : BitVec.t) ~to_:(to_ : NFA.StateSet.t) ~dfa:(dfa : dfa) : dfa = + let dfa = + match NFAStateSetMap.find_opt from dfa with + | None -> NFAStateSetMap.add from (Transitions.singleton bv to_) dfa + | Some trans -> NFAStateSetMap.add from (Transitions.add bv to_ trans) dfa + in + (* make sure the to_ is in the set of keys*) + add_key ~from:to_ ~dfa + + type dula_build = + { marked : NFAStateSetSet.t + ; unmarked : NFAStateSetSet.t + ; k : int + ; dfa : dfa + } + + let build_transitions (dula : dula_build) ~t = + let rec build_transitions ({marked; unmarked; k; dfa} as dula) t n max = + let from = t in + let bv = (BitVec.Bits n) in + let transition : NFA.StateSet.t = NFA.Transitions.all_transitions t bv ~k in + let dfa = add_transition ~from bv ~to_:transition ~dfa in + let unmarked = + if NFAStateSetSet.mem transition marked || NFAStateSetSet.mem transition unmarked then + unmarked + else + NFAStateSetSet.add transition unmarked + in + let dula = { dula with dfa; unmarked } in + if n = max then + dula else - NFAStateSetSet.add transition unmarked + build_transitions dula t (n + 1) max in - let dula = { dula with dfa; unmarked } in - if n = max then - dula - else - build_transitions dula t (n + 1) max - in - let BitVec.(Bits max) = - BitVec.ones ~m:(dula.k * 2 + 1) - in - build_transitions dula t 0 max - - let rec build_dfa ({marked; unmarked; k = _; dfa = _} as dula) = - match NFAStateSetSet.max_elt_opt unmarked with - | None -> dula - | Some t -> - let marked = NFAStateSetSet.add t marked in - let unmarked = NFAStateSetSet.remove t unmarked in - let dula = - build_transitions { dula with marked; unmarked } ~t + let BitVec.(Bits max) = + BitVec.ones ~m:(dula.k * 2 + 1) in - build_dfa dula - - let start = NFAStateSetSet.of_list [NFA.StateSet.singleton (Lane 0, Err 0);NFA.StateSet.empty] - - let build_dula ~k = - if (k < 1) || k > 3 then - failwith "build_dula can only be called with 1 <= k <= 3" - else - build_dfa { marked = NFAStateSetSet.empty; unmarked = start; k; dfa = NFAStateSetMap.empty } + build_transitions dula t 0 max + + let rec build_dfa ({marked; unmarked; k = _; dfa = _} as dula) = + match NFAStateSetSet.max_elt_opt unmarked with + | None -> dula + | Some t -> + let marked = NFAStateSetSet.add t marked in + let unmarked = NFAStateSetSet.remove t unmarked in + let dula = + build_transitions { dula with marked; unmarked } ~t + in + build_dfa dula + + let start = NFAStateSetSet.of_list [NFA.StateSet.start;NFA.StateSet.err] + + let build_dula ~k = + if (k < 1) || k > 3 then + failwith "build_dula can only be called with 1 <= k <= 3" + else + build_dfa { marked = NFAStateSetSet.empty; unmarked = start; k; dfa = NFAStateSetMap.empty } - let build_and_print_dula ~k = - let {dfa;_} = build_dula ~k in - NFAStateSetMap.pp_map ~pp_val:(Transitions.pp_map ~pp_val:NFA.StateSet.pp_states) Format.std_formatter dfa; - Format.pp_print_newline Format.std_formatter () + let build_and_print_dula ~k = + let {dfa;_} = build_dula ~k in + NFAStateSetMap.pp_map ~pp_val:(Transitions.pp_map ~pp_val:NFA.StateSet.pp_states) Format.std_formatter dfa; + Format.pp_print_newline Format.std_formatter () + end end diff --git a/lib/internal/DemarauBVNFA.ml b/lib/internal/DemarauBVNFA.ml new file mode 100644 index 0000000..abb8d26 --- /dev/null +++ b/lib/internal/DemarauBVNFA.ml @@ -0,0 +1,63 @@ +module BV = BitVec + +type states = States of (BV.t array) * (BV.t array) + +module StateSet = struct + type t = states + + let start ~k : t = + let arr = Array.make (k + 1) BV.zero in + (* Using k + 1 just to keep things simple *) + let trans = Array.make (k + 1) BV.zero in + let init = BV.snoc_zeros ~m:k (BV.one) in + arr.(0) <- init; + States (arr, trans) + + let _find_index ~f arr : int option = + let rec find_index f arr n len = + if n < len then + begin if f arr.(n) then + Some n + else + find_index f arr (n + 1) len + end + else + None + in + find_index f arr 0 (Array.length arr) + + let min_cost_opt (States (arr,_)) : int option = + _find_index ~f:(fun bv -> BV.non_zero bv) arr + + + let pp_states ppf (States (arr,trans)) = + Format.fprintf ppf + "@[states@ @[%a@]@ transpose @[%a@]@]" + (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.pp_print_char ppf '|') BV.pp_bv) (Array.to_list arr) + (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.pp_print_char ppf '|') BV.pp_bv) (Array.to_list trans) +end + +module Transitions = struct + + let all_transitions (States (input, trans) : StateSet.t) bv ~k : StateSet.t = + let output = Array.make (k + 1) BV.zero in + let out_trans = Array.make (k + 1) BV.zero in + let del_mask = ref BV.zero in + let prev = ref BV.zero in + for i = 0 to k do + let prev_bv = !prev in + let ins_subs = (BV.logor (BV.shift_left prev_bv 1) prev_bv) in + let dels = BV.logand bv !del_mask in + let transpose_transitions = BV.shift_right_logical (BV.logand bv trans.(i)) 1 in + let prev_transitions = BV.logor ins_subs dels in + let self_transitions = BV.logand bv input.(i) in + let transitions = BV.logor (BV.logor prev_transitions self_transitions) transpose_transitions in + let transpose_intermediate = BV.shift_left (BV.logand bv (BV.shift_right_logical prev_bv 1)) 2 in + del_mask := BV.logor (BV.shift_right_logical input.(i) 1) (BV.shift_right_logical !del_mask 1); + prev := input.(i); + out_trans.(i) <- transpose_intermediate; + output.(i) <- transitions + done; + States (output, out_trans) + +end diff --git a/lib/internal/DemarauNFA.ml b/lib/internal/DemarauNFA.ml index 7dcf211..f48cd9c 100644 --- a/lib/internal/DemarauNFA.ml +++ b/lib/internal/DemarauNFA.ml @@ -40,7 +40,7 @@ module StateSet = struct else Some min_cost - let start : t = singleton (State.Std {lane = 0; error = 0}) + let start ~k:_ : t = singleton (State.Std {lane = 0; error = 0}) let err : t = empty diff --git a/lib/internal/LevBVNFA.ml b/lib/internal/LevBVNFA.ml new file mode 100644 index 0000000..3025200 --- /dev/null +++ b/lib/internal/LevBVNFA.ml @@ -0,0 +1,51 @@ +module BV = BitVec + +type states = States of (BV.t array) [@@unboxed] + +module StateSet = struct + type t = states + + let start ~k : t = + let arr = Array.make (k + 1) BV.zero in + let init = BV.snoc_zeros ~m:k (BV.one) in + arr.(0) <- init; + States arr + + let _find_index ~f arr : int option = + let rec find_index f arr n len = + if n < len then + begin if f arr.(n) then + Some n + else + find_index f arr (n + 1) len + end + else + None + in + find_index f arr 0 (Array.length arr) + + let min_cost_opt (States arr) : int option = + _find_index ~f:(fun bv -> BV.non_zero bv) arr + +end + +module Transitions = struct + + let all_transitions (States input : StateSet.t) bv ~k : StateSet.t = + let output = Array.make (k + 1) BV.zero in + let del_mask = ref BV.zero in + let prev = ref BV.zero in + for i = 0 to k do + let prev_bv = !prev in + let ins_subs = (BV.logor (BV.shift_left prev_bv 1) prev_bv) in + let dels = BV.logand bv !del_mask in + let prev_transitions = BV.logor ins_subs dels in + let self_transitions = BV.logand bv input.(i) in + let transitions = BV.logor prev_transitions self_transitions in + del_mask := BV.logor (BV.shift_right_logical input.(i) 1) (BV.shift_right_logical !del_mask 1); + prev := input.(i); + output.(i) <- transitions + done; + States output + +end diff --git a/lib/internal/LevNFA.ml b/lib/internal/LevNFA.ml index 29d0687..c500e8e 100644 --- a/lib/internal/LevNFA.ml +++ b/lib/internal/LevNFA.ml @@ -38,7 +38,7 @@ module StateSet = struct else Some min_cost - let start : t = singleton (Lane 0, Err 0) + let start ~k:_ : t = singleton (Lane 0, Err 0) let err : t = empty diff --git a/lib/internal/NFA.ml b/lib/internal/NFA.ml new file mode 100644 index 0000000..5d4f771 --- /dev/null +++ b/lib/internal/NFA.ml @@ -0,0 +1,18 @@ +module type NFA_t = sig + module State : sig + type t + val compare : t -> t -> int + end + module StateSet : sig + include Set.S with type elt = State.t + + val start : t + val err : t + + val pp_comma : Format.formatter -> unit -> unit + val pp_states : Format.formatter -> t -> unit + end + module Transitions : sig + val all_transitions : StateSet.t -> BitVec.t -> k:int -> StateSet.t + end +end diff --git a/lib/internal/bitVec.ml b/lib/internal/bitVec.ml index 56538f7..5c11696 100644 --- a/lib/internal/bitVec.ml +++ b/lib/internal/bitVec.ml @@ -52,5 +52,17 @@ let snoc_zeros (Bits n) ~m = let zero = (Bits Int.zero) +let one = (Bits Int.one) + +let non_zero (Bits x) = not (Int.equal Int.zero x) + +let logor (Bits x) (Bits y) = Bits (Int.logor x y) + +let logand (Bits x) (Bits y) = Bits (Int.logand x y) + +let shift_right_logical (Bits x) n = Bits (Int.shift_right_logical x n) + +let shift_left (Bits x) n = Bits (Int.shift_left x n) + let pp_bv ppf (Bits n)= Format.fprintf ppf "%o" n diff --git a/lib/internal/matcher.ml b/lib/internal/matcher.ml new file mode 100644 index 0000000..e161065 --- /dev/null +++ b/lib/internal/matcher.ml @@ -0,0 +1,91 @@ +module type S = sig + type ch + type t + + val length : t -> int + val get : t -> int -> ch + val equal : ch -> ch -> bool +end + +module type NFA_t = sig + module StateSet : sig + type t + + val start : k:int -> t + + val min_cost_opt : t -> int option + end + module Transitions : sig + val all_transitions : StateSet.t -> BitVec.t -> k:int -> StateSet.t + end +end + +module Make (St : S) (NFA : NFA_t) = struct + + module GBV = StringOps.BitVecOps (St) + + type nfa_state = {nfa : NFA.StateSet.t option; k : int; str: St.t; str_len: int; fed_so_far: int} + + let start ~k ~str = + if (k < 0) then + failwith "the limit k cannot be negative" + else if (k > ((Sys.int_size - 1) / 2)) then + failwith "the limit k cannot be larger than ((int_size - 1) / 2)" + else + {nfa = Some (NFA.StateSet.start ~k); k; str; str_len = St.length str; fed_so_far = 0 } + + let feed {nfa;k;str;str_len;fed_so_far} ~ch = + let index = fed_so_far + 1 in + if Option.is_none nfa + || index > str_len + k then + {nfa = None; k; str; str_len; fed_so_far = index} + else + let bv = GBV.bit_vec_of ch str ~index ~k in + let nfa = NFA.Transitions.all_transitions (Option.get nfa) bv ~k in + {nfa = Some nfa;k;str;str_len;fed_so_far = index} + + let current_error {nfa;_} : int option = + match nfa with + | None -> None + | Some nfa -> NFA.StateSet.min_cost_opt nfa + + let end_input {nfa;k;str=_;str_len;fed_so_far} : int option = + let size_diff = str_len - fed_so_far in + if Option.is_none nfa (* handles over feeding *) + || size_diff > k then (* handle under feeding *) + None + else + (* add (str_len - fed_so_far + k) many sentinels *) + let sentinels = str_len - fed_so_far + k in + let nfa = + BitVec.pos_fold + ~f:(fun n nfa -> + let bv = GBV.bit_vec_of_sentinel ~str_len ~index:(fed_so_far + 1 + sentinels - n) ~k in + NFA.Transitions.all_transitions nfa bv ~k) + ~init:(Option.get nfa) + sentinels + in + NFA.StateSet.min_cost_opt nfa + + let feed_str nfa_state ~str = + (* TODO early exit *) + let len = St.length str in + BitVec.pos_fold + ~f:(fun n nfa -> feed nfa ~ch:(St.get str (len - n))) + ~init:nfa_state + len + + let get_distance ~k str1 str2 = + let len_diff = + let len1 = St.length str1 in + let len2 = St.length str2 in + Int.abs (len1 - len2) + in + if len_diff > k then + None + else + let start = start ~k ~str:str1 in + let end_ = feed_str start ~str:str2 in + let cost = end_input end_ in + cost +end diff --git a/lib/match.ml b/lib/match.ml index 7ed4d7e..2de3857 100644 --- a/lib/match.ml +++ b/lib/match.ml @@ -9,93 +9,7 @@ module type S = sig val equal : ch -> ch -> bool end -module type NFA_t = sig - module StateSet : sig - type t - - val start : t - val err : t - - val min_cost_opt : t -> int option - val is_err : t -> bool - end - module Transitions : sig - val all_transitions : StateSet.t -> BitVec.t -> k:int -> StateSet.t - end -end - -module MakeMatcher (St : S) (NFA : NFA_t) = struct - - module GBV = StringOps.BitVecOps (St) - - type nfa_state = {nfa : NFA.StateSet.t; k : int; str: St.t; str_len: int; fed_so_far: int} - - let start ~k ~str = - if (k < 0) then - failwith "the limit k cannot be negative" - else if (k > ((Sys.int_size - 1) / 2)) then - failwith "the limit k cannot be larger than ((int_size - 1) / 2)" - else - {nfa = NFA.StateSet.start; k; str; str_len = St.length str; fed_so_far = 0 } - - let feed {nfa;k;str;str_len;fed_so_far} ~ch = - let index = fed_so_far + 1 in - if NFA.StateSet.is_err nfa - || index > str_len + k then - {nfa = NFA.StateSet.err; k; str; str_len; fed_so_far = index} - else - let bv = GBV.bit_vec_of ch str ~index ~k in - let nfa = NFA.Transitions.all_transitions nfa bv ~k in - {nfa;k;str;str_len;fed_so_far = index} - - let current_error {nfa;_} : int option = - if NFA.StateSet.is_err nfa then - None - else - NFA.StateSet.min_cost_opt nfa - - let end_input {nfa;k;str=_;str_len;fed_so_far} : int option = - let size_diff = str_len - fed_so_far in - if NFA.StateSet.is_err nfa (* handles over feeding *) - || size_diff > k then (* handle under feeding *) - None - else - (* add (str_len - fed_so_far + k) many sentinels *) - let sentinels = str_len - fed_so_far + k in - let nfa = - BitVec.pos_fold - ~f:(fun n nfa -> - let bv = GBV.bit_vec_of_sentinel ~str_len ~index:(fed_so_far + 1 + sentinels - n) ~k in - NFA.Transitions.all_transitions nfa bv ~k) - ~init:nfa - sentinels - in - NFA.StateSet.min_cost_opt nfa - - let feed_str nfa_state ~str = - (* TODO early exit *) - let len = St.length str in - BitVec.pos_fold - ~f:(fun n nfa -> feed nfa ~ch:(St.get str (len - n))) - ~init:nfa_state - len - - let get_distance ~k str1 str2 = - let len_diff = - let len1 = St.length str1 in - let len2 = St.length str2 in - Int.abs (len1 - len2) - in - if len_diff > k then - None - else - let start = start ~k ~str:str1 in - let end_ = feed_str start ~str:str2 in - let cost = end_input end_ in - cost -end - module Make (St : S) = struct - module Lev = MakeMatcher (St) (LevNFA) - module Dem = MakeMatcher (St) (DemarauNFA) + module Lev = Matcher.Make (St) (LevBVNFA) + module Dem = Matcher.Make (St) (DemarauBVNFA) end diff --git a/test/dem_tests.ml b/test/dem_tests.ml index 8ea0873..200824a 100644 --- a/test/dem_tests.ml +++ b/test/dem_tests.ml @@ -82,6 +82,28 @@ let%test "no triangle inequality" = get_distance ~k:4 "abcd" "bdac" = Some 4 +(* A delete followed by a transposition has the same error count as a substututione *) + +let%test "2 deletes then transposition match" = + get_distance ~k:4 "abcdef" "aedf" + = Some 3 + +let%test "2 deletes then transposition" = + get_distance ~k:4 "abcd" "dc" + = Some 3 + +let%test "1 delete then transposition" = + get_distance ~k:4 "abc" "cb" + = Some 2 + +let%test "1 substitution then 1 delete" = + get_distance ~k:4 "abc" "cb" + = Some 2 + +let%test "1 substitution then 1 delete then match" = + get_distance ~k:4 "abcd" "cbd" + = Some 2 + (* empty *) let%test "empty-empty" = get_distance ~k:1 "" "" = Some 0 diff --git a/test/lev_tests.ml b/test/lev_tests.ml index 2929ecd..0d70a96 100644 --- a/test/lev_tests.ml +++ b/test/lev_tests.ml @@ -93,3 +93,7 @@ let%test "k=0 empty-a" = let%test "k=0 a-empty" = get_distance ~k:0 "a" "" = None + +let%test "delete match match insert match" = + get_distance ~k:4 "abdc" "bdac" + = Some 2