use std::sync::Arc;
use arrow::array::{Array, ArrayRef, StringBuilder};
use crate::rng::Rng;
static FR_TO_EN: &[(&str, &str)] = &[
("Rue", "Street"),
("Avenue", "Avenue"),
("Boulevard", "Boulevard"),
("Place", "Square"),
("Chemin", "Road"),
("Route", "Route"),
("Allée", "Lane"),
("Passage", "Passage"),
("de la", "of the"),
("du", "of the"),
("des", "of the"),
];
pub fn apply_address_scramble(arr: &dyn Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let mut builder = StringBuilder::with_capacity(n, n * 32);
for i in 0..n {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
if s.len() < 2 {
builder.append_value(s);
continue;
}
let parts: Vec<&str> = s.splitn(2, ' ').collect();
if parts.len() >= 2 && parts[0].chars().all(|c| c.is_ascii_digit()) {
let new_num = rng2.next_usize(998) + 1; builder.append_value(format!("{} {}", new_num, parts[1]));
} else {
builder.append_value(s);
}
}
*rng = rng2;
Arc::new(builder.finish())
}
pub fn apply_language_mix(arr: &dyn Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let mut builder = StringBuilder::with_capacity(n, n * 32);
for i in 0..n {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
let mut result = s.to_string();
for &(fr, en) in FR_TO_EN.iter() {
if result.contains(fr) && rng2.next_f64() < 0.3 {
result = result.replacen(fr, en, 1);
}
}
builder.append_value(&result);
}
*rng = rng2;
Arc::new(builder.finish())
}
pub fn apply_postal_corrupt(arr: &dyn Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let mut builder = StringBuilder::with_capacity(n, n * 16);
for i in 0..n {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
let mut chars: Vec<char> = s.chars().collect();
if chars.len() < 5 {
builder.append_value(s);
continue;
}
let digit_count = chars.iter().filter(|c| c.is_ascii_digit()).count();
if digit_count == 0 {
builder.append_value(s);
continue;
}
let target = rng2.next_usize(digit_count);
let pos = chars
.iter()
.enumerate()
.filter(|(_, c)| c.is_ascii_digit())
.nth(target)
.map(|(j, _)| j)
.expect("target < digit_count");
let new_digit = (rng2.next_usize(10) as u8 + 48) as char;
chars[pos] = new_digit;
builder.append_value(chars.into_iter().collect::<String>());
}
*rng = rng2;
Arc::new(builder.finish())
}
#[cfg(test)]
mod tests {
use super::*;
use arrow::array::{AsArray, StringArray};
fn test_rng() -> Rng {
Rng::new(42)
}
fn make_arr(vals: &[&str]) -> ArrayRef {
Arc::new(StringArray::from(vals.to_vec()))
}
#[test]
fn test_address_scramble() {
let arr = make_arr(&["123 Main Street", "42 Rue de Paris", "NoNumber", ""]);
let mut rng = test_rng();
let result = apply_address_scramble(&*arr, &mut rng);
let s = result.as_string::<i32>();
assert_eq!(result.len(), 4);
assert_ne!(s.value(0), "123 Main Street");
assert_ne!(s.value(1), "42 Rue de Paris");
assert_eq!(s.value(2), "NoNumber");
}
#[test]
fn test_language_mix() {
let arr = make_arr(&["Rue de la Paix", "Avenue des Champs", "English Road"]);
let mut rng = test_rng();
let result = apply_language_mix(&*arr, &mut rng);
let s = result.as_string::<i32>();
assert_eq!(result.len(), 3);
assert_ne!(s.value(0), "English Road");
}
#[test]
fn test_postal_corrupt() {
let arr = make_arr(&["75001", "12345", "ABC", "ABCDE"]);
let mut rng = test_rng();
let result = apply_postal_corrupt(&*arr, &mut rng);
let s = result.as_string::<i32>();
assert_eq!(result.len(), 4);
assert_eq!(s.value(2), "ABC");
}
#[test]
fn test_deterministic() {
let arr = make_arr(&["123 Main St", "456 Oak Ave"]);
let a = apply_address_scramble(&*arr, &mut Rng::new(42));
let b = apply_address_scramble(&*arr, &mut Rng::new(42));
let sa = a.as_string::<i32>();
let sb = b.as_string::<i32>();
assert_eq!(sa.value(0), sb.value(0));
}
}