0

I'm having a bit of an issue with custom sorting strings. What I basically have is an std::list of std::pair, whose elements are enum class and std::string. Enum is for colours (those do sort correctly) and strings contain numbers in [2, 10] interval, with additions of letters J, Q, K and A, respectively. As you may have already guessed, it's a deck of cards, that must look like this after sorting: 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A.

What should my lambda function look like in order to achieve this? Or is there another way?

This is what I've compe up with so far:

enum class Colours
{
Spades, Clubs, Hearts, Diamonds
};

typedef std::list<std::pair<Colours, std::string>> Deck;
typedef std::pair<Boje, std::string> Pair;

And here's the actual sorting:

deck.sort( [] (const Pair &x, const Pair &y) -> bool {
        if(x.first == y.first) return x.second < y.second;
        else return x.first < y.first;
    } );

And the current sorting result: 10 2 3 4 5 6 7 8 9 A J K Q

14
  • I just noticed I forgot to provide the results, it's edited. 10 comes before 2, and letters are A J K Q, but need to be J Q K A. Commented May 16, 2018 at 9:26
  • it's normal to use an enum for the card faces also, with a conversion function to convert to strings when needed Commented May 16, 2018 at 9:28
  • I would suggest creating an array/list with the correct order, try to grab the index of the two elements and compare the indices Commented May 16, 2018 at 9:29
  • can the issue be that you are comparing character instead of strings Commented May 16, 2018 at 9:29
  • just use char instead of std::string and it should work. Commented May 16, 2018 at 9:31

5 Answers 5

3

One thing you have going for you is that the first character of each string is unique. This code is untested, but it should be close:

unsigned int face_value(std::string const &face) {
    if(std::isdigit(face[0])) {
        // if the length is 1 we just need the digit
        if(face.length() == 1) {
            return face[0] - '0';
        }
        // if it's not 1, it has to be 10
        return 10;
    }
    switch(face[0]) {
    case 'J':
        return 11;
        break;

    case 'Q':
        return 12;
        break;

    case 'K':
        return 13;
        break;

    case 'A':
        return 14;
        break;

    default:
        assert(false);
        break;
    }
}

bool face_compare(std::string const &first, std::string const &second) {
    return face_value(first) < face_value(second);
}

With the helper functions, the lambda is trivial:

deck.sort( [] (const Pair &x, const Pair &y) -> bool {
    return std::tie(x.first, face_value(x.second)) <
           std::tie(y.first, face_value(y.second));
});

As a side note, my guess is that you want to compare face values before Colours, but I'm sticking with your original logic for now.

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

2 Comments

I'd express it as return std::tie(x.first, face_value(x.second)) < std::tie(y.first, face_value(y.second))
@Caleth - Honestly didn't know tuples supported the boolean operators. Thanks for the suggestion!
3

You can have a lookup of the face values of your cards, expressed as a std::map<std::string, int>. Rather than explicitly specifying the less than of two values, we can use the std::tuple less than, with std::tie.

// throws std::out_of_range if not passed a valid face
int face_value (const std::string & face)
{
    static const std::map<std::string, int> faces {
        { "2", 2 },  { "3", 3 },  { "4", 4 },
        { "5", 5 },  { "6", 6 },  { "7", 7 },
        { "8", 8 },  { "9", 9 },  { "10", 10 },
        { "J", 11 }, { "Q", 12 }, { "K", 13 },
        { "A", 14 },
    }

    return faces.at( face );
}

deck.sort( [] (const Pair &x, const Pair &y) -> bool {
    return std::tie(x.first, face_value(x.second)) < std::tie(y.first, face_value(y.second));
} );

Comments

0

The problem is that you are comparing strings, that is, you compare them in alphabetical order. Let's see which comparisons will yield bad results:

2, 3, 4, 5, 6, 7, 8, 9 vs. 10

J vs A

Q vs K

K vs A

So you will need to write your comparison to check whether any of the values is 10, A or J. If it is 10, check whether the other is a number. If it is A, check whether the other is J or K. If it is K, check whether the other is Q or A. If any of those cases are met, then return the logical value which corresponds to your needs. Otherwise work as you did before:

deck.sort( [] (const Pair &x, const Pair &y) -> bool {
        if(x.first == y.first) {
            //Here you need to implement your custom cases as described above
            return x.second < y.second;
        }
        else return x.first < y.first;
    } );

1 Comment

This is what I eventually ended up doing. I did have to write a whole bunch of ifs, but it works though. Thanks!
0

It is not sorting the way you want because 10 is less than 2 because 1 is less than 2. You might be better off using a class and a comparison operator:

struct Card {
    enum class Suite { Spades, Clubs, Hearts, Diamonds};

    operator<(const Card& other) {
        if (suite == other.suite) { return number < other.number; }
        return suite < other.suite;
    }

    string toString(); // Function to make your card string representation.

    private:
       int number; // 2 - 14 (11, 12, 13, 14: J, Q, K, A)
       Suite suite;
}

Then you could do:

std::list<Card> myList;
myList.sort();

Note: Untested code, also needs appropriate constructors converters etc.

1 Comment

That would be great, but defining operators and using classes is not allowed unfortunately.
0

How about enums for suit and value:

enum class Suit {
    Spades, Clubs, Hearts, Diamonds
};

enum class Val {
    v2, v3, v4, v5, v6, v7, v8, v9, v10, vJ, vQ, vK, vA
};

typedef std::pair<Suit, Val> Pair;

Then, if you want it, a method to print a Pair:

std::string GetCardName(Pair p)
{
    std::string suit_name;
    switch (p.first)
    {
        case Suit::Spades : suit_name = "Spades"; break;
        /* ... */
        case Suit::Diamonds : suit_name = "Diamonds"; break;
    }

    std::string val_name;
    switch (p.second)
    {
        case Val::v2 : val_name = "2"; break;
        case Val::v3 : val_name = "3"; break;
        /* ... */
        case Val::vA : val_name = "Ace"; break;
    }

    return val_name + " of " + suit_name;
}

Then you can compare exactly as you originally wrote, since the Suit and Val enums are implicitly ordered:

std::list<Pair> Deck =
{
    std::make_pair(Suit::Spades, Val::vA),
    std::make_pair(Suit::Clubs, Val::v7)
};

Deck.sort( [](const Pair& p1, const Pair& p2)
{
    if(p1.first == p2.first) return p1.second < p2.second;
    else return p1.first < p2.first;
} );

for (const auto& pair : Deck)
{
    std::cout << GetCardName(pair) << std::endl;
}

Output:

Ace of Spades
7 of Clubs

Here is a minimal working example of the whole thing.

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.