diff --git a/Framework/Kernel/inc/MantidKernel/Strings.h b/Framework/Kernel/inc/MantidKernel/Strings.h
index 3a346ee494cb414299327765c591f2ee638ecab7..ce9e578c9587f7bf550df00fdd96ca89d60832cc 100644
--- a/Framework/Kernel/inc/MantidKernel/Strings.h
+++ b/Framework/Kernel/inc/MantidKernel/Strings.h
@@ -5,10 +5,12 @@
 // Includes
 //----------------------------------------------------------------------
 #include "MantidKernel/DllConfig.h"
+#include "MantidKernel/StringTokenizer.h"
 #include "MantidKernel/System.h"
 
-#include <map>
+#include <boost/lexical_cast.hpp>
 #include <iosfwd>
+#include <map>
 #include <set>
 #include <sstream>
 #include <string>
@@ -249,12 +251,96 @@ MANTID_KERNEL_DLL int isMember(const std::vector<std::string> &group,
                                const std::string &candidate);
 
 /// Parses a number range, e.g. "1,4-9,54-111,3,10", to the vector containing
-/// all the elements
-/// within the range
+/// all the elements within the range
 MANTID_KERNEL_DLL std::vector<int>
 parseRange(const std::string &str, const std::string &elemSep = ",",
            const std::string &rangeSep = "-");
 
