如何有效地记忆唯一路径名的向量

时间:2012-09-06 08:16:22

标签: c++ performance stl

我有一个路径名列表,其中一些路径名标有“包含子树”标志。 我需要迭代包括子树在内的所有路径,但每个唯一路径只需迭代一次。

所以,如果我有这样的目录树:

C:\Models\
C:\Models\A
C:\Models\A\1
C:\Models\B
C:\Models\B\1

和2个选择的子树路径(INPUT):

{"C:\Models\", true}
{"C:\Models\A", true}

迭代时我需要避免以下路径重复:

C:\Models\
C:\Models\A
C:\Models\A\1
C:\Models\B
C:\Models\B\1
C:\Models\A   *** Duplicate ***
C:\Models\A\1 *** Duplicate ***

我决定使用vector + set:

std::vector<std::string> vecPaths;       // For iterating
std::set<std::string>    setUniquePaths; // For duplicates check

但这是无记忆效果的决定,因为每个路径将在向量中呈现2次1)并且在集合中呈现2)。

如何在不重复这些字符串的情况下提供字符串唯一性?

关于任务定义的注意事项:

  • INPUT是一系列对{string path,bool includeSubtre}
  • OUTPUT是一个路径向量,一个用于将来迭代的快照。

5 个答案:

答案 0 :(得分:2)

如果您只是在std::vector<std::string>迭代而不添加或删除元素(在单次迭代期间),那么为什么不只是为重复项使用一组指针/迭代器:

std::vector<std::string> subtreePaths = //the ones you want to iterate for
...
std::set<std::vector<std::string>::const_iterator> setUniquePaths;
for(auto iterS=subtreePaths.begin(); iterS!=subtreePaths.end(); ++iterS)
    for(auto iterP=vecPaths.begin(); iterP!=vecPaths.end(); ++iterP)
        if(matches(*iterP, *iterS) && setUniquePaths.insert(iterP).second)
            std::cout << *iterP << std::endl;    //or whatever

(当然auto是C ++ 11,可以用C ++ 98/03兼容的相应迭代器类型替换它。)

但也许我误解了你实际想要实现的目标。


编辑:如果您还没有vecPaths向量中的所有现有路径,并且您实际上想以某种方式迭代构建vecPaths的真实文件系统所有找到(和重复清理)路径的向量,然后我的上述方法当然是垃圾,因为它假设仅仅基于字符串的迭代在所有路径的已知向量上。

但如果是这种情况,您可以完全放弃向量并使用单个std::set<std::string>来收集您遇到的所有路径(现在自动唯一)。无需额外的载体。

答案 1 :(得分:2)

到目前为止,评论主要集中在内存数据结构上,但重要的是要记住目录遍历非常受IO限制。鉴于输入

{"C:\Models\", true}
{"C:\Models\A", true}

你想跳过整个第二个目录遍历。因此,您不希望最后消除重复。另一个例子,给出

{"C:\Models\A", true}
{"C:\Models\", true}

您希望在第二次枚举期间跳过A子树。

因此,使用所有已知路径名的两个 std::set<std::string>,一个用于非递归枚举目录,另一个用于您枚举的目录。在递归枚举期间,跳过第二组中已存在的任何子树。在输入结束时,您可以简单地合并这两组。

答案 2 :(得分:1)

如果有很多条目,我会建立一个包含所有唯一目录名称(不是路径)的向量,并使用树(例如http://tree.phi-sci.com/)通过一系列ID将所有看到的路径存储到矢量。要确定是否已经看到现有目录,请使用哈希映射为当前路径中的每个目录名构建一系列ID。如果路径完全匹配,请跳过它。如果不是,请将相关节点添加到树中以反映新路径。注意:这可能导致树中的多个节点引用相同的ID。

以下是代码:

std::vector< std::string > directories; // THIS IS THE INPUT!
std::vector< std::string > directory_names;
std::unordered_map< std::string, size_t > name_to_id_map;
tree< size_t > directory_paths;
for (auto idir = directories.begin(); idir != directories.end(); ++idir) {
    // Convert directories to a sequence of IDs (if new names are found, add
    // them to 'directory_names' and 'name_to_id_map'.  This is pretty mechanical code.
    std::vector< size_t > id_sequence = convert( *idir );

    // Walk the tree looking for this ID sequence.
    tree<size_t>::sibling_iterator current_tree_node;
    bool found = true;
    for (auto iid = id_sequence.begin(); iid != id_sequence.end(); ++iid) {
       if ( found ) {
          if ( !directory_paths.is_valid( current_tree_node ) ) {
             // Find a match among the roots of the tree.  Note: There might be a more elegant way to do this.
             tree<size_t>::sibling_iterator iroot( directory_paths.head );
             tree<size_t>::sibling_iterator iroot_end( directory_paths.feet );
             ++iroot_end;

             // Note: If the tree is sorted, we can use equal_range!
             current_tree_node = std::find( iroot, iroot_end, *iid );
             found = ( current_tree_node != iroot_end );
          }
          else {
             // Find a match among the siblings of 'current_tree_node'.
             tree<size_t>::sibling_iterator ichild = directory_paths.begin_child( current_tree_node );
             tree<size_t>::sibling_iterator ichild_end = directory_paths.end_child( current_tree_node );

             // Note: If the tree is sorted, we can use equal_range!
             current_tree_node = std::find( ichild, ichild_end, *iid );
             found = ( current_tree_node != ichild_end );
          }
       }

       if ( !found ) {
          // Add this node to the tree as a child of current_tree_node.
          if ( directory_paths.is_valid( current_tree_node ) ) {
             current_tree_node = directory_paths.insert_after( current_tree_node, *iid );
          }
          else if ( !directory_paths.empty() ) {
             current_tree_node = directory_paths.insert_after( directory_paths.feet, *iid );
          }
          else {
             current_tree_node = directory_paths.set_head( *iid );
          }
       }
    }

    if ( !found ) {
       // This directory path (i.e. *idir) has not been seen before.
       ...
    }
 }

例如,以下输入将创建5个唯一名称(C:,Models,A,1,B)。

C:\Models\
C:\Models\A
C:\Models\A\1
C:\Models\B
C:\Models\B\1

处理完第一行后,树将有两个节点。 处理完第二行后,树将有三个节点。 处理完第3行后,树将有4个节点。 处理完第4行后,树将有五个节点。 处理完第5行后,树将有6个节点。

如果碰巧遇到:C:\ Models \ 1 \ B,则不会将新条目添加到'directory_names'(或'name_to_id_map'),但树现在将有8个节点。

我认为这种实现非常节省内存,因为1)directory_names只存储子字符串,而不是完整路径; 2)永远不会为共享同一路径的一部分的两个目录创建多个字符串。基本上,在处理每个新目录时,只有关于名称和名称的唯一信息。存储路径(不包括'name_to_id_map'的开销,这对于实现正确的运行时间与内存平衡似乎很重要。)

注意:我不太明白你的意思是“和2个选择的子树路径(INPUT)”。

答案 3 :(得分:0)

您可以使用boost::variant来存储目录节点。每个节点都是文件或目录:

typedef boost::variant<
      std::string
    , std::map<std::string, boost::recursive_variant_>
    > Tree;

map确保目录中没有重复的名称。

您可能需要添加函数来填充和遍历此递归数据结构。

答案 4 :(得分:-1)

使用shared_ptr。但是字符串可以实现为COW,因此无需担心副本。