snippetcppMinor
Convert sequence of number into display string
Viewed 0 times
numberconvertintosequencedisplaystring
Problem
My requirement is to convert a sequence of numbers into a display string as below.
Example
Input
1,2,3,4,5,7,7,8,8,8,10,15,10,11,12,88,87,86
Output
1-5,7-8,10-12,15,86-88
Example
Input
1,2,3,4,5,7,7,8,8,8,10,15,10,11,12,88,87,86
Output
1-5,7-8,10-12,15,86-88
void makeDisplayString(std::vector& vector, std::wstring& string)
{
if(vector.empty()) return; // If empty do nothing
std::sort(vector.begin(),vector.end()); // Sort vector first
int& preValue =vector.at(0);
string.append(std::to_wstring(preValue));
bool continueFlag = false; // Flag to check if continuation of value
for(auto const & num : vector)
{
if(preValue ==num) continue; // if same number as previous , Skip it
if(preValue+1 == num) // If number is one more than privious value
{
preValue = num;
continueFlag=true; // set continuation flag and continue rest part
continue;
}
if(continueFlag)
{
// We reach here only if neither number is save as previous nor it is continous of numbering
// But befor this number there was a continous numbering
string.push_back(L'-');
string.append(std::to_wstring(preValue));
string.push_back(L',');
preValue=num;
string.append(std::to_wstring(num));
continueFlag = false;
continue;
}
// We reach here only if neither number is save as previous nor it is continous of numbering
// Also befor this number there was no continous numbering
preValue = num;
string.push_back(L',');
string.append(std::to_wstring(num));
}
// This is to handle a sequence ending with continous number
if(continueFlag)
{
string.push_back(L'-');
string.append(std::to_wstring(*vector.rbegin()));
}
}Solution
- Separate data representation from actual output, using standard algorithms where possible.
make_ranges below takes input sequence array and returns limits of all ranges found, in two different sequences from, to:template
void make_ranges(F& from, T& to, A array)
{
auto diff = array;
std::sort(array.begin(), array.end());
std::adjacent_difference(array.begin(), array.end(), diff.begin());
std::vector idx(array.size());
std::iota(idx.begin(), idx.end(), 0);
auto sep = [&diff] (size_t i) { return diff[i] > 1; };
for(auto p = idx.begin(); p != idx.end();)
{
from.push_back(array[*p]);
p = std::find_if(++p, idx.end(), sep);
to.push_back(array[*(p - 1)]);
}
}- For most generic use, use a stream for output:
template
void print_range(S& stream, I i, J j)
{
stream
void print_ranges(S& stream, const F& from, const T& to)
{
auto i = from.begin();
auto j = to.begin();
if (i != from.end())
print_range(stream, i++, j++);
while (i != from.end())
{
stream << ",";
print_range(stream, i++, j++);
}
}- For convenient use, provide a streaming operator interface:
template
S& operator& ranges)
{
typename std::decay::type from, to;
make_ranges(from, to, std::get(ranges));
print_ranges(stream, from, to);
return stream;
}where
array_ranges is a helper struct holding a sequence and indicating that its ranges should be streamed out instead of the sequence itself. Given a sequence, an array_ranges instance is made easily via helper function ranges:template
struct array_ranges : std::tuple { using std::tuple::tuple; };
template
array_ranges ranges(A&& a) { return array_ranges{std::forward(a)}; }Example
Now the above can be used as
std::vector arr{1,2,3,4,5,7,7,8,8,8,10,15,10,11,12,88,87,86};
std::cout << arr << std::endl;
std::cout << ranges(arr) << std::endl;See live example.
If you want to print the ranges into a string, just use an
std::ostringstream.Discussion
With the above separation, you can do more advanced tasks like extracting ranges from arbitrary sequences, processing them (e.g. filtering or merging two sets of ranges), and finally printing them.
If you don't need any extra processing, this separation is more costly than direct printing since it stores intermediate results in arrays
from, to. Using std::adjacent_difference introduces another intermediate result.It is possible to save the extra cost of "intermediate results" while keeping the same design by more advanced techniques like expression templates, but this goes way beyond this simple task.
I would personally prefer a clean design to optimal performance for this task. This makes code more readable and maintainable. Also, with a bit more abstraction, you may find that parts of the code above can be reused elsewhere (especially
print_ranges, but I don't want to go too far).Another choice you could follow is to have an
std::pair or (better) a dedicated data structure representing one range. A single sequence would then be enough instead of two sequences from, to, but processing such a sequence would be a bit more involved.Of course, you are free to flat everything into a single call without intermediates if you prefer.
Code Snippets
template<typename F, typename T, typename A>
void make_ranges(F& from, T& to, A array)
{
auto diff = array;
std::sort(array.begin(), array.end());
std::adjacent_difference(array.begin(), array.end(), diff.begin());
std::vector<size_t> idx(array.size());
std::iota(idx.begin(), idx.end(), 0);
auto sep = [&diff] (size_t i) { return diff[i] > 1; };
for(auto p = idx.begin(); p != idx.end();)
{
from.push_back(array[*p]);
p = std::find_if(++p, idx.end(), sep);
to.push_back(array[*(p - 1)]);
}
}template<typename S, typename I, typename J>
void print_range(S& stream, I i, J j)
{
stream << *i;
if (*j != *i)
stream << "-" << *j;
}
template<typename S, typename F, typename T>
void print_ranges(S& stream, const F& from, const T& to)
{
auto i = from.begin();
auto j = to.begin();
if (i != from.end())
print_range(stream, i++, j++);
while (i != from.end())
{
stream << ",";
print_range(stream, i++, j++);
}
}template<typename S, typename A>
S& operator<<(S& stream, const array_ranges<A>& ranges)
{
typename std::decay<A>::type from, to;
make_ranges(from, to, std::get<0>(ranges));
print_ranges(stream, from, to);
return stream;
}template<typename A>
struct array_ranges : std::tuple<A> { using std::tuple<A>::tuple; };
template<typename A>
array_ranges<A> ranges(A&& a) { return array_ranges<A>{std::forward<A>(a)}; }std::vector<int> arr{1,2,3,4,5,7,7,8,8,8,10,15,10,11,12,88,87,86};
std::cout << arr << std::endl;
std::cout << ranges(arr) << std::endl;Context
StackExchange Code Review Q#47845, answer score: 8
Revisions (0)
No revisions yet.