+/// Parses unsigned integer groups, e.g. "1+2,4-7,9,11" to a nested vector
+/// structure.
+template <typename Integer>
+std::vector<std::vector<Integer>> parseGroups(const std::string &str) {
+  std::vector<std::vector<Integer>> groups;
+
+  // Local helper functions.
+  auto translateAdd = [&groups](const std::string &str) {
+    const auto tokens = Kernel::StringTokenizer(
+        str, "+", Kernel::StringTokenizer::TOK_TRIM |
+                               Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
+    std::vector<Integer> group;
+    group.reserve(tokens.count());
+    for (const auto &t : tokens) {
+      // add this number to the group we're about to add
+      group.emplace_back(boost::lexical_cast<Integer>(t));
+    }
+    groups.emplace_back(std::move(group));
+  };
+
+  auto translateSumRange = [&groups](const std::string &str) {
+    // add a group with the numbers in the range
+    const auto tokens = Kernel::StringTokenizer(
+        str, "-", Kernel::StringTokenizer::TOK_TRIM |
+                               Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
+    if (tokens.count() != 2)
+      throw std::runtime_error("Malformed range (-) operation.");
+    Integer first = boost::lexical_cast<Integer>(tokens[0]);
+    Integer last = boost::lexical_cast<Integer>(tokens[1]);
+    if (first > last)
+      std::swap(first, last);
+    // add all the numbers in the range to the output group
+    std::vector<Integer> group;
+    group.reserve(last - first + 1);
+    for (Integer i = first; i <= last; ++i)
+      group.emplace_back(i);
+    if (!group.empty())
+      groups.emplace_back(std::move(group));
+  };
+
+  auto translateRange = [&groups](const std::string &str) {
+    // add a group per number
+    const auto tokens = Kernel::StringTokenizer(
+        str, ":", Kernel::StringTokenizer::TOK_TRIM |
+                               Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
+    if (tokens.count() != 2)
+      throw std::runtime_error("Malformed range (:) operation.");
+    Integer first = boost::lexical_cast<Integer>(tokens[0]);
+    Integer last = boost::lexical_cast<Integer>(tokens[1]);
+    if (first > last)
+      std::swap(first, last);
+    // add all the numbers in the range to separate output groups
+    for (Integer i = first; i <= last; ++i) {
+      groups.emplace_back(1, i);
+    }
+  };
+
+  try {
+    // split into comma separated groups, each group potentially containing
+    // an operation (+-:) that produces even more groups.
+    const auto tokens = StringTokenizer(
+        str, ",",
+        StringTokenizer::TOK_TRIM | StringTokenizer::TOK_IGNORE_EMPTY);
+    for (const auto &token : tokens) {
+      // Look for the various operators in the string. If one is found then
+      // do the necessary translation into groupings.
+      if (token.find('+') != std::string::npos) {
+        translateAdd(token);
+      } else if (token.find('-') != std::string::npos) {
+        translateSumRange(token);
+      } else if (token.find(':') != std::string::npos) {
+        translateRange(token);
+      } else if (!token.empty()) {
+        // contains a single number, just add it as a new group
+        groups.emplace_back(1, boost::lexical_cast<Integer>(token));
+      }
+    }
+  } catch (boost::bad_lexical_cast &) {
+    throw std::runtime_error("Cannot parse numbers from string: '" +
+                             str + "'");
+  }
+
+  return groups;
+}
+
 /// Extract a line from input stream, discarding any EOL characters encountered
 MANTID_KERNEL_DLL std::istream &extractToEOL(std::istream &is,
                                              std::string &str);
diff --git a/Framework/Kernel/test/StringsTest.h b/Framework/Kernel/test/StringsTest.h
index b1fcb44db56539ec59eacfa3d3035e8b242ec5bd..71b19fe68d4e4f79400aa4e76a396e3da4a957b1 100644
--- a/Framework/Kernel/test/StringsTest.h
+++ b/Framework/Kernel/test/StringsTest.h
@@ -487,6 +487,82 @@ public:
                             std::string("Range boundaries are reversed: 5-1"));
   }
 
+  void test_parseGroups_emptyString() {
+    std::vector<std::vector<int>> result;
+    TS_ASSERT_THROWS_NOTHING(result = parseGroups<int>(""))
+    TS_ASSERT(result.empty());
+  }
+
+  void test_parseGroups_comma() {
+    std::vector<std::vector<int>> result;
+    TS_ASSERT_THROWS_NOTHING(result = parseGroups<int>("7,13"))
+    std::vector<std::vector<int>> expected{{std::vector<int>(1, 7), std::vector<int>(1, 13)}};
+    TS_ASSERT_EQUALS(result, expected)
+  }
+
+  void test_parseGroups_plus() {
+    std::vector<std::vector<int>> result;
+    TS_ASSERT_THROWS_NOTHING(result = parseGroups<int>("7+13"))
+    std::vector<std::vector<int>> expected{{std::vector<int>()}};
+    expected.front().emplace_back(7);
+    expected.front().emplace_back(13);
+    TS_ASSERT_EQUALS(result, expected)
+
+  }
+
+  void test_parseGroups_dash() {
+    std::vector<std::vector<int>> result;
+    TS_ASSERT_THROWS_NOTHING(result = parseGroups<int>("7-13"))
+    std::vector<std::vector<int>> expected{{std::vector<int>()}};
+    for (int i = 7; i <= 13; ++i) {
+      expected.front().emplace_back(i);
+    }
+    TS_ASSERT_EQUALS(result, expected)
+  }
+
+  void test_parseGroups_complexExpression() {
+    std::vector<std::vector<int>> result;
+    TS_ASSERT_THROWS_NOTHING(result = parseGroups<int>("1,4+5+8,7-13,1"))
+    std::vector<std::vector<int>> expected;
+    expected.emplace_back(1, 1);
+    expected.emplace_back();
+    expected.back().emplace_back(4);
+    expected.back().emplace_back(5);
+    expected.back().emplace_back(8);
+    expected.emplace_back();
+    for (int i = 7; i <= 13; ++i) {
+      expected.back().emplace_back(i);
+    }
+    expected.emplace_back(1, 1);
+    TS_ASSERT_EQUALS(result, expected)
+  }
+
+  void test_parseGroups_acceptsWhitespace() {
+    std::vector<std::vector<int>> result;
+    TS_ASSERT_THROWS_NOTHING(result = parseGroups<int>(" 1\t, 4 +  5\t+ 8 , 7\t- 13 ,\t1  "))
+    std::vector<std::vector<int>> expected;
+    expected.emplace_back(1, 1);
+    expected.emplace_back();
+    expected.back().emplace_back(4);
+    expected.back().emplace_back(5);
+    expected.back().emplace_back(8);
+    expected.emplace_back();
+    for (int i = 7; i <= 13; ++i) {
+      expected.back().emplace_back(i);
+    }
+    expected.emplace_back(1, 1);
+    TS_ASSERT_EQUALS(result, expected)
+  }
+
+  void test_parseGroups_throwsWhenInputContainsNonnumericCharacters() {
+    TS_ASSERT_THROWS_EQUALS(parseGroups<int>("a"), const std::runtime_error &e, e.what(), std::string("Cannot parse numbers from string: 'a'"))
+  }
+
+  void test_parseGroups_throwsWhenOperationsAreInvalid() {
+    TS_ASSERT_THROWS_EQUALS(parseGroups<int>("-1"), const std::runtime_error &e, e.what(), std::string("Malformed range (-) operation."))
+    TS_ASSERT_THROWS_EQUALS(parseGroups<int>(":1"), const std::runtime_error &e, e.what(), std::string("Malformed range (:) operation."))
+  }
+
   void test_toString_vector_of_ints() {
     std::vector<int> sortedInts{1, 2, 3, 5, 6, 8};
     auto result = toString(sortedInts);