0

I have been working on this problem for sometime now. I have 3 .txt files containing some stuff to read in and arrange within a multimap,

std::multimap<std::string, std::multimap<int, int>>

but if the key already exists, I increment the value and if the key is unique, I put the value in the map.

The 3 .txt files (labelled "x," "y," and "z") contain this:

"x.txt" contains primary keys:

a a a b b b c c c d d d e e e

"y.txt" contains secondary keys:

1 2 2 3 4 4 5 4 4 2 6 6 6 6 6

and "z.txt," which counts the secondary keys, contains:

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

The desired output is this:

1 : 1
2 : 2
3 : 1
4 : 2
5 : 1
4 : 2
2 : 1
6 : 2
6 : 3

Which states that there is one 1 in a, two 2's in a, one 3 in b, two 4's in b... and so on.

My code (below) incorrectly produces this result:

1 : 1
2 : 1
2 : 1
2 : 1
3 : 1
4 : 1
4 : 1
5 : 1
6 : 1
6 : 1
6 : 1

Here is the code: Any suggestions?

//needed to output your result (print map)
template<typename a, typename b>
void print1(const std::multimap<a, b>& thing){
    for (std::multimap<a,b>::const_iterator it = begin(thing); it != thing.end(); it++){
        std::cout << it->first << " : " << it->second << std::endl;
    }
}


std::ifstream X("x.txt");
std::ifstream Y("y.txt");
std::ifstream Z("z.txt");


typedef std::multimap<int, int> Y_Z;
typedef std::multimap<std::string, Y_Z> X_Y_Z;

typedef std::pair<int, int> pair_y_z;
typedef std::pair<std::string, Y_Z> pair_x_y_z;

Y_Z y_z;
X_Y_Z x_y_z;

std::string x_;
int y_;
int z_;


if (X){
    while (X >> x_, Y >> y_, Z >> z_){

        if (x_y_z.empty()) {
            y_z.insert(pair_y_z(y_, z_));
            x_y_z.insert(pair_x_y_z(x_, y_z));
        }
        else {

            X_Y_Z::iterator first_iter = x_y_z.find(x_);
            X_Y_Z::iterator last_iter = x_y_z.upper_bound(x_);

            if (x_y_z.find(x_) == x_y_z.end()) {
                y_z.insert(pair_y_z(y_, z_));
                x_y_z.insert(pair_x_y_z(x_, y_z));
            }
            else{
                for (; first_iter != last_iter; first_iter++){
                    if (x_y_z.find(x_)->second.find(y_) == x_y_z.find(x_)->second.end()){
                        //no match
                        y_z.insert(pair_y_z(y_, z_));
                        x_y_z.insert(pair_x_y_z(x_, y_z));
                        break;
                    }
                    else{
                        //found match
                        x_y_z.find(x_)->second.find(y_)->second += z_;
                        break;
                    }
                }
            }
        }
    }
}

std::cin.get();
print1(y_z);
std::cin.get();
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
Chewco
  • 249
  • 2
  • 10
  • 2
    @Kerrek: He's shown the current and desired outputs, which is one of the ways we encourage clarifying the problem. I still don't understand what role the primary key plays here. – Ben Voigt Jul 06 '14 at 23:04
  • 1
    Even with that, it's not clear what problem the OP is trying to solve. What's the point of the third (z.txt) file? – Chad Jul 06 '14 at 23:09
  • A map won't work for this, your output requires that `5 : 1` for `c` is presented before the `4 : 2` for `c`. – Bill Lynch Jul 06 '14 at 23:18

2 Answers2

2

So. It's totally not clear why you are hurting yourself with this complicated data structure. You've got a key with two parts. So let's make the key to your std::map a std::pair<T1, T2>.

If we do that, the code becomes incredibly simple.

Source Code:

#include <iostream>
#include <fstream>
#include <utility>
#include <map>

int main() {
    std::ifstream X("x.txt");
    std::ifstream Y("y.txt");
    std::ifstream Z("z.txt");

    std::map<std::pair<std::string, int>, int> data;

    std::string x_;
    int y_;
    int z_;

    while (X >> x_, Y >> y_, Z >> z_)
        data[std::make_pair(x_, y_)] += z_;

    for (auto const & element : data)
        std::cout << std::get<1>(element.first) << " : " << element.second << "\n";
}

Output:

1 : 1
2 : 2
3 : 1
4 : 2
4 : 2
5 : 1
2 : 1
6 : 2
6 : 3

Further issues:

There is one discrepancy between my output and the desired output, but I think it's an error in what you expect.

Your current output expects:

{a, 1} : 1
{a, 2} : 2
{b, 3} : 1
{b, 4} : 2
{c, 5} : 1  <--- note that this key is out of order with the next one. 5 > 4.
{c, 4} : 2
{d, 2} : 1
{d, 6} : 2
{e, 6} : 3

But I'm assuming that the keys should all be sorted properly, which is what my output does.

David G
  • 94,763
  • 41
  • 167
  • 253
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • You said: "You've got a key with two parts. So let's make the key to your std::map a std::pair." <--this is pretty cool IMO. This is incredibly elegant and is exactly the solution I was looking for. Your result is even better than the result I gave (ordered correctly). I have one more question: would it be possible to modify the map (decrement values of z) using conditions checking both parts of the key? – Chewco Jul 07 '14 at 00:08
  • Yes, you could. Do you have an example of such a condition? – Bill Lynch Jul 07 '14 at 00:16
  • Well, not anything concrete yet. But something that goes like this: there is a similar (larger/smaller) map called data2 which has exactly the same types (string, int, int) and looks like data2[std::make_pair(a, b)] = c. I want to use int 'c' in data2 to modify int 'z' in data1 if int 'b' matches int 'y.' Like: If (std::get<1>(data1.first) == data2.first) { z -= c; } something crazy like that. – Chewco Jul 07 '14 at 00:36
  • A `std::map` or a `std::multimap` with the current key doesn't allow you to do that very efficiently. If you swapped the key such that it was `{b, a} -> c`, then I think you could. Most likely, you would iterate over `data2`, and then look to see if anything in `data1` matched it, if so, perform your decrement operation. Likely it would look like: `for (...) for (...) if (...) perform_operation;` – Bill Lynch Jul 07 '14 at 00:57
  • I think I can make that work. However, any idea as to how I can access 'z' to decrement (or even delete) it? I am used to using map::find to iterate through maps, but I am a little confused when the map has a "dual" key like this. – Chewco Jul 07 '14 at 03:37
  • When you use find, the resulting type is `std::pair, int>`. So ` x == iterator->first.first`, `y == iterator->first.second`, and `z == iterator->second`. – Bill Lynch Jul 07 '14 at 03:40
0

I don't see anything in your problem description that calls for a multimap. Just use std::map instead.

Also, get rid of y_z. Every time you use it, it appears that you should be using x_y_z[x].

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I ran towards using a multimap because I thought I needed it for the appearance of duplicate keys. But it turns out I could use a pair<> of elements as a single key – Chewco Jul 07 '14 at 00:10