use crate::models::{Config, DateComponent, IsExpected, MonthName, Token};
pub type RawDay = Option<(u8, u8)>;
pub type RawMonth = Option<(u8, Option<MonthName>)>;
pub type RawYear = Option<i32>;
pub fn interpret_tokens(tokens: &[Token], config: &Config) -> (RawDay, RawMonth, RawYear) {
let day_expected = config.day.expected != IsExpected::No;
let month_expected = config.month.expected != IsExpected::No;
let year_expected = config.year.expected != IsExpected::No;
let ordinal_day: Option<u8> = tokens.iter().find_map(|t| match t {
Token::OrdinalDay(n) => Some(*n),
_ => None,
});
let named_month: Option<MonthName> = tokens.iter().find_map(|t| match t {
Token::MonthName(m) => Some(*m),
_ => None,
});
let numerics: Vec<(i16, u8)> = tokens
.iter()
.filter_map(|t| match t {
Token::Numeric(value, digit_count) => Some((*value, *digit_count)),
_ => None,
})
.collect();
let day_anchor: Option<u8> = ordinal_day;
let month_anchor: Option<(u8, Option<MonthName>)> = named_month.map(|m| (m.number(), Some(m)));
let open_slots: Vec<DateComponent> = [
config.component_order.first,
config.component_order.second,
config.component_order.third,
]
.iter()
.filter(|&&component| match component {
DateComponent::Day => day_anchor.is_none() && day_expected,
DateComponent::Month => month_anchor.is_none() && month_expected,
DateComponent::Year => year_expected,
})
.copied()
.collect();
let (numeric_day, numeric_month, numeric_year) =
assign_numerics(&numerics, &open_slots, config);
let raw_day = day_anchor.map(|v| (v, 1u8)).or(numeric_day);
let raw_month = month_anchor.or(numeric_month);
let raw_year = numeric_year;
(
if day_expected { raw_day } else { None },
if month_expected { raw_month } else { None },
if year_expected { raw_year } else { None },
)
}
#[derive(Clone, Debug)]
struct Assignment {
day_index: Option<usize>,
month_index: Option<usize>,
year_index: Option<usize>,
}
impl Assignment {
fn component_count(&self) -> usize {
self.day_index.is_some() as usize
+ self.month_index.is_some() as usize
+ self.year_index.is_some() as usize
}
fn component_for_token(&self, token_index: usize) -> Option<DateComponent> {
if self.day_index == Some(token_index) {
Some(DateComponent::Day)
} else if self.month_index == Some(token_index) {
Some(DateComponent::Month)
} else if self.year_index == Some(token_index) {
Some(DateComponent::Year)
} else {
None
}
}
fn token_for(&self, component: DateComponent, numerics: &[(i16, u8)]) -> Option<(i16, u8)> {
let index = match component {
DateComponent::Day => self.day_index?,
DateComponent::Month => self.month_index?,
DateComponent::Year => self.year_index?,
};
numerics.get(index).copied()
}
}
fn assign_numerics(
numerics: &[(i16, u8)],
open_slots: &[DateComponent],
config: &Config,
) -> (RawDay, RawMonth, RawYear) {
if numerics.is_empty() || open_slots.is_empty() {
return (None, None, None);
}
if numerics.len() == open_slots.len()
&& let Some(direct) = try_config_order_assignment(numerics, open_slots, config)
{
return convert_assignment(&direct, numerics, config);
}
let viable = generate_viable_assignments(numerics, open_slots, config);
if viable.is_empty() {
return (None, None, None);
}
let best = pick_best_assignment(viable, numerics, config);
convert_assignment(&best, numerics, config)
}
fn try_config_order_assignment(
numerics: &[(i16, u8)],
open_slots: &[DateComponent],
config: &Config,
) -> Option<Assignment> {
let mut assignment = Assignment {
day_index: None,
month_index: None,
year_index: None,
};
for (token_index, (&slot, &(value, digit_count))) in
open_slots.iter().zip(numerics.iter()).enumerate()
{
let valid = match slot {
DateComponent::Day => config
.day
.try_as_day_candidate(value, digit_count)
.is_some(),
DateComponent::Month => config
.month
.try_as_month_candidate(value, digit_count)
.is_some(),
DateComponent::Year => config
.year
.try_as_year_candidate(value, digit_count)
.is_some(),
};
if !valid {
return None;
}
match slot {
DateComponent::Day => assignment.day_index = Some(token_index),
DateComponent::Month => assignment.month_index = Some(token_index),
DateComponent::Year => assignment.year_index = Some(token_index),
}
}
Some(assignment)
}
fn convert_assignment(
assignment: &Assignment,
numerics: &[(i16, u8)],
config: &Config,
) -> (RawDay, RawMonth, RawYear) {
let raw_day =
assignment
.token_for(DateComponent::Day, numerics)
.and_then(|(value, digit_count)| {
config
.day
.try_as_day_candidate(value, digit_count)
.map(|v| (v, digit_count))
});
let raw_month = assignment
.token_for(DateComponent::Month, numerics)
.and_then(|(value, digit_count)| {
config
.month
.try_as_month_candidate(value, digit_count)
.map(|number| {
let name = MonthName::try_from(number).ok();
(number, name)
})
});
let raw_year = assignment
.token_for(DateComponent::Year, numerics)
.and_then(|(value, digit_count)| config.year.try_as_year_candidate(value, digit_count));
(raw_day, raw_month, raw_year)
}
fn generate_viable_assignments(
numerics: &[(i16, u8)],
open_slots: &[DateComponent],
config: &Config,
) -> Vec<Assignment> {
let max_token_count = numerics.len().min(open_slots.len());
let mut viable: Vec<Assignment> = Vec::new();
for token_count in (1..=max_token_count).rev() {
for token_indices in combinations(numerics.len(), token_count) {
for slot_indices in combinations(open_slots.len(), token_count) {
let chosen_slots: Vec<DateComponent> =
slot_indices.iter().map(|&i| open_slots[i]).collect();
for token_permutation in permutations(token_count) {
let mut assignment = Assignment {
day_index: None,
month_index: None,
year_index: None,
};
let mut all_valid = true;
for (slot_position, &perm_index) in token_permutation.iter().enumerate() {
let token_index = token_indices[perm_index];
let (value, digit_count) = numerics[token_index];
let slot = chosen_slots[slot_position];
let valid = match slot {
DateComponent::Day => config
.day
.try_as_day_candidate(value, digit_count)
.is_some(),
DateComponent::Month => config
.month
.try_as_month_candidate(value, digit_count)
.is_some(),
DateComponent::Year => config
.year
.try_as_year_candidate(value, digit_count)
.is_some(),
};
if !valid {
all_valid = false;
break;
}
match slot {
DateComponent::Day => assignment.day_index = Some(token_index),
DateComponent::Month => assignment.month_index = Some(token_index),
DateComponent::Year => assignment.year_index = Some(token_index),
}
}
if all_valid {
viable.push(assignment);
}
}
}
}
if !viable.is_empty() {
break;
}
}
viable
}
fn pick_best_assignment(
mut viable: Vec<Assignment>,
numerics: &[(i16, u8)],
config: &Config,
) -> Assignment {
let order = [
config.component_order.first,
config.component_order.second,
config.component_order.third,
];
let slot_validity_count = |(value, digit_count): (i16, u8)| -> usize {
let can_be_day = config
.day
.try_as_day_candidate(value, digit_count)
.is_some();
let can_be_month = config
.month
.try_as_month_candidate(value, digit_count)
.is_some();
let can_be_year = config
.year
.try_as_year_candidate(value, digit_count)
.is_some();
can_be_day as usize + can_be_month as usize + can_be_year as usize
};
let token_valid_for_prescribed = |token_index: usize| -> bool {
let (value, digit_count) = match numerics.get(token_index) {
Some(&t) => t,
None => return false,
};
let prescribed = match order.get(token_index) {
Some(&c) => c,
None => return true, };
match prescribed {
DateComponent::Day => config
.day
.try_as_day_candidate(value, digit_count)
.is_some(),
DateComponent::Month => config
.month
.try_as_month_candidate(value, digit_count)
.is_some(),
DateComponent::Year => config
.year
.try_as_year_candidate(value, digit_count)
.is_some(),
}
};
let mut scored: Vec<(usize, usize, usize, usize, usize, Assignment)> = viable
.drain(..)
.map(|assignment| {
let component_count = assignment.component_count();
let positional_validity_score: usize = [
assignment.day_index,
assignment.month_index,
assignment.year_index,
]
.iter()
.filter_map(|&index| index)
.filter(|&token_index| token_valid_for_prescribed(token_index))
.count();
let unambiguity_score: usize = [
(DateComponent::Day, assignment.day_index),
(DateComponent::Month, assignment.month_index),
(DateComponent::Year, assignment.year_index),
]
.iter()
.filter(|(slot, assigned_index)| {
let Some(assigned_token_index) = assigned_index else {
return false;
};
let valid_token_count = numerics
.iter()
.enumerate()
.filter(|(token_index, token)| {
let (value, digit_count) = **token;
let is_assigned = assignment.component_for_token(*token_index).is_some();
if !is_assigned {
return false;
}
match slot {
DateComponent::Day => config
.day
.try_as_day_candidate(value, digit_count)
.is_some(),
DateComponent::Month => config
.month
.try_as_month_candidate(value, digit_count)
.is_some(),
DateComponent::Year => config
.year
.try_as_year_candidate(value, digit_count)
.is_some(),
}
})
.count();
valid_token_count == 1
&& assignment.component_for_token(*assigned_token_index) == Some(*slot)
})
.count();
let exclusivity_sum: usize = [
assignment.day_index,
assignment.month_index,
assignment.year_index,
]
.iter()
.filter_map(|&index| index)
.map(|token_index| slot_validity_count(numerics[token_index]))
.sum();
let agreement_score: usize = numerics
.iter()
.enumerate()
.filter(|(index, _)| {
let prescribed = match order.get(*index) {
Some(c) => *c,
None => return false,
};
assignment.component_for_token(*index) == Some(prescribed)
})
.count();
(
component_count,
positional_validity_score,
unambiguity_score,
exclusivity_sum,
agreement_score,
assignment,
)
})
.collect();
scored.sort_by(|a, b| {
b.0.cmp(&a.0) .then(b.1.cmp(&a.1)) .then(b.2.cmp(&a.2)) .then(a.3.cmp(&b.3)) .then(b.4.cmp(&a.4)) .then_with(|| {
let earliest_token_index = |assignment: &Assignment| -> usize {
[
assignment.day_index,
assignment.month_index,
assignment.year_index,
]
.iter()
.filter_map(|&i| i)
.sum()
};
let a_earliest = earliest_token_index(&a.5);
let b_earliest = earliest_token_index(&b.5);
a_earliest.cmp(&b_earliest) })
});
let best = scored.remove(0);
best.5
}
fn combinations(total: usize, choose: usize) -> Vec<Vec<usize>> {
if choose == 0 {
return vec![vec![]];
}
if choose > total {
return vec![];
}
let mut result: Vec<Vec<usize>> = Vec::new();
let mut indices: Vec<usize> = (0..choose).collect();
loop {
result.push(indices.clone());
let mut i = choose;
loop {
if i == 0 {
return result;
}
i -= 1;
if indices[i] < total - choose + i {
break;
}
}
indices[i] += 1;
for j in i + 1..choose {
indices[j] = indices[j - 1] + 1;
}
}
}
fn permutations(n: usize) -> Vec<Vec<usize>> {
if n == 0 {
return vec![vec![]];
}
let mut result: Vec<Vec<usize>> = Vec::new();
let mut indices: Vec<usize> = (0..n).collect();
result.push(indices.clone());
loop {
let mut i = n - 1;
while i > 0 && indices[i - 1] >= indices[i] {
i -= 1;
}
if i == 0 {
break;
}
let pivot = i - 1;
let mut j = n - 1;
while indices[j] <= indices[pivot] {
j -= 1;
}
indices.swap(pivot, j);
indices[i..].reverse();
result.push(indices.clone());
}
result
}