Fan floor by median sensor value within a range

Add action to set the floor speed based on sensors from within a defined
valid range and using their median value. The floor speed is selected
from the first map key entry that the median value is less than where 3
or more sensor values are valid. In the case where less than 3 sensor
values are valid, use the highest valid value and default the floor
speed when 0 sensor values are valid.

Tested:
    Configured wspoon with this action & correct floor resulted:
        Sensor value invalidated outside of range
        Single valid ambient sensor
        Default floor with no valid sensors(kill ambient service)
        Highest value used w/ 2 valid sensors
        Middle value used w/ odd number of valid sensors(median)
        Average value of middle two valid sensors w/ even number(median)

Change-Id: Ia1599ff13e25dbd7caa7b02c9340cc3e1e9947c6
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/utility.cpp b/control/utility.cpp
new file mode 100644
index 0000000..49a8a13
--- /dev/null
+++ b/control/utility.cpp
@@ -0,0 +1,39 @@
+#include <algorithm>
+#include <stdexcept>
+
+#include "utility.hpp"
+
+namespace phosphor
+{
+namespace fan
+{
+namespace control
+{
+namespace utility
+{
+
+int64_t getMedian(std::vector<int64_t>& values)
+{
+    if (values.empty())
+    {
+        throw std::out_of_range("getMedian(): Empty list of values");
+    }
+    const auto oddIt = values.begin() + values.size() / 2;
+    std::nth_element(values.begin(), oddIt, values.end());
+    auto median = *oddIt;
+    // Determine median for even number of values
+    if (values.size() % 2 == 0)
+    {
+        // Use average of middle 2 values for median
+        const auto evenIt = values.begin() + values.size() / 2 - 1;
+        std::nth_element(values.begin(), evenIt, values.end());
+        median = (median + *evenIt) / 2;
+    }
+
+    return median;
+}
+
+} // namespace utility
+} // namespace control
+} // namespace fan
+} // namespace phosphor