2

I have the following array of strings:

std::string names_str[7] = {"Alex","Louis","Alex","Simon","Matthew", "Carl", "Simon"};

I want to make a new array of ints, of the same size, and each index element should be equivalent to its string element from the original string array. Ending result should look like this:

int names[7] = {0, 1, 0, 2, 3, 4, 2};

How can I implement an algorithm which would fill my array of ints with numbers in such fashion?

I have started with such pseudo-code, but it absolutely makes no sense so far:

for (int i = 0; i < 7; i++) {
    int counter = 0;
    if names_str[i] has already been used
        names[i] = assign the number
    else
        names[i] = counter;
    counter++;
}
2
  • See std::set or std::map. These containers only accept unique keys (strings). Commented Nov 18, 2018 at 22:17
  • 1
    If you set counter = 0; every time through the loop it's not going to count very far. Commented Nov 18, 2018 at 22:52

3 Answers 3

2

You can use a std::map to keep track of known string counters, eg:

#include <string>
#include <map>

std::string names_str[7] = {"Alex", "Louis", "Alex", "Simon", "Matthew", "Carl", "Simon"};
int names[7];

std::map<std::string, int> counter_map;
int counter = 0;

for (int i = 0; i < 7; ++i)
{
    auto iter = counter_map.find(names_str[i]);
    if (iter == counter_map.end())
        iter = counter_map.insert(std::make_pair(names_str[i], counter++)).first;
    names[i] = iter->second;
}

Live Demo

Alternatively, since insert() returns an iterator to an existing keyed element if the key already exists, you can avoid a redundant search via find():

#include <string>
#include <map>

std::string names_str[7] = {"Alex", "Louis", "Alex", "Simon", "Matthew", "Carl", "Simon"};
int names[7];

std::map<std::string, int> counter_map;
int counter = 0;

for (int i = 0; i < 7; ++i)
{
    auto ret = counter_map.insert(std::make_pair(names_str[i], counter));
    if (ret.second) ++counter;
    names[i] = ret.first->second;
}

Live Demo

Either way, since you want to "transform" an array to another array of the same size, this is a good use case for std::transform():

#include <string>
#include <map>
#include <algorithm>

std::string names_str[7] = {"Alex", "Louis", "Alex", "Simon", "Matthew", "Carl", "Simon"};
int names[7];

std::map<std::string, int> counter_map;
int counter = 0;

std::transform(std::begin(names_str), std::end(names_str), std::begin(names),
    [&](const std::string &name) {
        auto iter = counter_map.find(name);
        if (iter == counter_map.end())
            iter = counter_map.insert(std::make_pair(name, counter++)).first;
        return iter->second;
    }
);

Live demo

#include <string>
#include <map>
#include <algorithm>

std::string names_str[7] = {"Alex", "Louis", "Alex", "Simon", "Matthew", "Carl", "Simon"};
int names[7];

std::map<std::string, int> counter_map;
int counter = 0;

std::transform(std::begin(names_str), std::end(names_str), std::begin(names),
    [&](const std::string &name) {
        auto ret = counter_map.insert(std::make_pair(name, counter));
        if (ret.second) ++counter;
        return ret.first->second;
    }
);

Live Demo

Sign up to request clarification or add additional context in comments.

4 Comments

Found this interesting, too, however, got names = { 0, 1, 0, 3, 4, 5, 3 } in both your suggested solutions. So, we're not quite there.
@Micha you are right, I misinterpreted the result the OP wanted. I have tweaked my examples to produce the wanted result the OP provided.
@RemyLebeau many thanks for your effort! Will analyze all approaches. Using 4th solution works just fine, however, if my array size is defined by a variable: int N = 7; std::string names_str[N] = {..names here..}; int names[N]; then I'm getting an error. "No matching function for call to 'begin...'". How can I get past this?
@weno std::begin() and std::end() work with standard containers and fixed arrays, but not with variable-length arrays (which are non-standard and non-portable). When you need a variable-length array, use std::vector, eg: std::vector<std::string> names_str(N); /* populate names_str[0..N-1] as needed */ std::vector<int> names(N); /* transform names_str[] into names[] as needed */
0

@Remy Lebeau, learned something today, no doubt about it, however, maybe @weno didn't expect a solution (serveral of them) that sophisticated. He came up with C-style arrays, so I wondered, if there's a more C-style solution to the problem at hand.

So, in case somebody is interested, here is my solution (with some monitoring added).

#include <iostream>
#include <string>

int seen(int max,
         std::string const& name,
         std::string const names_str[]
) {
    for(int i = 0; i < max; i++)
        if (name == names_str[i]) 
            return i;

    return -1;
}

int main() {
    std::string names_str[7] = {"Alex", "Louis", "Alex", "Simon", "Matthew",
                                "Carl", "Simon"};
    int names[7]{0};
    int counter {0};

    std::cout << "Monitor:\n";

    for(int i = 0; i < 7; i++) {
        int before {-1};
        if((before = seen(i, names_str[i], names_str)) != -1) { // found!
            std::cout << i << ", " << before << '\n'; // monitor
            names[i] = names[before];
        } else {
            std::cout << i << '\n'; // monitor
            names[i] = counter++;
        }
    }

    std::cout << "Result: \n";

    for(int i = 0; i < 7; i++)
        std::cout << names[i] << '\n';
}

That's it for now. Regards,

Comments

0

Difficult to be as elegant as solutions proposed with map.

However, here is a solution with use of std::vector, based on std::stable_sort.
The idea is simply to detect repetition after the sort.
Once repetitions are detected, a simple loop, analogue to the pseudo code proposed by the OP, allows to get the final indices.

The program is provided hereafter.
It is possible to adapt this method for C-like arrays. Complexity is O(nlogn), so in my opinion it is at least as efficient as solutions proposed with map despite an increased verbosity.
However, efficiency is unlikely to be the most important criteria in such an exercise!

#include    <iostream>
#include    <vector>
#include    <string>
#include    <algorithm>

int main () {
    std::vector<std::string> names {"Alex","Louis","Alex","Simon","Matthew", "Carl", "Simon"};
    std::vector<int> index (names.size());
    for (int i = 0; i < names.size(); i++) index[i] = i;

//  Sort and duplicate index when repetition
    std::stable_sort (index.begin(), index.end(), [&names] (int i, int j) {return names[i] < names[j];});
    std::vector<bool> seen (names.size(), false);
    std::vector<int> index_corrected (names.size());
    index_corrected[0] = index[0];
    for (int i = 1; i < index.size(); i++) {
        if (names[index[i]] == names[index[i-1]]) {
            seen[index[i]] = true;
            index_corrected[index[i]] = index_corrected[i-1];
        } else {
            index_corrected[index[i]] = index[i];
        }
    }

    std::vector<int> index_new (names.size());
    int k = 0;
    for (int i = 0; i < names.size(); i++) {
        if (seen[i]) index_new[i] = index_new[index_corrected[i]];
        else index_new[i] = k++;
    }
    for (auto i: index_new) std::cout << i << " ";
    std::cout << "\n";  
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.