【问题标题】:STL Map sortingSTL 地图排序
【发布时间】:2014-02-01 11:51:37
【问题描述】:

更新:我遵循约翰的指导并修改了他的代码,通过创建比较器函数解决了我的问题,并将其插入到 STL 映射中的比较参数中。由于我的字符串日期严格采用所示格式,因此使用 substr 就可以了。我的输出和代码如下供您参考。

Date            Total Sales
01JAN1900       $4
20JAN1902       $40
18NOV1912       $2500
19NOV1912       $2500
19OCT1923       $25
01JAN1991       $22
15NOV1991       $300
Grand Total:    $5391


 struct CompareDates 
:
  public std::binary_function <bool, std::string, std::string>
{
  bool operator() (const std::string& lhs, const std::string& rhs)
  {


     if(lhs.substr(5,4) < rhs.substr(5,4))
     {
         return true;

     }
     else if (lhs.substr(5,4) == rhs.substr(5,4) && lhs.substr(2,3) < rhs.substr(2,3))
     {
         return true;
     }
     else if (lhs.substr(5,4) == rhs.substr(5,4) && lhs.substr(2,3) == rhs.substr(2,3) && lhs.substr(0,2) < rhs.substr(0,2))
     {
         return true;

     }
     else
     {
         return false;
     }


  }
};

map<string, double,CompareDates> dailyDatePrices;

最初的问题:我需要将原始数据分类为每日报告格式。因此,我使用 STL map 将日期存储为键,将商品价格存储为值。根据我的阅读,STL 映射是自动排序的。但是我不希望它按地图排序,因为它会生成下面所述的不想要的当前报告输出。我想根据字符串日期(从最早到最新)进行排序,并希望它是那种精确的格式。在使用 map 之前,我已经使用向量和函数比较器对日期进行排序。有什么办法可以去做吗?谢谢!

Raw Data
STRAW:10:15NOV1991
TOY:10:15NOV1991
BARLEY:5:01OCT1992

Undesired Current Report Output
01OCT1992 5
15NOV1991 20

Expected Report Output
15NOV1991 20
01OCT1992 5

【问题讨论】:

  • 使用选择的排序标准实例化地图。
  • 您将不得不将字符串转换为日期,并让日期上的排序得到正确的顺序(或在执行该排序的强者上使用比较器) - 最好寻找日期库
  • 您的Date 类是如何定义的?特别是 operator&lt; 在那个班级?
  • @juanchopanza:这根本不是重复的。这个问题是关于改变排序顺序的。这个问题是关于改变排序的类型。

标签: c++ sorting map stl


【解决方案1】:

对我来说,一个清晰的设计是定义一个 Date 类,其中包含适当的日/月/年数据成员(以及公共访问器,对字段值进行适当检查,例如检查月份必须在范围 1.. .12 等)。

然后您可以重载operator&lt;,在Date 实例上定义正确的排序。

然后您可以简单地拥有一个std::map&lt;Date, int&gt;(其中int 用于存储价格),并且由于Date 映射键上的正确排序定义,事情将“开箱即用” .

如果您想以特定方式格式化日期,您可以定义一个以Date 作为输入并返回格式化日期string 的函数(这可以根据国际化/本地化问题进行修改;我认为将此输出格式方面与“业务逻辑”分开很重要)。

可编译的代码如下(用VS2012简单测试过):

#include <iostream> // for std::cout, std::endl
#include <map>      // for std::map
#include <sstream>  // for std::ostringstream
#include <string>   // for std::string
#include <vector>   // for std::vector
using namespace std;

// A simple date.
// In real world code, this should be a class with private
// date members, and proper accessors.
struct Date {
    int day;
    int month;
    int year;

    Date() : day(0), month(0), year(0) {}

    Date(int d, int m, int y)
        : day(d), month(m), year(y)
    {}
};

// Define proper sorting for dates
bool operator<(const Date& d1, const Date& d2) {
    // First compare years
    if (d1.year < d2.year)
        return true;
    if (d1.year > d2.year)
        return false;

    // Same year, compare months
    if (d1.month < d2.month)
        return true;
    if (d1.month > d2.month)
        return false;

    // Same year and month, compare days
    return (d1.day < d2.day);
}

// Format dates in a specific format
string FormatDate(const Date& date) {
    // NOTE: bounds checking for day and month omitted.

    ostringstream os;

    if (date.day < 10)
        os << '0';
    os << date.day;

    static const char* monthNames[] = {
        "JAN", "FEB", "MAR", "APR",
        "MAY", "JUN", "JUL", "AUG",
        "SEP", "OCT", "NOV", "DEC"
    }; 
    os << monthNames[date.month - 1];

    os << date.year;

    return os.str();
}

struct Item {
    string type;
    int price;
    Date date;

    Item(const string& t, int p, const Date& d)
        : type(t), price(p), date(d)
    {}
};


