adiamond
adiamond

Reputation: 33

Implementing SQL-like order in C++

Is there an algorithm in c++ to sort by multiple columns? Or some other way like SQL does it in a very simple way, ORDER BY <>,<>? For example, given SQL SillyData table:

Name   Age  FavouriteFruet LuckyNumber
--------------------------------------
Mary   22   Apple          2
Alice  22   Banana         7
Bob    21   Orange         8
Mark   21   Apple          0
John   22   Banana         3

When I do SELECT * FROM SillyData ORDER BY Age, FavouriteFruet, LuckyNumber this yeilds the result:

Name   Age  FavouriteFruet LuckyNumber
--------------------------------------
Mark   21   Apple          0
Bob    21   Orange         8
Mary   22   Apple          2
John   22   Banana         3
Alice  22   Banana         7

All I can think of is a list of tuples. But then I would have to sort by first tuple type, lock the order of it, then second, ...and so on. Looks complicated and cumbersome. Any better way?

Upvotes: 3

Views: 378

Answers (3)

Galik
Galik

Reputation: 48625

You can use std::stable_sort for this. For example I have defined different individual sort functions for type info and use them with std::stable_sort to achieve any relative ordering I desire:

struct info
{
    std::string name;
    int age;
    std::string fav_fruit;
    int fav_number;

    // static functions define sorting methods
    static bool sort_by_name(info const& lhs, info const& rhs)
    {
        return lhs.name < rhs.name;
    }

    static bool sort_by_age(info const& lhs, info const& rhs)
    {
        return lhs.age < rhs.age;
    }

    static bool sort_by_fav_fruit(info const& lhs, info const& rhs)
    {
        return lhs.fav_fruit < rhs.fav_fruit;
    }

    static bool sort_by_fav_number(info const& lhs, info const& rhs)
    {
        return lhs.fav_number < rhs.fav_number;
    }

    // friend function provides output operator
    friend std::ostream& operator<<(std::ostream& os, info const& i)
    {
        return os << i.name << '\t' << i.age << '\t' << i.fav_fruit << '\t' << i.fav_number;
    }
};

int main()
{

    std::vector<info> infos
    {
        {"Mary",  22, "Apple",  2},
        {"Alice", 22, "Banana", 7},
        {"Bob",   21, "Orange", 8},
        {"Mark",  21, "Apple",  0},
        {"John",  22, "Banana", 3},
    };

    // std::stable_sort will not rearrange the currently sorted order
    // of items that are otherwise considered equal in terms of the current
    // sort condition

    // NOTE: We sort them in reverse order of importance, the most significant
    // ordering being the last one imposed

    std::stable_sort(infos.begin(), infos.end(), info::sort_by_fav_number);
    std::stable_sort(infos.begin(), infos.end(), info::sort_by_fav_fruit);
    std::stable_sort(infos.begin(), infos.end(), info::sort_by_age);

    std::cout << "Name\tAge\tFruit\tNumber" << '\n';
    std::cout << "----\t---\t-----\t------" << '\n';

    for(auto i: infos)
        std::cout << i << '\n';
}

Output:

Name    Age Fruit   Number
----    --- -----   ------
Mark    21  Apple   0
Bob     21  Orange  8
Mary    22  Apple   2
John    22  Banana  3
Alice   22  Banana  7

Upvotes: 1

Richard Hodges
Richard Hodges

Reputation: 69892

assuming that you want a case-insensitive compare for the strings, this is a starting point:

#include <tuple>
#include <string>
#include <iostream>
#include <cstring>
#include <functional>

struct row
{
    std::string Name;
    int Age;
    std::string FavouriteFruet;
    int LuckyNumber;
};

auto index_order(const row& r)
{
    auto as_upper = [](std::string s)
    {
        std::transform(s.begin(), s.end(), s.begin(), [](auto c){return std::toupper(c); });
        return s;
    };
    return std::make_tuple(std::cref(r.Age), as_upper(r.FavouriteFruet), std::cref(r.LuckyNumber), as_upper(r.Name));
}

int main()
{
    std::vector<row> results =
    {
        { "Mary", 22, "Apple", 2},
        { "Alice", 22, "Banana", 7},
        { "Bob", 21, "Orange", 8},
        { "Mark", 21, "Apple", 0},
        { "John", 22, "Banana", 3},
    };

    std::sort(results.begin(), results.end(),
              [](auto const& l, auto const& r)
              {
                  return index_order(l) < index_order(r);
              });

    for (auto const& r : results)
    {
        std::cout << r.Name << " | " << r.Age << " | " << r.FavouriteFruet << " | " << r.LuckyNumber << '\n';
    }    
}

expected output:

Mark | 21 | Apple | 0
Bob | 21 | Orange | 8
Mary | 22 | Apple | 2
John | 22 | Banana | 3
Alice | 22 | Banana | 7

Upvotes: 3

You probably want some lexicographical order.

So use std::lexicographical_compare probably passed to some std::sort as the compare test.

(I hope that you are using C++11 at least)

Upvotes: 0

Related Questions