【问题标题】:Which STL container(s)/algorithm(s) could I use to solve this?我可以使用哪个 STL 容器/算法来解决这个问题?
【发布时间】:2015-06-11 06:49:25
【问题描述】:

我有一个 MFC 项目,给定初始根路径,它遍历每个文件、文件夹和子文件夹,然后在列表控件中向用户显示每个文件。由于这很容易成为一个相当冗长的操作,我偶尔将控制权交给操作系统(通过处理单个消息泵队列),允许显示迄今为止发现的每个元素。现在,棘手的部分来了……

我想保持元素按它们最后一次已知的修改时间戳排序,这(我相信)需要某种插入排序技术。由于某些元素可能包含重复的时间戳,但文件路径不同,并且我们将按时间戳排序(以MM:DD:YY hh:mm 的格式存储为std::string),一个简单的std::vector 似乎无法完成这项工作。另外,我不希望用户在开始对元素进行排序之前等待整个操作完成,因为等待的时间是未知的,就像我上面所说的,很容易变得足够长,让任何人不耐烦。

最后,我需要一些方法来保持插入到列表控件中的元素平等地映射到容器上的排序操作,以便用户可以实时看到根路径的最近修改的内容(和子内容)-时间。

为了实现这一目标,应该使用什么合适的容器和算法?
这基本上是我现在正在做的事情:

void CFileSearchDlg::UpdateDirectoryList(std::string strRootPath)
{
    CFilesystem fs; // Helper class which uses C++11 <filesystem> to iterate through file path entries
    DWORD time = GetTickCount(); // Determines if we should yield control to the OS after a certain period of time
    m_listView.DeleteAllItems(); // Clears all current elements from the list view control

    /*
    // CFilesystem::Search() takes in a root path and a lambda expression, and executes the expression for each
    // element found within the root path, passing a basic_directory_entry<path> as a parameter to the lambda
    // expression, and will continue to parse until no entries are left (or until we return false)...
    */
    fs.Search(strRootPath, [&](CFilesystem::path_entry pe)
        {
            // This is primarily a Unicode project, so we need to convert the search results to an std::wstring
            std::string path = pe.path().string();
            std::wstring wpath;
            wpath.assign(path.begin(), path.end());

            // Get a Win32 handle to the file/folder, or display an error & exit
            auto hFile = CreateFileA(path.c_str(), GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
            if (hFile == INVALID_HANDLE_VALUE) {
                MessageBoxA(NULL, "Failed to open file", path.c_str(), MB_OK | MB_ICONERROR);
                return false; // Stop parsing
            }

            // Get the date & time attributes of the file/folder, or display an error & exit
            TCHAR fileTime[MAX_PATH];
            ZeroMemory(&fileTime, sizeof(TCHAR)* MAX_PATH);
            auto ret = GetLastWriteTime(hFile, fileTime, MAX_PATH);
            CloseHandle(hFile);
            if (!ret) {
                MessageBoxA(NULL, "Failed to get date & time attributes", path.c_str(), MB_OK | MB_ICONERROR);
                return false; // Stop parsing
            }
            std::wstring strTime(fileTime);

            /************************************************** 
            // THIS IS WHERE THE MAGIC IS SUPPOSED TO HAPPEN //
            /*************************************************/
            InsertPathItem(m_listView, wpath, strTime); // ... But how?

            // Check if we should yield control to the operating system
            auto tick = GetTickCount();
            if (tick - time > 100) {
                YieldControl();
                time = tick;
            }

            // Continue to parse directory contents
            return true;
        }
    );
}

编辑 完整的答案似乎是 galinette(关于正确的 STL 容器)和 foraidt(关于将视图与数据同步)的组合。

【问题讨论】:

  • 反对票是怎么回事?对于特定场景,这是一个有效的问题。至少留下一个你不喜欢的理由......
  • std::map 能完成这项工作吗?

标签: c++ algorithm sorting stl containers


【解决方案1】:

只需使用std::multimap,键类型可以是整数时间戳(最快的方式),或者如果默认字符串排序保持时间戳顺序(这更慢),则按照您建议的时间字符串

std::multimap<time_t, std::wstring>

std::multimap<std::string, std::wstring>

插入:

myFileMap.insert(std::pair<time_t, std::wstring>(time,wPath));

【讨论】:

  • 如果有的话,也许std::multimap 会起作用(因为可能存在重复的时间戳),但是仍然存在在列表控件中保持项目排序的问题。有没有办法知道元素如何以及何时移动?即 where 将新元素插入到控件中?
  • @MatteoItalia:我正在编辑答案,而你正在评论:)
  • 只是为了澄清......你的意思是std::multimap吗?我在std::multimapmap 上找不到任何信息...
【解决方案2】:

std::set 保持其元素根据其 Comparator 排序。

编辑:您需要提供一个严格小于确定性的比较器(忘记了数学术语)。如果可能有多个相同的时间戳,则需要用另一个属性(例如 id)消除歧义。

【讨论】:

  • @MatteoItalia 确实如此,但是在我点击“发布”一秒后?我们这里有一个严肃的投票狙击手:p
  • 嘿,至少他们是赞成票。我的问题在询问后几分钟内就得到了几票否决,没有任何理由留下=P
【解决方案3】:

同步 MFC 控件与您的内部数据结构的排序,您可以尝试虚拟列表:关键是使用LVS_OWNERDATA 风格。然后,该列表将不会自行存储项目数据,而是回调您的代码以获取显示项目所需的信息。您可以在此处跳转到自定义排序容器并检索信息。

'Using virtual lists' on codeproject 的文章看起来很全面。

接下来,排序本身。您可以尝试自定义类型,其中包含要显示的文本以及数字格式的排序标准,例如:

struct item {
    std::string label;
    time_t mtime;
};

出于性能原因,在将控制权交还给操作系统之前,将多个项目插入向量中然后对其进行一次排序可能会很有用。不过,您必须测试这实际上是否比直接插入到已排序的容器中效果更好。

无论如何,要在容器类型之间轻松切换,您可以指定一个可用作排序谓词的排序函子(使用 C++11 lambda 可能更优雅):

struct item_less {
    bool operator()(const item & a, const item & b) {
        return a.mtime < b.mtime;
    }
};

像这样使用它:

std::set<item, item_less> a; // Automatic sorting
std::vector<item> b;
std::sort(a.begin(), a.end(), item_less()); // Manual sorting

【讨论】:

  • +1 在这和@galinette 的回答之间,我相信我应该有足够的信息来解决我的问题。如果可以的话,我会投票作为答案。
  • 每次发现新元素时,手动对(可能)包含数十万(或更多)元素的容器进行排序是否真的可行?在解析根文件夹时,AFAIK 不知道在操作实际完成之前会有多少元素。这就是为什么我需要在运行时对所有新数据进行插入排序。更复杂的是,std::multimap 不符合 RandomAccessIterator,而LVN_GETDISPINFO 回调似乎利用了索引……那么我的数据如何映射到相应的 CListCtrl 元素?
  • @RectangleEquals 可能在插入许多元素后只排序一次而不是根据排序顺序插入每个元素。我的直觉表明它至少不会变慢。但这在很大程度上取决于您的具体情况,您必须尝试找出答案。由于空间局部性,存储项目(不是指向项目的指针)的vector 也可能表现更好。您也可以尝试使用deque,它提供随机访问但不需要连续的内存块,这在内存稀缺和碎片化时可能变得很重要。
猜你喜欢
  • 2023-01-08
  • 2021-01-16
  • 1970-01-01
  • 1970-01-01
  • 2016-01-24
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
相关资源
最近更新 更多