///|
pub struct DatasetSummary {
  columns : Int
  rows : Int
  means : Array[Double]
  standard_deviations : Array[Double]
  minima : Array[Double]
  maxima : Array[Double]
}

///|
pub fn summarize_covariates(
  covariates : Array[Array[Double]],
) -> DatasetSummary {
  if covariates.length() == 0 {
    return {
      columns: 0,
      rows: 0,
      means: [],
      standard_deviations: [],
      minima: [],
      maxima: [],
    }
  }
  let columns = covariates[0].length()
  let means = Array::make(columns, 0.0)
  let minima = Array::make(columns, 1.7976931348623157e308)
  let maxima = Array::make(columns, -1.7976931348623157e308)
  for row in covariates {
    for j in 0.. maxima[j] {
        maxima[j] = row[j]
      }
    }
  }
  for j in 0.. Array[Double] {
  let result = Array::new(capacity=covariates.length())
  for row in covariates {
    if column >= 0 && column < row.length() {
      result.push(row[column])
    }
  }
  result
}

///|
pub fn center_covariates(
  covariates : Array[Array[Double]],
) -> Array[Array[Double]] {
  let summary = summarize_covariates(covariates)
  let result : Array[Array[Double]] = Array::new(capacity=covariates.length())
  for row in covariates {
    let centered = Array::new(capacity=summary.columns)
    for j in 0.. Array[Array[Double]] {
  let summary = summarize_covariates(covariates)
  let result : Array[Array[Double]] = Array::new(capacity=covariates.length())
  for row in covariates {
    let scaled = Array::new(capacity=summary.columns)
    for j in 0.. Array[Double] {
  let lower = quantile(values, lower_probability)
  let upper = quantile(values, upper_probability)
  let result = Array::new(capacity=values.length())
  for value in values {
    result.push(clamp(value, lower, upper))
  }
  result
}

///|
pub fn rank_transform(values : Array[Double]) -> Array[Double] {
  let result = Array::make(values.length(), 0.0)
  for i in 0.. Array[Array[Double]] {
  if covariates.length() == 0 || degree <= 0 {
    return []
  }
  let result : Array[Array[Double]] = Array::new(capacity=covariates.length())
  for row in covariates {
    let expanded = Array::new()
    for value in row {
      expanded.push(value)
    }
    if degree >= 2 {
      for d in 2..<=degree {
        for value in row {
          expanded.push(@math.pow(value, d.to_double()))
        }
      }
    }
    result.push(expanded)
  }
  result
}

///|
pub fn interaction_features(
  covariates : Array[Array[Double]],
) -> Array[Array[Double]] {
  let result : Array[Array[Double]] = Array::new(capacity=covariates.length())
  for row in covariates {
    let expanded = row.copy()
    for i in 0.. DataSplit {
  let fraction = clamp(validation_fraction, 0.0, 0.9)
  let order = Array::new(capacity=dataset.n())
  for i in 0..