Como faço para classificar um vetor de pares com base no segundo elemento do par?

133

Se eu tiver um vetor de pares:

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

Existe uma maneira fácil de classificar a lista em ordem crescente com base no segundo elemento do par?

Eu sei que posso escrever um pequeno objeto de função que fará o trabalho, mas existe uma maneira de usar partes existentes do STL e std::lessfazer o trabalho diretamente?

Edição: Eu entendo que eu posso escrever uma função ou classe separada para passar para o terceiro argumento para classificar. A questão é se posso ou não criar coisas comuns. Eu realmente algo que se parece com:

std::sort(vec.begin(), vec.end(), std::something_magic<int, int, std::less>());
David Norman
fonte
Aqui está um exemplo: <br> std :: sort em um vetor de pares
LeppyR64
1
O c ++ não possui lamdas, portanto você não pode fazer exatamente o que deseja, precisará criar uma função / functor separado. Isso pode ser de uma só linha, então realmente não deve ser um grande problema.
Evan Teran
1
C ++ tem lambdas agora! Woo!
David Poole

Respostas:

212

EDIT : usando c ++ 14, a melhor solução é muito fácil de escrever, graças às lambdas que agora podem ter parâmetros do tipo auto. Esta é a minha solução favorita atual

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

Basta usar um comparador personalizado (é um terceiro argumento opcional 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());

Se você estiver usando um compilador C ++ 11, poderá escrever o mesmo usando lambdas:

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

EDIT : em resposta às suas edições à sua pergunta, eis algumas reflexões ... se você realmente quer ser criativo e poder reutilizar muito esse conceito, basta criar um modelo:

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);
    }
};

então você pode fazer isso também:

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

ou mesmo

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

Embora, para ser honesto, isso seja um pouco exagerado, basta escrever a função de 3 linhas e concluir com ela :-P

Evan Teran
fonte
Tenha em mente que isso é diferente de operator<no pair<T1,T2>. O comparador padrão usa o primeiro e o segundo elemento (caso os primeiros sejam iguais). Aqui apenas o segundo está sendo usado.
Googol
@Googol: Foi exatamente isso que o OP pediu ... Ele disse: "is there and easy way to sort the list in increasing order based on the second element of the pair?"
Evan Teran
@ Evan-Teran, sim, eu sei. Eu estava apenas indicando que, se os dois segundos forem iguais, o resultado poderá ser confuso (se usado para classificação, por exemplo). Esse problema não é afetado pelo comparador padrão porque ele usa o segundo elemento para desempate. Cheguei a essa pergunta procurando um comparador que usasse o segundo elemento como informação principal para comparar, mas também precisava que ele usasse o primeiro para desempate, portanto, gostaria de evitar que outros percam esse ponto (como eu, em fato).
Googol
71

Você pode usar o impulso assim:

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

Não conheço uma maneira padrão de fazer isso de forma igualmente curta e concisa, mas você pode boost::bindentender que é tudo composto por cabeçalhos.

Johannes Schaub - litb
fonte
1
+1 para usar o Boost. Aliás, com um compilador moderno, você provavelmente já pode substituir o boost por std :: tr1, pois ele estará no padrão em breve.
Andreas Magnusson
infelizmente, tentei o mesmo com c ++ 1x std :: bind do tronco do gcc, e falhou porque não tem a opção <for bind. não sei no entanto se o que c ++ 1x diz sobre isso. provavelmente ele lhe diz para usar lambda para que :)
Johannes Schaub - litb
1
Suponho que o impulso não seja padrão, mas é próximo o suficiente. :-)
David Norman
Poste
34

É muito simples você usar a função de classificação do algoritmo e adicionar sua própria função de comparação

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

Agora você deve fazer a comparação com base na segunda seleção, para declarar "myComparison" como

bool myComparison(const pair<int,int> &a,const pair<int,int> &b)
{
       return a.second<b.second;
}
Ezio
fonte
5
Simples e direto ao ponto". Não precisa de reforço ou de uma versão específica do C ++. +1
Thomio 30/11
1
Isso deve ser marcado como a melhor solução. Não precisa do c ++ 14 para implementá-lo.
Kartik Chauhan
Você pode me explicar como essa comparação funciona? Estamos passando dois elementos para o myComparision de cada vez e como ele é capaz de classificar? Além disso, que papel desempenha a.second <b.second?
era s'q
30

Com o C ++ 0x, podemos usar funções 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; } );

Neste exemplo, o tipo de retorno boolé deduzido implicitamente.

Tipos de retorno Lambda

Quando uma função lambda possui uma única instrução, e esta é uma instrução de retorno, o compilador pode deduzir o tipo de retorno. No C ++ 11, §5.1.2 / 4:

...

  • Se a instrução composta for da forma, { return expression ; }o tipo da expressão retornada após a conversão de lvalue para rvalue (4.1), conversão de matriz em ponteiro (4.2) e conversão de função em ponteiro (4.3);
  • caso contrário void,.

Para especificar explicitamente o tipo de retorno, use o formulário []() -> Type { }, como em:

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
fonte
1
Por que if (lhs.second == 0)?
the
Nenhum significado particular; lhs.second < rhs.secondpode retornar trueou falsee o compilador pode deduzir claramente bool. Só queria demonstrar o []() -> Type { }caso.
Andreas Spindler
Pelo menos com clang, essa dedução implícita pode não funcionar corretamente, eu tive que adicionar -> bool como o tipo de retorno lambda para fazê-lo funcionar corretamente.
MoDJ
5

Para algo reutilizável:

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);
    }
};

Você pode usá-lo como

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

ou

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

Você precisaria confiar em um select2nd não padrão

Greg Rogers
fonte
-1

Tente trocar os elementos dos pares para que você possa usá-lo std::sort()normalmente.

hadizadeh.ali
fonte