///|
pub suberror VectorError {
DimensionMismatch(String)
EmptyVector
InvalidK
ClusterError(String)
IndexError(String)
}
///|
pub impl Show for VectorError with fn output(self, logger) {
match self {
DimensionMismatch(s) => logger.write_string("DimensionMismatch: " + s)
EmptyVector => logger.write_string("EmptyVector")
InvalidK => logger.write_string("InvalidK")
ClusterError(s) => logger.write_string("ClusterError: " + s)
IndexError(s) => logger.write_string("IndexError: " + s)
}
}
///|
pub(all) struct Document {
id : String
vector : Array[Double]
metadata : Array[(String, String)]
}
///|
pub fn Document::new(
id : String,
vector : Array[Double],
metadata : Array[(String, String)],
) -> Document {
{ id, vector, metadata }
}
///|
pub(all) enum DistanceMetric {
Cosine
Euclidean
Manhattan
DotProduct
}
///|
pub fn dot_product(
v1 : Array[Double],
v2 : Array[Double],
) -> Double raise VectorError {
if v1.length() != v2.length() {
raise DimensionMismatch(
"Vector dimensions do not match: " +
v1.length().to_string() +
" vs " +
v2.length().to_string(),
)
}
if v1.length() == 0 {
raise EmptyVector
}
let mut sum = 0.0
for i = 0; i < v1.length(); i = i + 1 {
sum = sum + v1[i] * v2[i]
}
sum
}
///|
pub fn euclidean_distance(
v1 : Array[Double],
v2 : Array[Double],
) -> Double raise VectorError {
if v1.length() != v2.length() {
raise DimensionMismatch(
"Vector dimensions do not match: " +
v1.length().to_string() +
" vs " +
v2.length().to_string(),
)
}
if v1.length() == 0 {
raise EmptyVector
}
let mut sum = 0.0
for i = 0; i < v1.length(); i = i + 1 {
let diff = v1[i] - v2[i]
sum = sum + diff * diff
}
sum.sqrt()
}
///|
pub fn manhattan_distance(
v1 : Array[Double],
v2 : Array[Double],
) -> Double raise VectorError {
if v1.length() != v2.length() {
raise DimensionMismatch(
"Vector dimensions do not match: " +
v1.length().to_string() +
" vs " +
v2.length().to_string(),
)
}
if v1.length() == 0 {
raise EmptyVector
}
let mut sum = 0.0
for i = 0; i < v1.length(); i = i + 1 {
let diff = v1[i] - v2[i]
let abs_diff = if diff < 0.0 { -diff } else { diff }
sum = sum + abs_diff
}
sum
}
///|
pub fn cosine_similarity(
v1 : Array[Double],
v2 : Array[Double],
) -> Double raise VectorError {
let dot = dot_product(v1, v2)
let mut norm1 = 0.0
let mut norm2 = 0.0
for i = 0; i < v1.length(); i = i + 1 {
norm1 = norm1 + v1[i] * v1[i]
norm2 = norm2 + v2[i] * v2[i]
}
if norm1 == 0.0 || norm2 == 0.0 {
return 0.0
}
dot / (norm1.sqrt() * norm2.sqrt())
}
///|
pub fn calculate_distance(
v1 : Array[Double],
v2 : Array[Double],
metric : DistanceMetric,
) -> Double raise VectorError {
match metric {
Cosine => cosine_similarity(v1, v2)
Euclidean => euclidean_distance(v1, v2)
Manhattan => manhattan_distance(v1, v2)
DotProduct => dot_product(v1, v2)
}
}
///|
fn find_str(s : String, sub : String, start : Int) -> Int {
let len_s = s.length()
let len_sub = sub.length()
if len_sub == 0 {
return start
}
for i = start; i <= len_s - len_sub; i = i + 1 {
let mut match_found = true
for j = 0; j < len_sub; j = j + 1 {
if s[i + j] != sub[j] {
match_found = false
break
}
}
if match_found {
return i
}
}
-1
}
///|
fn parse_double(s : String) -> Double {
let trimmed = s.trim().to_owned()
let len = trimmed.length()
if len == 0 {
return 0.0
}
let mut val = 0.0
let mut is_neg = false
let mut idx = 0
if trimmed[0] == '-' {
is_neg = true
idx = 1
} else if trimmed[0] == '+' {
idx = 1
}
while idx < len && trimmed[idx] >= '0' && trimmed[idx] <= '9' {
let digit = (trimmed[idx].to_int() - 48).to_double()
val = val * 10.0 + digit
idx = idx + 1
}
if idx < len && trimmed[idx] == '.' {
idx = idx + 1
let mut factor = 0.1
while idx < len && trimmed[idx] >= '0' && trimmed[idx] <= '9' {
let digit = (trimmed[idx].to_int() - 48).to_double()
val = val + digit * factor
factor = factor * 0.1
idx = idx + 1
}
}
if is_neg {
-val
} else {
val
}
}
///|
pub fn Document::to_json(self : Document) -> String {
let sb = StringBuilder::new()
sb.write_string("{\"id\":\"" + self.id + "\",\"vector\":[")
for i = 0; i < self.vector.length(); i = i + 1 {
if i > 0 {
sb.write_string(",")
}
sb.write_string(self.vector[i].to_string())
}
sb.write_string("],\"metadata\":[")
for i = 0; i < self.metadata.length(); i = i + 1 {
if i > 0 {
sb.write_string(",")
}
sb.write_string(
"[\"" + self.metadata[i].0 + "\",\"" + self.metadata[i].1 + "\"]",
)
}
sb.write_string("]}")
sb.to_string()
}
///|
pub fn Document::from_json(json : String) -> Document raise VectorError {
let id_tag = "\"id\":\""
let id_idx = find_str(json, id_tag, 0)
if id_idx == -1 {
raise IndexError("Missing id field in JSON")
}
let id_start = id_idx + id_tag.length()
let id_end = find_str(json, "\"", id_start)
if id_end == -1 {
raise IndexError("Mismatched quote for id in JSON")
}
let id = json[id_start:id_end].to_owned()
let vec_tag = "\"vector\":["
let vec_idx = find_str(json, vec_tag, 0)
if vec_idx == -1 {
raise IndexError("Missing vector field in JSON")
}
let vec_start = vec_idx + vec_tag.length()
let vec_end = find_str(json, "]", vec_start)
if vec_end == -1 {
raise IndexError("Missing closing bracket for vector in JSON")
}
let vec_content = json[vec_start:vec_end].to_owned()
let vector = []
if vec_content.trim().to_owned().length() > 0 {
let parts = vec_content.split(",")
for p in parts {
vector.push(parse_double(p.to_owned()))
}
}
let meta_tag = "\"metadata\":["
let meta_idx = find_str(json, meta_tag, 0)
if meta_idx == -1 {
raise IndexError("Missing metadata field in JSON")
}
let meta_start = meta_idx + meta_tag.length()
let metadata = []
let mut search_idx = meta_start
while true {
let sub_start_idx = find_str(json, "[\"", search_idx)
if sub_start_idx == -1 || sub_start_idx > json.length() - 5 {
break
}
let key_start = sub_start_idx + 2
let key_end = find_str(json, "\"", key_start)
if key_end == -1 {
break
}
let key = json[key_start:key_end].to_owned()
let val_sep = "\",\""
let val_sep_idx = find_str(json, val_sep, key_end)
if val_sep_idx == -1 {
break
}
let val_start = val_sep_idx + val_sep.length()
let val_end = find_str(json, "\"", val_start)
if val_end == -1 {
break
}
let val = json[val_start:val_end].to_owned()
metadata.push((key, val))
search_idx = val_end + 2
}
{ id, vector, metadata }
}