do_memory_core/patterns/
similarity.rs1use crate::types::TaskContext;
4
5pub(super) fn sequence_similarity(seq1: &[String], seq2: &[String]) -> f32 {
7 if seq1.is_empty() && seq2.is_empty() {
8 return 1.0;
9 }
10 if seq1.is_empty() || seq2.is_empty() {
11 return 0.0;
12 }
13
14 let distance = edit_distance(seq1, seq2);
15 let max_len = seq1.len().max(seq2.len());
16
17 1.0 - (distance as f32 / max_len as f32)
18}
19
20fn edit_distance(seq1: &[String], seq2: &[String]) -> usize {
27 let (s1, s2) = if seq1.len() < seq2.len() {
29 (seq1, seq2)
30 } else {
31 (seq2, seq1)
32 };
33
34 let len1 = s1.len();
35 let len2 = s2.len();
36
37 if len1 == 0 {
39 return len2;
40 }
41
42 let mut prev_row: Vec<usize> = (0..=len1).collect();
43 let mut curr_row = vec![0; len1 + 1];
44
45 for j in 1..=len2 {
46 curr_row[0] = j;
47 for i in 1..=len1 {
48 let cost = usize::from(s1[i - 1] != s2[j - 1]);
49 curr_row[i] = (prev_row[i] + 1)
50 .min(curr_row[i - 1] + 1)
51 .min(prev_row[i - 1] + cost);
52 }
53 std::mem::swap(&mut prev_row, &mut curr_row);
54 }
55
56 prev_row[len1]
57}
58
59pub(super) fn string_similarity(s1: &str, s2: &str) -> f32 {
61 if s1.is_empty() && s2.is_empty() {
62 return 1.0;
63 }
64 if s1.is_empty() || s2.is_empty() {
65 return 0.0;
66 }
67
68 let chars1: Vec<char> = s1.chars().collect();
69 let chars2: Vec<char> = s2.chars().collect();
70
71 let distance = char_edit_distance(&chars1, &chars2);
72 let max_len = chars1.len().max(chars2.len());
73
74 1.0 - (distance as f32 / max_len as f32)
75}
76
77fn char_edit_distance(chars1: &[char], chars2: &[char]) -> usize {
84 let (s1, s2) = if chars1.len() < chars2.len() {
86 (chars1, chars2)
87 } else {
88 (chars2, chars1)
89 };
90
91 let len1 = s1.len();
92 let len2 = s2.len();
93
94 if len1 == 0 {
96 return len2;
97 }
98
99 let mut prev_row: Vec<usize> = (0..=len1).collect();
100 let mut curr_row = vec![0; len1 + 1];
101
102 for j in 1..=len2 {
103 curr_row[0] = j;
104 for i in 1..=len1 {
105 let cost = usize::from(s1[i - 1] != s2[j - 1]);
106 curr_row[i] = (prev_row[i] + 1)
107 .min(curr_row[i - 1] + 1)
108 .min(prev_row[i - 1] + cost);
109 }
110 std::mem::swap(&mut prev_row, &mut curr_row);
111 }
112
113 prev_row[len1]
114}
115
116pub(super) fn tool_sequence_similarity(
118 tools1: &[String],
119 ctx1: &TaskContext,
120 tools2: &[String],
121 ctx2: &TaskContext,
122) -> f32 {
123 let sequence_similarity = sequence_similarity(tools1, tools2);
124 let context_similarity = context_similarity(ctx1, ctx2);
125 sequence_similarity * 0.7 + context_similarity * 0.3
126}
127
128pub(super) fn decision_point_similarity(
130 cond1: &str,
131 act1: &str,
132 ctx1: &TaskContext,
133 cond2: &str,
134 act2: &str,
135 ctx2: &TaskContext,
136) -> f32 {
137 let condition_sim = string_similarity(cond1, cond2);
138 let action_sim = string_similarity(act1, act2);
139 let context_sim = context_similarity(ctx1, ctx2);
140 condition_sim * 0.4 + action_sim * 0.4 + context_sim * 0.2
141}
142
143pub(super) fn error_recovery_similarity(
145 err1: &str,
146 steps1: &[String],
147 ctx1: &TaskContext,
148 err2: &str,
149 steps2: &[String],
150 ctx2: &TaskContext,
151) -> f32 {
152 let error_sim = string_similarity(err1, err2);
153 let steps_sim = sequence_similarity(steps1, steps2);
154 let context_sim = context_similarity(ctx1, ctx2);
155 error_sim * 0.4 + steps_sim * 0.4 + context_sim * 0.2
156}
157
158pub(super) fn context_pattern_similarity(
160 feat1: &[String],
161 rec1: &str,
162 feat2: &[String],
163 rec2: &str,
164) -> f32 {
165 let features_sim = sequence_similarity(feat1, feat2);
166 let approach_sim = string_similarity(rec1, rec2);
167 features_sim * 0.6 + approach_sim * 0.4
168}
169
170pub(super) fn context_similarity(ctx1: &TaskContext, ctx2: &TaskContext) -> f32 {
172 let mut score = 0.0;
173 let mut weight_sum = 0.0;
174
175 if ctx1.domain == ctx2.domain {
177 score += 0.4;
178 }
179 weight_sum += 0.4;
180
181 match (&ctx1.language, &ctx2.language) {
183 (Some(l1), Some(l2)) if l1 == l2 => score += 0.3,
184 (None, None) => score += 0.15, _ => {}
186 }
187 weight_sum += 0.3;
188
189 if !ctx1.tags.is_empty() || !ctx2.tags.is_empty() {
191 let common_tags: Vec<_> = ctx1.tags.iter().filter(|t| ctx2.tags.contains(t)).collect();
192
193 let total_unique_tags = ctx1
194 .tags
195 .iter()
196 .chain(ctx2.tags.iter())
197 .collect::<std::collections::HashSet<_>>()
198 .len();
199
200 if total_unique_tags > 0 {
201 let jaccard = common_tags.len() as f32 / total_unique_tags as f32;
202 score += jaccard * 0.3;
203 }
204 }
205 weight_sum += 0.3;
206
207 if weight_sum > 0.0 {
208 score / weight_sum
209 } else {
210 0.0
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_sequence_similarity() {
220 let seq1 = vec!["a".to_string(), "b".to_string(), "c".to_string()];
221 let seq2 = vec!["a".to_string(), "b".to_string(), "c".to_string()];
222
223 assert_eq!(sequence_similarity(&seq1, &seq2), 1.0);
224
225 let seq3 = vec!["a".to_string(), "b".to_string(), "d".to_string()];
226 let sim = sequence_similarity(&seq1, &seq3);
227 assert!(sim > 0.6 && sim < 0.7);
229 }
230
231 #[test]
232 fn test_string_similarity() {
233 assert_eq!(string_similarity("hello", "hello"), 1.0);
234 assert_eq!(string_similarity("", ""), 1.0);
235 assert_eq!(string_similarity("abc", ""), 0.0);
236
237 let sim = string_similarity("hello", "hallo");
239 assert!(sim > 0.7 && sim < 0.9);
240 }
241
242 #[test]
243 fn test_context_similarity() {
244 let ctx1 = TaskContext {
245 domain: "web-api".to_string(),
246 language: Some("rust".to_string()),
247 tags: vec!["async".to_string(), "http".to_string()],
248 ..Default::default()
249 };
250
251 let ctx2 = TaskContext {
252 domain: "web-api".to_string(),
253 language: Some("rust".to_string()),
254 tags: vec!["async".to_string(), "rest".to_string()],
255 ..Default::default()
256 };
257
258 let similarity = context_similarity(&ctx1, &ctx2);
259
260 assert!(similarity > 0.7);
262 }
263}