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