const ADDRESS_UNITS: &[&str] = &[
"丁目", "番地", "番", "号", "丁", "ノ", "の", ];
const KANJI_DIGITS: &[char] = &['一', '二', '三', '四', '五', '六', '七', '八', '九'];
const KANJI_UNITS: &[char] = &['十', '百', '千'];
#[inline]
fn is_kanji_numeral(ch: char) -> bool {
KANJI_DIGITS.contains(&ch) || KANJI_UNITS.contains(&ch)
}
fn kanji_to_arabic(s: &str) -> Option<u32> {
if s.is_empty() {
return None;
}
let mut result: u32 = 0;
let mut current: u32 = 0;
for ch in s.chars() {
match ch {
'一' => current += 1,
'二' => current += 2,
'三' => current += 3,
'四' => current += 4,
'五' => current += 5,
'六' => current += 6,
'七' => current += 7,
'八' => current += 8,
'九' => current += 9,
'十' => {
let multiplier = if current == 0 { 1 } else { current };
result += multiplier * 10;
current = 0;
}
'百' => {
let multiplier = if current == 0 { 1 } else { current };
result += multiplier * 100;
current = 0;
}
'千' => {
let multiplier = if current == 0 { 1 } else { current };
result += multiplier * 1000;
current = 0;
}
_ => return None, }
}
result += current;
if result == 0 {
None
} else {
Some(result)
}
}
fn address_unit_at(s: &str, pos: usize) -> Option<&str> {
let tail = &s[pos..];
for &unit in ADDRESS_UNITS {
if let Some(after_unit) = tail.strip_prefix(unit) {
if unit == "の" || unit == "ノ" {
let next_ch = after_unit.chars().next();
let triggers = next_ch.is_some_and(|c| c.is_ascii_digit() || is_kanji_numeral(c));
if !triggers {
continue;
}
}
return Some(unit);
}
}
None
}
pub fn normalize_address(input: &str) -> String {
let mut output = String::with_capacity(input.len());
let bytes = input.as_bytes();
let len = bytes.len();
let mut pos = 0;
while pos < len {
let run_start = pos;
let mut run_end = pos;
let mut char_iter = input[pos..].char_indices();
loop {
match char_iter.next() {
Some((offset, ch)) if is_kanji_numeral(ch) => {
run_end = pos + offset + ch.len_utf8();
}
_ => break,
}
}
if run_end > run_start {
if let Some(unit) = address_unit_at(input, run_end) {
let kanji_str = &input[run_start..run_end];
if let Some(arabic) = kanji_to_arabic(kanji_str) {
let arabic_str = arabic.to_string();
output.push_str(&arabic_str);
output.push_str(unit);
pos = run_end + unit.len();
continue;
}
}
let first_char = input[run_start..].chars().next().unwrap();
output.push(first_char);
pos = run_start + first_char.len_utf8();
} else {
let ch = input[pos..].chars().next().unwrap();
output.push(ch);
pos += ch.len_utf8();
}
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arabic_basic_digits() {
assert_eq!(kanji_to_arabic("一"), Some(1));
assert_eq!(kanji_to_arabic("九"), Some(9));
}
#[test]
fn arabic_juu_alone() {
assert_eq!(kanji_to_arabic("十"), Some(10));
}
#[test]
fn arabic_juu_prefix() {
assert_eq!(kanji_to_arabic("十五"), Some(15));
assert_eq!(kanji_to_arabic("十二"), Some(12));
}
#[test]
fn arabic_compound() {
assert_eq!(kanji_to_arabic("二十三"), Some(23));
assert_eq!(kanji_to_arabic("三百二十五"), Some(325));
}
#[test]
fn arabic_hyaku() {
assert_eq!(kanji_to_arabic("百"), Some(100));
assert_eq!(kanji_to_arabic("百二十"), Some(120));
}
#[test]
fn arabic_empty() {
assert_eq!(kanji_to_arabic(""), None);
}
#[test]
fn arabic_non_kanji() {
assert_eq!(kanji_to_arabic("abc"), None);
}
#[test]
fn converts_simple_kanji_number() {
assert_eq!(normalize_address("三丁目"), "3丁目");
}
#[test]
fn converts_compound_kanji_number() {
assert_eq!(normalize_address("二十三番地"), "23番地");
}
#[test]
fn converts_full_address() {
assert_eq!(normalize_address("一丁目二番三号"), "1丁目2番3号");
}
#[test]
fn leaves_arabic_unchanged() {
assert_eq!(normalize_address("3丁目4番5号"), "3丁目4番5号");
}
#[test]
fn leaves_non_address_unchanged() {
assert_eq!(normalize_address("渡辺花子"), "渡辺花子");
}
#[test]
fn handles_juu_alone() {
assert_eq!(normalize_address("十番"), "10番");
}
#[test]
fn handles_mixed_input() {
assert_eq!(normalize_address("東京都千代田区一丁目"), "東京都千代田区1丁目");
}
#[test]
fn converts_hyaku_nijuu_go() {
assert_eq!(normalize_address("百二十五号"), "125号");
}
#[test]
fn no_change_already_arabic() {
assert_eq!(normalize_address("3-4-5"), "3-4-5");
}
#[test]
fn mixed_arabic_and_kanji() {
assert_eq!(normalize_address("三丁目4番5号"), "3丁目4番5号");
}
#[test]
fn handles_banchi() {
assert_eq!(normalize_address("二十三番地"), "23番地");
}
}