int main() {
    vector<Item> items;
    items.push_back(Item("STRAW", 10, Date(15, 11, 1991)));
    items.push_back(Item("TOY",   10, Date(15, 11, 1991)));
    items.push_back(Item("BARLEY", 5, Date( 1, 10, 1992)));

    map<Date, int> priceData;
    for (const auto& item : items) {
        auto where = priceData.find(item.date);
        if (where != priceData.end()) {
            where->second += item.price;
        } else {
            priceData[item.date] = item.price;
        }
    }

    for (const auto& e : priceData) {
        cout << FormatDate(e.first) << " " << e.second << endl;
    } 
}

输出:

1991 年 11 月 15 日 20
01OCT1992 5

【讨论】:

    【解决方案2】:

    std::map 已排序。没有办法构造一个未排序的map

    问题不在于map 已排序。问题在于您的密钥是如何设计的。

    您说过您的密钥是日期,但实际上它是stringmap 怎么知道string 中的数据实际上是一个日期,并且它应该以某种方式先按年排序,然后按月排序,然后按天排序?它不能。你必须告诉它这样做。

    将您的键更改为这种格式的字符串:

    YYYYMMDD

    其中YMD 都是数字。不要尝试在 11 月使用 NOV - 请改用 11

    您也可以使用unsigned longs 代替strings 作为密钥。这将使比较更快,但计算值有点棘手。


    如果您必须坚持使用它们的键的原始格式,那么您还有一些工作要做。地图根据地图的比较器进行排序,该比较器指定为template parameters之一:

    【C++03示例】

    struct CompareDates 
    :
      public std::binary_function <bool, std::string, std::string>
    {
      bool operator() (const std::string& lhs, const std::string& rhs)
      {
        // return true if lhs < rhs
        // return false otherwise
    
        // step 1:  compare years.  if lhs.year < rhs.year, return true.  else, continue
        // step 2: compare months.  if lhs.month < rhs.month, return true.  else, continue.
        //    note:  don't just compare the strings, else "AUG" < "JAN" etc
        // step 3: compare days.  if lhs.day < rhs.day, return true.  else, return false.
      }
    };
    

    由于这似乎是家庭作业,我会让你填写上面缺少的部分。 :)

    使用此比较器来比较键,您可以实例化一个自动进行正确排序的映射:

    std::map <Key, Value, CompareDates> myMap;
    

    【讨论】:

    • 哦。没有考虑到。 +1
    • +1 如果比较器足够智能,OP 根本不需要更改键;比较器可以在仅比较期间对它们进行转换。昂贵,是的,但至少可行。可靠的答案。
    • @WhozCraig:当然可行。不过,这是很多工作,而且容易出错。如果 OP 出于某种原因无法更改密钥,那我可能不会这样做。
    • @JohnDibling 是的,我提出这个问题的唯一原因。当然,我更喜欢您在此处描述的自然可排序的键,并强烈建议 OP 考虑它。
    • 您好,感谢您的回复,我之前考虑过使用数字。但是,假设我要严格遵循字符串日期格式,例如“01OCT1992”还有其他选择吗?
    【解决方案3】:

    对时态数据进行词法排序的一种方法是将其转换为以下形式:

    From: 01OCT1992
    To: 1992-10-01
    

    现在,用于比较字符串的默认运算符

    【讨论】:

      【解决方案4】:

      如果您只想按照与当前使用的相反的顺序对地图进行排序,您只需给它一个std::greater 比较器,而不是默认的std::less

      std::map<date, other_type, std::greater<date>> example;
      // Otherwise use example
      

      例子:

      #include <iostream>
      #include <functional>
      #include <map>
      
      int main() {
          std::map<int, float, std::greater<int>> example;
          example.emplace(std::make_pair(10, 10.0));
          example.emplace(std::make_pair(12, 12.0));
      
          for (auto const& entry : example)
          {
              std::cout << "Key: " << entry.first << " " << entry.second << std::endl;
          }
      
          return 0;
      }
      

      http://ideone.com/9Guuil

      【讨论】:

      • -1 您的答案几乎与问题完全无关。有趣的是人们如何盲目地 +1
      • @Slava:我看不出它几乎完全不相关。 OP 的期望输出和实际输出之间的唯一区别是实际输出的顺序相反。 (s) 他没有提供其他信息来描述正在发生的事情。约翰的回答(其中 OP 只是使用字符串而不是正确的日期类型)也很可能,这就是他从我那里得到 +1 的原因。
      • 是的,正式地,您可以为提供的数据提供答案,但很明显,OP 想要按时间顺序对数据进行排序,而不是盲目地反转那 2 行。所以 -1
      猜你喜欢
      • 1970-01-01
      • 2018-10-10
      • 1970-01-01
      • 2017-10-15
      • 1970-01-01
      • 1970-01-01
      • 2012-04-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多