I must admit that I didn't fully understand the solution presented in the self-answer of OP.
This encouraged me to present a different one.
The general idea is to apply set operations to the individual strings.
All characters of first string are possible candidates to be common. For each other string, if a character of first is not in other string then it is removed from first string. (i.e. a set intersection) Finally, the left characters in first string are that which are common with all other strings.
To make set operations in C++, I could have stored each string in a std::set
to perform std::set_intersection()
afterwards.
Instead, I applied std::sort()
to the first string. The string might have duplicated characters. These are removed by a succeeding call of std::unique()
.
The other strings (2nd, 3rd, etc.) are sorted as well. The call of std::unique()
isn't necessary because the following intersection won't consider any duplicate in the second string as there will never be one in the first string.
For the intersection, I recalled an older answer of mine (to SO: how to find the intersection of two std::set in C++?). I considered to use std::set_intersection()
but IMHO it might not store the result into the range it's reading from. I also didn't want another string copy for each iteration and, hence, used an alternative implementation intersection()
instead.
This is what I got:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
void intersection(std::string &first, const std::string &second)
{
std::string::iterator iter1 = first.begin();
for (std::string::const_iterator iter2 = second.begin();
iter1 != first.end() && iter2 != second.end();) {
if (*iter1 < *iter2) iter1 = first.erase(iter1);
else {
if (!(*iter2 < *iter1)) ++iter1;
++iter2;
}
}
first.erase(iter1, first.end());
}
size_t check(const std::vector<std::string> &sample)
{
// check trivial case (sample empty)
if (sample.empty()) return 0;
std::string first = sample[0];
// sort string characters by their values
std::sort(first.begin(), first.end());
// remove duplicates
std::string::iterator end = std::unique(first.begin(), first.end());
first.erase(end, first.end());
// check the other strings agains first
for (size_t i = 1, n = sample.size(); i < n; ++i) {
std::string other = sample[i];
std::sort(other.begin(), other.end());
intersection(first, other);
}
#if 1 // set 0 to disable diagnostics
// diagnostics
std::cout << '"' << first << "\" ";
#endif // 1
return first.size();
}
int main()
{
std::vector<std::string> samples[] = {
{ "Weeb", "Hello", "Anime" },
{ "llana", "allon", "mellon", "gallon" }
};
// check samples
for (const std::vector<std::string> &sample : samples) {
// print input
std::cout << "check(";
const char *sep = "";
for (const std::string &word : sample) {
std::cout << sep << '"' << word << '"';
sep = ", ";
}
std::cout << "):\n";
// print common characters
std::cout << " Common characters: " << check(sample) << '\n';
}
return 0;
}
Output:
check("Weeb", "Hello", "Anime"):
Common characters: "e" 1
check("llana", "allon", "mellon", "gallon"):
Common characters: "ln" 2
Live Demo on coliru
Notes:
The algorithm doesn't distinguish between letters and any other character. It would work for digits and special characters (and any other character) as well.
According to the previous fact, the algorithm does distinguish lowercase and uppercase letters. I'm not sure whether or not OP intended this nor if OP is aware about this. I found it legal to make it this way if there is no explicit constraint concerning this. (And, it takes off the burden of any concerns regarding encoding, internationalization, and such.)