8

How do I use the following struct:

struct point 
{
    int x;
    int y;
    int z;
};

as a key for std::map<point, bool>? How should I define operator< for two points?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
szx
  • 6,433
  • 6
  • 46
  • 67
  • How do you want the points to be ordered? Or is the order not important? – Björn Pollex May 24 '11 at 11:07
  • @Space_C0wb0y: It must, at least, be a strict weak ordering. – Lightness Races in Orbit May 24 '11 at 11:08
  • 1
    possible duplicate of [Operator< and strict weak ordering](http://stackoverflow.com/questions/979759/operator-and-strict-weak-ordering) – Lightness Races in Orbit May 24 '11 at 11:09
  • @Space_C0wb0y: The order doesn't matter – szx May 24 '11 at 11:09
  • 1
    And you're sure you wouldn't like points adjacent in your map to be close to each other in this 3D space? Because then you'd need a fractal mapping, which is much more complicated to implement. – leftaroundabout May 24 '11 at 11:14
  • Not a duplicate. The asker of the linked question was unable to use `::boost::make_tuple`, but this OP might be able to. – finnw May 24 '11 at 11:18
  • possible duplicate of [What's the simplest way of defining lexicographic comparison for elements of a class?](http://stackoverflow.com/questions/2500664/whats-the-simplest-way-of-defining-lexicographic-comparison-for-elements-of-a-cl) – Kirill V. Lyadvinsky May 24 '11 at 11:24
  • @finnw: IMO the question "how should I define operator<" isn't "how can I make a pointless wrapper around an object that, for whatever reason, I'm not using in the first place". In fairness this may be more of a related question than a duplicate. – Lightness Races in Orbit May 24 '11 at 11:25
  • 1
    +1 this question anyway for (a) apparently coders with high reputation will routinely implement this wrong, even failing to understand _weak strict ordering_ (b) even I learned a valuable trick in the process \ – sehe May 24 '11 at 11:48

6 Answers6

18

Standard library containers like std::map require that your ordering is a "Strict Weak Ordering", so you have to be very careful when designing one.

The typical approach for a 3-ary tuple looks like this:

bool operator<(const point& other) const
{
   if (x != other.x)
       return (x < other.x);

   if (y != other.y)
       return (y < other.y);

   return (z < other.z);
}

It's like a comparator for just x, but with the difference that if the two xs are the same, you fall through to compare ys. If they are the same, then similarly you fall through to the z comparison.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
6

Of course, boost::tuple<int,int,int> would make this utterly unnecessary.

Update Adding the all-inclusive have-your-cake-and-eat-it-too no-drawback solution here. IMHO it rocks!

#include <boost/tuple/tuple_comparison.hpp>

struct point 
{
    int x, y, z;
    point(int x, int y, int z) : x(x), y(y), z(z) {}
    bool operator<(const point& rhs) const 
    {
        return boost::tie(x, y, z) < boost::tie(rhs.x, rhs.y, rhs.z);
    }
};

Here is the kicker: it all optimizes away. Compile:

int main()
{
    point a(1,2,3), b(3,2,1);
    bool lt = a<b;
    return lt?0:255;
}

With g++ -O2 yields the following in assembly.

main:
.LFB1132:
        pushl   %ebp
        xorl    %eax, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret
.LFE1132:

The compiler was able to optimize the whole of this program to ... return 0 effectively. That is pretty neat.


Here goes the simple answer:

struct point 
{
    point(int x, int y, int z) 
        : x(x), y(y), z(z) {}
    int x;
    int y;
    int z;
    bool operator<(const point& rhs) const 
    {
        if (x<rhs.x) return true;
        if (x==rhs.x) 
        { 
            if (y<rhs.y) return true;
            if (y==rhs.y) return z<rhs.z;
        }
        return false;
    }
};

Also, I would consider looking for a redefinition of my struct that will allow using std::lexicographical_compare

#include <algorithm>

// ...
bool operator<(const point& rhs) const 
{
    return std::lexicographical_compare(&xyz, &xyz+3, &rhs.xyz, &rhs.xyz+3);
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    By adding a constructor to the struct `point`, you made it non-POD. It was previously POD. – Nawaz May 24 '11 at 11:31
  • 1
    @finnw: POD cannot have *user-defined* constructor. See [Can't C++ POD type have any constructor?](http://stackoverflow.com/questions/5442717/cant-c-pod-type-have-any-constructor) – Nawaz May 24 '11 at 11:37
  • @finnw, @Nawaz: Good points about POD struct (removing 'recommendation'). However, I have added a brainwave based on boost::tie() from Boost Tuple. It totally eradicates the competition :) – sehe May 24 '11 at 11:42
  • 1
    @finnw: You shouldn't have deleted the comment. It's helpful for others. Deletion makes the discussion look weird. :P – Nawaz May 24 '11 at 11:44
1

One lazy way to do it:

bool operator<( point const &pt ) const
{
    return ::boost::make_tuple(x,y,z) <
           ::boost::make_tuple(pt.x,pt.y,pt.z);
}
finnw
  • 47,861
  • 24
  • 143
  • 221
  • That gave me a terrific idea, will update my answer in 5 minutes after I test that! – sehe May 24 '11 at 11:30
  • Wouldn't `tie` be better and faster? – Xeo May 24 '11 at 11:32
  • @Xeo, I don't know about that. It creates a temporary struct of references instead of a temporary struct of values. Both could be optimized away by the compiler. But if they are not, then you have introduced an extra layer of indirection. – finnw May 24 '11 at 11:40
  • @Xeo: we think alike once again, see http://stackoverflow.com/questions/6109445/how-to-map-a-bool-to-a-3d-point-struct-with-stdmap/6109591#6109591; That is some feat of optimization there! (_Of course I needed to see the disassembly before posting a solution based on `tie()`_!) – sehe May 24 '11 at 11:45
0

The simplest way to write this is like this:

bool operator<(const point& p) const
{
    if(x < p.x)
    {
        return true;
    }
    else if(p.x < x)
    {
        return false;
    }

    if( y < p.y)
    {
        return true;
    }
    else if( p.y < y)
    {
        return false;
    }

    if( z < p.z)
    {
        return true;
    }
    else if(p.z < z)
    {
        return false;
    }

    return false;

}
Asha
  • 11,002
  • 6
  • 44
  • 66
0

if they're supposed to be ordered in a certain way, you'll want to specify that exact way (for example by euclidean distance from 0/0/0). If all you want is to distinguish different points, you could do something like

x == x2 ? (y == y2 ?  (z < z2) : y < y2) : x < x2 
b.buchhold
  • 3,837
  • 2
  • 24
  • 33
-1

Just a guess guys,

bool operator<(const point& other) const
{
  return( memcmp( (void*) this, (void*) &other, sizeof(point)) < 0);
}

Of course, the order is kind of weird since x,y and z are signed values, but this should be suitable to order into a std::map, isn't it ?

Thomas Vincent
  • 2,214
  • 4
  • 17
  • 25
  • What on earth are you doing? >. – Lightness Races in Orbit May 24 '11 at 18:52
  • I think your should consider this solution. – Thomas Vincent May 25 '11 at 00:16
  • I can provide more code is your are still skeptical about it. But since the `point` struct contain only 3 int and is **not virtual**, the size of the struct is 3 int long and the `(void*)` casting place the compare method on the first int ! Comparing the memory print of 3 **aligned** `int` is a way to compare them with uniqueness guarantee. – Thomas Vincent May 25 '11 at 00:24