Add map/reduce and filter functionality for the Array class
authorGunnar Beutner <gunnar.beutner@netways.de>
Fri, 29 Jul 2016 08:47:13 +0000 (10:47 +0200)
committerGunnar Beutner <gunnar.beutner@netways.de>
Fri, 29 Jul 2016 08:48:15 +0000 (10:48 +0200)
fixes #12247

doc/19-library-reference.md
lib/base/array-script.cpp
lib/base/array.hpp

index 3d1f13654beb84d1dcbe76079173f7cab6ca75dc..99664fb03c4defc1691b7efcb4553ed80f565425 100644 (file)
@@ -692,6 +692,43 @@ Signature:
 
 Returns a new array with all elements of the current array in reverse order.
 
+### <a id="array-map"></a> Array#map
+
+Signature:
+
+    function map(func);
+
+Calls `func(element)` for each of the elements in the array and returns
+a new array containing the return values of these function calls.
+
+### <a id="array-reduce"></a> Array#reduce
+
+Signature:
+
+    function reduce(func);
+
+Reduces the elements of the array into a single value by calling the provided
+function `func` as `func(a, b)` repeatedly where `a` is the previous result of
+function call (null initially) and `b` is an element of the array.
+
+### <a id="array-filter"> Array#filter
+
+Signature:
+
+    function filter(func);
+
+Returns a copy of the array containing only the elements for which `func(element)`
+is true.
+
+### <a id="array-filter"> Array#unique
+
+Signature:
+
+    function unique();
+
+Returns a copy of the array with all duplicate elements removed. The original order
+of the array is not preserved.
+
 ## <a id="dictionary-type"></a> Dictionary type
 
 Inherits methods from the [Object type](19-library-reference.md#object-type).
index c8e4731e2a12d8b102bacb85eebe2789adf3d29b..c0482312a1fa14b5a06174f0cac37e3bbfa8c988 100644 (file)
@@ -22,6 +22,7 @@
 #include "base/functionwrapper.hpp"
 #include "base/scriptframe.hpp"
 #include "base/objectlock.hpp"
+#include "base/exception.hpp"
 #include <boost/foreach.hpp>
 
 using namespace icinga;
@@ -94,6 +95,11 @@ static Array::Ptr ArraySort(const std::vector<Value>& args)
                ObjectLock olock(arr);
                std::sort(arr->Begin(), arr->End());
        } else {
+               Function::Ptr function = args[0];
+
+               if (vframe->Sandboxed && !function->IsSideEffectFree())
+                       BOOST_THROW_EXCEPTION(ScriptError("Sort function must be side-effect free."));
+
                ObjectLock olock(arr);
                std::sort(arr->Begin(), arr->End(), boost::bind(ArraySortCmp, args[0], _1, _2));
        }
@@ -137,6 +143,86 @@ static Array::Ptr ArrayReverse(void)
        return self->Reverse();
 }
 
+static Array::Ptr ArrayMap(const Function::Ptr& function)
+{
+       ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
+       Array::Ptr self = static_cast<Array::Ptr>(vframe->Self);
+
+       if (vframe->Sandboxed && !function->IsSideEffectFree())
+               BOOST_THROW_EXCEPTION(ScriptError("Map function must be side-effect free."));
+
+       Array::Ptr result = new Array();
+
+       ObjectLock olock(self);
+       BOOST_FOREACH(const Value& item, self) {
+               ScriptFrame uframe;
+               std::vector<Value> args;
+               args.push_back(item);
+               result->Add(function->Invoke(args));
+       }
+
+       return result;
+}
+
+static Value ArrayReduce(const Function::Ptr& function)
+{
+       ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
+       Array::Ptr self = static_cast<Array::Ptr>(vframe->Self);
+
+       if (vframe->Sandboxed && !function->IsSideEffectFree())
+               BOOST_THROW_EXCEPTION(ScriptError("Reduce function must be side-effect free."));
+
+       Value result;
+
+       ObjectLock olock(self);
+       BOOST_FOREACH(const Value& item, self) {
+               ScriptFrame uframe;
+               std::vector<Value> args;
+               args.push_back(result);
+               args.push_back(item);
+               result = function->Invoke(args);
+       }
+
+       return result;
+}
+
+static Array::Ptr ArrayFilter(const Function::Ptr& function)
+{
+       ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
+       Array::Ptr self = static_cast<Array::Ptr>(vframe->Self);
+
+       if (vframe->Sandboxed && !function->IsSideEffectFree())
+               BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free."));
+
+       Array::Ptr result = new Array();
+
+       ObjectLock olock(self);
+       BOOST_FOREACH(const Value& item, self) {
+               ScriptFrame uframe;
+               std::vector<Value> args;
+               args.push_back(item);
+               if (function->Invoke(args))
+                       result->Add(item);
+       }
+
+       return result;
+}
+
+static Array::Ptr ArrayUnique(void)
+{
+       ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
+       Array::Ptr self = static_cast<Array::Ptr>(vframe->Self);
+
+       std::set<Value> result;
+
+       ObjectLock olock(self);
+       BOOST_FOREACH(const Value& item, self) {
+               result.insert(item);
+       }
+
+       return Array::FromSet(result);
+}
+
 Object::Ptr Array::GetPrototype(void)
 {
        static Dictionary::Ptr prototype;
@@ -154,6 +240,10 @@ Object::Ptr Array::GetPrototype(void)
                prototype->Set("shallow_clone", new Function(WrapFunction(ArrayShallowClone), true));
                prototype->Set("join", new Function(WrapFunction(ArrayJoin), true));
                prototype->Set("reverse", new Function(WrapFunction(ArrayReverse), true));
+               prototype->Set("map", new Function(WrapFunction(ArrayMap), true));
+               prototype->Set("reduce", new Function(WrapFunction(ArrayReduce), true));
+               prototype->Set("filter", new Function(WrapFunction(ArrayFilter), true));
+               prototype->Set("unique", new Function(WrapFunction(ArrayUnique), true));
        }
 
        return prototype;
index 5981ab01e636b69b703a87f48fe5774dae0cea31..0d50be8f2a818d90aeda326e2d198d7637352626 100644 (file)
@@ -118,6 +118,15 @@ public:
                return std::set<T>(Begin(), End());
        }
 
+       template<typename T>
+       static Array::Ptr FromSet(const std::set<T>& v)
+       {
+               Array::Ptr result = new Array();
+               ObjectLock olock(result);
+               std::copy(v.begin(), v.end(), std::back_inserter(result->m_Data));
+               return result;
+       }
+
        virtual Object::Ptr Clone(void) const override;
 
        Array::Ptr Reverse(void) const;