¿Cómo clasifico un vector de pares basado en el segundo elemento del par?

133

Si tengo un vector de pares:

std::vector<std::pair<int, int> > vec;

¿Hay una manera fácil de ordenar la lista en orden creciente según el segundo elemento del par?

Sé que puedo escribir un pequeño objeto de función que haga el trabajo, pero ¿hay alguna manera de usar las partes existentes de la STL y std::lesshacer el trabajo directamente?

EDITAR: Entiendo que puedo escribir una función o clase separada para pasar al tercer argumento para ordenar. La pregunta es si puedo o no construirlo a partir de material estándar. Realmente tengo algo que se parece a:

std::sort(vec.begin(), vec.end(), std::something_magic<int, int, std::less>());
David Norman
fuente
Aquí hay un ejemplo: <br> std :: ordenar en un vector de pares
LeppyR64
1
c ++ no tiene lamdas, por lo que no puede hacer exactamente lo que quiere, deberá crear una función / functor por separado. Esto puede ser de una sola línea, por lo que realmente no debería ser un gran problema.
Evan Teran el
1
¡C ++ tiene lambdas ahora! ¡Cortejar!
David Poole

Respuestas:

212

EDITAR : usando c ++ 14, la mejor solución es muy fácil de escribir gracias a lambdas que ahora puede tener parámetros de tipo auto. Esta es mi solución favorita actual

std::sort(v.begin(), v.end(), [](auto &left, auto &right) {
    return left.second < right.second;
});

Simplemente use un comparador personalizado (es un tercer argumento opcional para std::sort)

struct sort_pred {
    bool operator()(const std::pair<int,int> &left, const std::pair<int,int> &right) {
        return left.second < right.second;
    }
};

std::sort(v.begin(), v.end(), sort_pred());

Si está utilizando un compilador de C ++ 11, puede escribir lo mismo con lambdas:

std::sort(v.begin(), v.end(), [](const std::pair<int,int> &left, const std::pair<int,int> &right) {
    return left.second < right.second;
});

EDITAR : en respuesta a sus ediciones a su pregunta, aquí hay algunos pensamientos ... si realmente quiere ser creativo y poder reutilizar mucho este concepto, simplemente haga una plantilla:

template <class T1, class T2, class Pred = std::less<T2> >
struct sort_pair_second {
    bool operator()(const std::pair<T1,T2>&left, const std::pair<T1,T2>&right) {
        Pred p;
        return p(left.second, right.second);
    }
};

entonces puedes hacer esto también:

std::sort(v.begin(), v.end(), sort_pair_second<int, int>());

o incluso

std::sort(v.begin(), v.end(), sort_pair_second<int, int, std::greater<int> >());

Aunque para ser honesto, todo esto es un poco exagerado, solo escriba la función de 3 líneas y termine con ella :-P

Evan Teran
fuente
Tenga en cuenta que esto es diferente de operator<adentro pair<T1,T2>. El comparador predeterminado usa el primer y el segundo elemento (en caso de que los primeros sean iguales). Aquí solo se está utilizando el segundo.
Googol
@Googol: Eso es precisamente lo que solicitó el OP ... Dijo: "is there and easy way to sort the list in increasing order based on the second element of the pair?"
Evan Teran el
@ evan-teran, sí, lo sé. Solo estaba indicando que si los elementos de ambos segundos son iguales, entonces el resultado puede ser confuso (si se usa para ordenar, por ejemplo). El comparador predeterminado no sufre este problema porque utiliza el segundo elemento para romper el empate. Llegué a esta pregunta buscando un comparador que usara el segundo elemento como información principal para comparar, pero también necesitaba que usara el primero para romper el empate, por lo que me gustaría evitar que otros pierdan ese punto (como yo, en hecho, lo hizo).
Googol
71

Puedes usar boost así:

std::sort(a.begin(), a.end(), 
          boost::bind(&std::pair<int, int>::second, _1) <
          boost::bind(&std::pair<int, int>::second, _2));

