1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
mod cell;
mod domain;
mod row;
mod row_iterator;

pub use domain::Domain;
pub use row::Row;

use cell::Cell;
use itertools::Itertools;
use row::Input;
use row_iterator::RowIterator;

pub struct DecisionTable {
  columns: Vec<Domain>,
  rows: Vec<Row>,
}

impl DecisionTable {
  /// Create a new DecisionTable with rows having specified number of input columns
  pub fn new<T1, T2>(columns: T2) -> Self
  where
    T1: Into<Domain>,
    T2: IntoIterator<Item = T1>,
  {
    DecisionTable {
      columns: columns.into_iter().map(Into::into).collect(),
      rows: Vec::new(),
    }
  }

  pub fn columns_len(&self) -> usize {
    self.columns.len()
  }

  pub fn rows_len(&self) -> usize {
    self.rows.len()
  }

  /// Iterate through all rows
  pub fn rows<'a>(&'a self) -> impl Iterator<Item = &'a Row> {
    RowIterator::new(&self.rows)
  }

  pub fn contains<'a, T>(&self, input: T) -> bool
  where
    T: Into<&'a Input> + Clone,
  {
    self
      .rows
      .iter()
      .find(|row| row.input_eq(input.clone().into()))
      .is_some()
  }

  pub fn add(&mut self, row: Row) {
    if row.input_len() != self.columns_len() {
      panic!(format!(
        "Invalid row length: expected {}, found {}",
        self.columns_len(),
        row.input_len()
      ))
    }

    for (idx, cell) in row.input_cells().enumerate() {
      if !self.columns[idx].contains(cell) {
        panic!(format!("Invalid value in column {}: {}", idx, cell))
      }
    }

    self.rows.push(row);
  }

  /// Returns `true` if the removed row existed
  pub fn remove(&mut self, row: &Row) -> bool {
    let idx = self.rows.iter().position(|r| r == row);
    if let Some(idx) = idx {
      self.rows.remove(idx);
    }
    idx.is_some()
  }

  /// Returns `true` if the table includes every possible combination of inputs.
  pub fn is_complete(&self) -> bool {
    let missing_inputs: Vec<Input> = self
      .columns
      .iter()
      .map(|domain| domain.iter())
      .multi_cartesian_product()
      .filter(|input| {
        !self.contains(
          &input
            .clone()
            .into_iter()
            .map(|c| c.clone())
            .collect::<Input>(),
        )
      })
      .map(|input| input.into_iter().map(|c| c.clone()).collect())
      .collect();

    missing_inputs.len() == 0
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn add_adds_row() {
    let domain = Domain::from(vec!["cell1", "cell2"]);
    let mut table = DecisionTable::new(vec![domain]);
    let row = Row::new(vec!["cell1"], "output");
    table.add(row);
    assert_eq!(table.rows_len(), 1);
  }

  #[test]
  #[should_panic(expected = "Invalid row length: expected 1, found 2")]
  fn add_panics_when_input_length_is_invalid() {
    let domain = Domain::from(vec!["cell1", "cell2"]);
    let mut table = DecisionTable::new(vec![domain]);
    let row = Row::new(vec!["cell1", "cell2"], "output");
    table.add(row);
  }

  #[test]
  #[should_panic(expected = "Invalid value in column 1: cell3")]
  fn add_panics_when_input_value_is_invalid() {
    let domain = Domain::from(vec!["cell1", "cell2"]);
    let mut table = DecisionTable::new(vec![&domain, &domain]);
    let row = Row::new(vec!["cell1", "cell3"], "output");
    table.add(row);
  }

  #[test]
  fn remove_removes_row() {
    let domain = Domain::from(vec!["cell"]);
    let mut table = DecisionTable::new(vec![domain]);
    let row = Row::new(vec!["cell"], "output");
    table.add(row.clone());
    table.remove(&row);
    assert_eq!(table.rows_len(), 0);
  }

  #[test]
  fn is_complete_returns_true_when_table_is_complete() {
    let domain = Domain::from(vec!["cell1", "cell2"]);
    let mut table = DecisionTable::new(vec![&domain, &domain]);
    table.add(Row::new(vec!["cell1", "cell1"], "output"));
    table.add(Row::new(vec!["cell1", "cell2"], "output"));
    table.add(Row::new(vec!["cell2", "cell1"], "output"));
    table.add(Row::new(vec!["cell2", "cell2"], "output"));
    assert_eq!(table.is_complete(), true);
  }

  #[test]
  fn is_complete_returns_false_when_table_is_incomplete() {
    let domain = Domain::from(vec!["cell1", "cell2"]);
    let mut table = DecisionTable::new(vec![&domain, &domain]);
    table.add(Row::new(vec!["cell1", "cell1"], "output"));
    table.add(Row::new(vec!["cell1", "cell2"], "output"));
    // missing row: table.add(Row::new(vec!["cell2", "cell1"], "output"));
    table.add(Row::new(vec!["cell2", "cell2"], "output"));
    assert_eq!(table.is_complete(), false);
  }
}