logic_mesh/blocks/math/
median.rs

1// Copyright (c) 2022-2023, Radu Racariu.
2
3use uuid::Uuid;
4
5use crate::{
6    base::{
7        block::{Block, BlockDesc, BlockProps, BlockState},
8        input::{input_reader::InputReader, Input, InputProps},
9        output::Output,
10    },
11    blocks::utils::convert_units,
12};
13
14use libhaystack::val::{kind::HaystackKind, Number, Value};
15
16use crate::{blocks::InputImpl, blocks::OutputImpl};
17
18/// Calculates the median of multiple numbers from the 16 inputs
19/// this block has.
20/// The operation would take into account the units of those input's values,
21/// if the units are not convertible, the block would be in an error state.
22#[block]
23#[derive(BlockProps, Debug)]
24#[dis = "Median"]
25#[category = "math"]
26#[input(kind = "Number", count = 16)]
27pub struct Median {
28    #[output(kind = "Number")]
29    pub out: OutputImpl,
30}
31
32impl Block for Median {
33    async fn execute(&mut self) {
34        self.read_inputs_until_ready().await;
35
36        let val = self
37            .inputs()
38            .into_iter()
39            .filter_map(|input| match input.get_value().as_ref() {
40                Some(Value::Number(num)) => Some(*num),
41                _ => None,
42            })
43            .collect::<Vec<Number>>();
44
45        if let Ok(mut numbers) = convert_units(&val) {
46            if self.state() != BlockState::Running {
47                self.set_state(BlockState::Running);
48            }
49
50            numbers.sort();
51
52            let median = if numbers.len() % 2 == 0 {
53                let mid = numbers.len() / 2;
54                (numbers[mid - 1].value + numbers[mid].value) / 2.0
55            } else {
56                let mid = numbers.len() / 2;
57                numbers[mid].value
58            };
59
60            let median = if let Some(Number {
61                value: _,
62                unit: Some(unit),
63            }) = numbers.first()
64            {
65                Number::make_with_unit(median, unit)
66            } else {
67                Number::make(median)
68            };
69
70            self.out.set(median.into());
71        } else {
72            self.set_state(BlockState::Fault);
73        }
74    }
75}
76
77#[cfg(test)]
78mod test {
79
80    use crate::base::block::test_utils::write_block_inputs;
81    use crate::base::input::input_reader::InputReader;
82    use crate::{base::block::Block, blocks::math::Median};
83
84    #[tokio::test]
85    async fn test_median_block() {
86        let mut block = Median::new();
87
88        write_block_inputs(&mut [(block._inputs.get_mut(0).unwrap(), 1.into())]).await;
89        block.read_inputs().await;
90        write_block_inputs(&mut [(block._inputs.get_mut(15).unwrap(), 9.into())]).await;
91
92        block.execute().await;
93        assert_eq!(block.out.value, 5.into());
94
95        write_block_inputs(&mut [(block._inputs.get_mut(1).unwrap(), 1.into())]).await;
96        block.read_inputs().await;
97        write_block_inputs(&mut [(block._inputs.get_mut(2).unwrap(), 2.into())]).await;
98        block.read_inputs().await;
99        write_block_inputs(&mut [(block._inputs.get_mut(3).unwrap(), 3.into())]).await;
100
101        block.execute().await;
102        assert_eq!(block.out.value, 2.into());
103    }
104}