No conozco una forma estándar de hacer esto igualmente corto y conciso, pero puedes elegir boost::bindque todo consiste en encabezados.

Johannes Schaub - litb
fuente
1
+1 por usar Boost. Por cierto, con un compilador moderno, probablemente ya puedas reemplazar boost con std :: tr1, ya que pronto estará en el estándar.
Andreas Magnusson el
desafortunadamente, intenté lo mismo con c ++ 1x std :: bind de gcc trunk, y falló porque no tiene la opción <for bind. sin embargo, no sé si lo que c ++ 1x dice sobre esto. probablemente te dice que uses lambda para eso :)
Johannes Schaub - litb
1
Supongo que impulsar no es estándar, pero está lo suficientemente cerca. :-)
David Norman
Publicé una pregunta de seguimiento a esta respuesta aquí: stackoverflow.com/q/4184917/220636
nabulke
34

Es bastante simple que use la función de clasificación del algoritmo y agregue su propia función de comparación

vector< pair<int,int > > v;
sort(v.begin(),v.end(),myComparison);

Ahora tiene que hacer la comparación basada en la segunda selección, así que declare "myComparison" como

bool myComparison(const pair<int,int> &a,const pair<int,int> &b)
{
       return a.second<b.second;
}
Ezio
fuente
55
Simple y "al punto". No necesita impulso ni una versión específica de C ++. +1
Thomio
1
Esto debe ser marcado como la mejor solución. No necesita c ++ 14 para implementarlo.
Kartik Chauhan
¿Me puede explicar cómo funciona esta comparación? ¿Estamos pasando dos elementos a myComparision a la vez, entonces, cómo puede ordenar? Además, ¿qué papel juega a.second <b.second?
era s'q
30

Con C ++ 0x podemos usar funciones lambda:

using namespace std;
vector<pair<int, int>> v;
        .
        .
sort(v.begin(), v.end(),
     [](const pair<int, int>& lhs, const pair<int, int>& rhs) {
             return lhs.second < rhs.second; } );

En este ejemplo, el tipo de retorno boolse deduce implícitamente.

Tipos de retorno de lambda

Cuando una función lambda tiene una sola declaración, y esta es una declaración de retorno, el compilador puede deducir el tipo de retorno. De C ++ 11, §5.1.2 / 4:

...

  • Si la declaración compuesta tiene la forma { return expression ; }del tipo de la expresión devuelta después de la conversión de valor a valor (4.1), conversión de matriz a puntero (4.2) y conversión de función a puntero (4.3);
  • de lo contrario, void.

Para especificar explícitamente el tipo de retorno, use el formulario []() -> Type { }, como en:

sort(v.begin(), v.end(),
     [](const pair<int, int>& lhs, const pair<int, int>& rhs) -> bool {
             if (lhs.second == 0)
                 return true;
             return lhs.second < rhs.second; } );
Andreas Spindler
fuente
1
¿Por qué if (lhs.second == 0)?
los cerdos
Sin significado particular; lhs.second < rhs.secondpuede regresar trueo falsey el compilador puede deducir claramente bool. Solo quería demostrar el []() -> Type { }caso.
Andreas Spindler
Al menos con clang, esta deducción implícita puede no funcionar correctamente, tuve que agregar -> bool como el tipo de retorno lambda para que funcione correctamente.
MoDJ
5

Por algo reutilizable:

template<template <typename> class P = std::less >
struct compare_pair_second {
    template<class T1, class T2> bool operator()(const std::pair<T1, T2>& left, const std::pair<T1, T2>& right) {
        return P<T2>()(left.second, right.second);
    }
};

Puedes usarlo como

std::sort(foo.begin(), foo.end(), compare_pair_second<>());

o

std::sort(foo.begin(), foo.end(), compare_pair_second<std::less>());
Leon Timmermans
fuente
-1

Intente intercambiar los elementos de los pares para que pueda usarlos std::sort()normalmente.

hadizadeh.ali
fuente