From 6f40ba8f1f61767296912d04622d6d695098d272 Mon Sep 17 00:00:00 2001 From: Nick Draper <nick.draper@stfc.ac.uk> Date: Tue, 4 Jul 2017 11:43:36 +0100 Subject: [PATCH] Shoeten arrays with integer lists re #15164 --- .../Kernel/inc/MantidKernel/PropertyHelper.h | 52 +++++++++--- Framework/Kernel/inc/MantidKernel/Strings.h | 59 ++++++++++++++ Framework/Kernel/test/ArrayPropertyTest.h | 79 ++++++++++++++++++- Framework/Kernel/test/StringsTest.h | 22 ++++++ 4 files changed, 196 insertions(+), 16 deletions(-) diff --git a/Framework/Kernel/inc/MantidKernel/PropertyHelper.h b/Framework/Kernel/inc/MantidKernel/PropertyHelper.h index 397b88b1de7..bb22da4b44e 100644 --- a/Framework/Kernel/inc/MantidKernel/PropertyHelper.h +++ b/Framework/Kernel/inc/MantidKernel/PropertyHelper.h @@ -7,8 +7,11 @@ #endif #include "MantidKernel/OptionalBool.h" +#include "MantidKernel/Strings.h" #include "MantidKernel/StringTokenizer.h" +#include <type_traits> + namespace Mantid { namespace Kernel { @@ -25,21 +28,44 @@ template <typename T> std::string toString(const boost::shared_ptr<T> &value) { throw boost::bad_lexical_cast(); } -/// Specialisation for a property of type std::vector. +/** Specialization for a property of type std::vector of non integral types. +* This will catch Vectors of char, double, float etc. +* This simply concatenates the values using a delimiter +*/ template <typename T> std::string toString(const std::vector<T> &value, - const std::string &delimiter = ",") { - std::stringstream result; - std::size_t vsize = value.size(); - for (std::size_t i = 0; i < vsize; ++i) { - result << value[i]; - if (i + 1 != vsize) - result << delimiter; - } - return result.str(); + const std::string &delimiter = ",", + typename std::enable_if<!(std::is_integral<T>::value && std::is_arithmetic<T>::value)>::type* = 0) { + return Strings::join(value.begin(), value.end(), delimiter); +} + +/** Specialization for a property of type std::vector of integral types. +* This will catch Vectors of int, long, long long etc +* including signed and unsigned types of these. +* This concatenates the values using a delimiter, +* adjacent items that are precisely 1 away from each other +* will be compressed into a list syntax e.g. 1-5. +*/ +template <typename T> +std::string toString(const std::vector<T> &value, + const std::string &delimiter = ",", + typename std::enable_if<std::is_integral<T>::value && std::is_arithmetic<T>::value>::type* = 0) { + return Strings::joinCompress(value.begin(), value.end(), delimiter, "-"); +} + + +/** Explicit specialization for a property of type std::vector<bool>. +* This will catch Vectors of char, double, float etc. +* This simply concatenates the values using a delimiter +*/ +template <> +std::string toString(const std::vector<bool> &value, + const std::string &delimiter, + typename std::enable_if<std::is_same<bool,bool>::value>::type*) { + return Strings::join(value.begin(), value.end(), delimiter); } -/// Specialisation for a property of type std::vector<std::vector>. +/// Specialization for a property of type std::vector<std::vector>. template <typename T> std::string toString(const std::vector<std::vector<T>> &value, const std::string &outerDelimiter = ",", @@ -60,11 +86,11 @@ std::string toString(const std::vector<std::vector<T>> &value, return result.str(); } -/// Specialisation for any type, should be appropriate for properties with a +/// Specialization for any type, should be appropriate for properties with a /// single value. template <typename T> int findSize(const T &) { return 1; } -/// Specialisation for properties that are of type vector. +/// Specialization for properties that are of type vector. template <typename T> int findSize(const std::vector<T> &value) { return static_cast<int>(value.size()); } diff --git a/Framework/Kernel/inc/MantidKernel/Strings.h b/Framework/Kernel/inc/MantidKernel/Strings.h index cb0129f0bcf..b08da50d4f4 100644 --- a/Framework/Kernel/inc/MantidKernel/Strings.h +++ b/Framework/Kernel/inc/MantidKernel/Strings.h @@ -71,6 +71,65 @@ DLLExport std::string join(ITERATOR_TYPE begin, ITERATOR_TYPE end, return output.str(); } +//------------------------------------------------------------------------------------------------ +/** Join a set or vector of (something that turns into a string) together +* into one string, separated by a separator, +* adjacent items that are precisely 1 away from each other +* will be compressed into a list syntax e.g. 1-5. +* Returns an empty string if the range is null. +* Does not add the separator after the LAST item. +* +* For example, join a vector of strings with commas with: +* out = join(v.begin(), v.end(), ", "); +* +* @param begin :: iterator at the start +* @param end :: iterator at the end +* @param separator :: string to append between items. +* @param listSeparator :: string to append between list items. +* @return +*/ +template <typename ITERATOR_TYPE> +DLLExport std::string joinCompress(ITERATOR_TYPE begin, ITERATOR_TYPE end, + const std::string &separator = ",", + const std::string &listSeparator = "-") { + + std::stringstream result; + + ITERATOR_TYPE i; + ITERATOR_TYPE previousValue; + std::string currentSeparator = separator; + for (i = begin; i != end;) { + // was this one higher than the last value + if (i == begin) { + //Special case, always include the first value + result << *i; + } else { + //if it is one higher than the last value + if (*i == (*previousValue + 1)) { + currentSeparator = listSeparator; + } else { + if (currentSeparator == listSeparator) { + //add the last value that was the end of the list + result << currentSeparator; + result << *previousValue; + currentSeparator = separator; + } + // add the current value + result << currentSeparator; + result << *i; + } + } + previousValue = i; + i++; + // if we have got to the end and part of a list output the last value + if ((i == end) && (currentSeparator == listSeparator)) { + result << currentSeparator; + result << *previousValue; + } + } + return result.str(); +} + /// Return a string with all matching occurence-strings MANTID_KERNEL_DLL std::string replace(const std::string &input, const std::string &find_what, diff --git a/Framework/Kernel/test/ArrayPropertyTest.h b/Framework/Kernel/test/ArrayPropertyTest.h index 1805ef6bad8..a55aa5a06fa 100644 --- a/Framework/Kernel/test/ArrayPropertyTest.h +++ b/Framework/Kernel/test/ArrayPropertyTest.h @@ -26,8 +26,8 @@ public: TS_ASSERT(!iProp->documentation().compare("")) TS_ASSERT(typeid(std::vector<int>) == *iProp->type_info()) TS_ASSERT(iProp->isDefault()) - TS_ASSERT(iProp->operator()().empty()) - + TS_ASSERT(iProp->operator()().empty()); + TS_ASSERT(!dProp->name().compare("doubleProp")) TS_ASSERT(!dProp->documentation().compare("")) TS_ASSERT(typeid(std::vector<double>) == *dProp->type_info()) @@ -82,7 +82,7 @@ public: TS_ASSERT_EQUALS(i.operator()()[0], 1); TS_ASSERT_EQUALS(i.operator()()[1], 2); TS_ASSERT_EQUALS(i.operator()()[2], 3); - TS_ASSERT_EQUALS(i.getDefault(), i_stringValue); + TS_ASSERT_EQUALS(i.getDefault(), "1-3"); TS_ASSERT(i.isDefault()); ArrayProperty<int> i2("i", "-1-1"); @@ -301,6 +301,79 @@ public: static_cast<Property *>(0)) } + void testListShortening() { + std::vector<std::string> inputList{ + "1,2,3", + "-1,0,1", + "356,366,367,368,370,371,372,375", + "7,6,5,6,7,8,10", + "1-9998, 9999, 2000, 20002-29999" + }; + std::vector<std::string> resultList{ + "1-3", + "-1-1", + "356,366-368,370-372,375", + "7,6,5-8,10", + "1-9999,2000,20002-29999" + }; + + TSM_ASSERT("Test Failed for vectors of int", listShorteningwithType<int>(inputList, resultList)); + TSM_ASSERT("Test Failed for vectors of long", listShorteningwithType<long>(inputList, resultList)); + TSM_ASSERT("Test Failed for vectors of long long", listShorteningwithType<long long>(inputList, resultList)); + // explicit test for in32_t with matches det_id_t and spec_id_t + TSM_ASSERT("Test Failed for vectors of int32_t", listShorteningwithType<int32_t>(inputList, resultList)); + + //unsigned types + std::vector<std::string> inputListUnsigned{ + "1,2,3", + "356,366,367,368,370,371,372,375", + "7,6,5,6,7,8,10", + "1-9998, 9999, 2000, 20002-29999" + }; + std::vector<std::string> resultListUnsigned{ + "1-3", + "356,366-368,370-372,375", + "7,6,5-8,10", + "1-9999,2000,20002-29999" + }; + TSM_ASSERT("Test Failed for vectors of unsigned int", + listShorteningwithType<unsigned int>(inputListUnsigned, resultListUnsigned)); + TSM_ASSERT("Test Failed for vectors of size_t", + listShorteningwithType<size_t>(inputListUnsigned, resultListUnsigned)); + + //check shortening does not happen for floating point types + std::vector<std::string> inputListFloat{ + "1.0,2.0,3.0", + "1.0,1.5,2.0,3.0", + "-1,0,1", + }; + std::vector<std::string> resultListFloat{ + "1,2,3", + "1,1.5,2,3", + "-1,0,1", + }; + TSM_ASSERT("Test Failed for vectors of float", + listShorteningwithType<float>(inputListFloat, resultListFloat)); + TSM_ASSERT("Test Failed for vectors of double", + listShorteningwithType<double>(inputListFloat, resultListFloat)); + + } + + template <typename T> + bool listShorteningwithType(const std::vector<std::string>& inputList, + const std::vector<std::string>& resultList) { + + bool success = true; + for (size_t i = 0; i < inputList.size(); i++) { + ArrayProperty<T> intListProperty("i", inputList[i]); + TS_ASSERT_EQUALS(intListProperty.value(), resultList[i]); + if (intListProperty.value() != resultList[i]) { + success = false; + } + } + return success; + } + private: ArrayProperty<int> *iProp; ArrayProperty<double> *dProp; diff --git a/Framework/Kernel/test/StringsTest.h b/Framework/Kernel/test/StringsTest.h index e233c2e033b..d3dcc1ef92c 100644 --- a/Framework/Kernel/test/StringsTest.h +++ b/Framework/Kernel/test/StringsTest.h @@ -267,6 +267,28 @@ public: TS_ASSERT_EQUALS(out, "Help,Me,I'm,Stuck,Inside,A,Test"); } + void test_joinCompress() { + + std::vector<std::vector<int>> inputList{ + {1,2,3}, + {-1,0,1}, + {356,366,367,368,370,371,372,375}, + {7,6,5,6,7,8,10} + }; + std::vector<std::string> resultList{ + "1-3", + "-1-1", + "356,366-368,370-372,375", + "7,6,5-8,10" + }; + + for (size_t i = 0; i < inputList.size(); i++) { + const auto& inputVector = inputList[i]; + TS_ASSERT_EQUALS(joinCompress(inputVector.begin(), inputVector.end(),",","-"), resultList[i]); + } + + } + void test_endsWithInt() { TS_ASSERT_EQUALS(endsWithInt("pixel22"), 22); TS_ASSERT_EQUALS(endsWithInt("pixel000123"), 123); -- GitLab