0

I have a large number of repeated data transformations that I'd like to DRY up as much as possible. e.g.

struct A {
  first: Option<f32>,
  second: i64,
  ...
}

let data = vec![A{None, 1}, A{Some(2.), 3}, ...];

Currently, I have fairly repetive code to calculate the mean of each field. e.g.

let mean_first = data.iter().filter(|a| a.first.is_some()).map(|a| a.first.unwrap() ).sum::<f32>() / data.length() as f32;
let mean_second = data.iter().map(|a| a.second).sum::<i64>() / data.length() as f32;

In reality, the struct has potentially hundreds of numeric fields of mixed and optional types, and I'd like to compute several types of statistics for each field. Is it possible to define a function that takes a Vec, and the name of a field member of T, and returns the mean for those members and handles both float and integer values, optional or not?

If there were a programmatic way to get a field by knowing it's string name, a solution might look like:

fn mean(vec: Vec<T>, field: String) -> f32 {
  ...
}

let mean_first = mean(data, "first");
let mean_second = mean(data, "second");

or more OO might look like

let mean_first = data.mean("first");
let mean_second = data.mean("second");

If not possible with a function, is a macro a good fit here?

Montana Low
  • 81
  • 1
  • 1
  • 4
  • You can do this to some degree with macros. – Gurwinder Singh Oct 16 '21 at 01:29
  • 2
    The problem is that your fields appear to need different treatment. Like the filtering for the `Option` type... how would a generic macro know what to do with the various fields? Maybe a struct just isn't the right thing to use here and you might want to look into table-style processing tools like the `polars` crate (inspired by the famous `pandas` package in Python). – cadolphs Oct 16 '21 at 01:38
  • For 2D data like this you might want to use polars https://www.pola.rs/. – remykarem Aug 09 '22 at 03:20

1 Answers1

2

One way is to use macros:

macro_rules! mean {
    ($v: expr, $f: expr, $ty: ty) => {
        $v.iter().filter_map($f).sum::<$ty>() / $v.len() as $ty
    };
}

which you can use for example:

struct Foo {
    f: i32,
    g: Option<i32>,
}

fn main() {
    let m = mean!(v, |x| Some(x.f), i32);
    let m = mean!(v, |x| x.g, i32);
    let m = mean!(v, |x| Some(x.f as f32), f32);
    let m = mean!(v, |x| x.g.map(|v| v as f64), f64);
}

Gurwinder Singh
  • 38,557
  • 6
  • 51
  • 76