Home > Software engineering >  Looking for an efficient method or algorithm to check whether a file belongs to a certain item in a
Looking for an efficient method or algorithm to check whether a file belongs to a certain item in a

Time:01-06

I have a list of folder paths. There may be many, dozens or even hundreds of folder paths in this list. for example:

C:\Program Files\7-Zip
// many directories under C:\Program Files\

C:\ProgramData\Adobe
C:\ProgramData\boost_interprocess
C:\ProgramData\dftmp
C:\ProgramData\Microsoft DNX
C:\ProgramData\NVIDIA Corporation
C:\ProgramData\Oracle
// many directories under C:\ProgramData\

C:\Windows\Boot
// many directories under C:\Windows\

D:\Datas\
// many directories under D:\Datas\

Now I provide a path, such as C:\Windows\1.log. I need to check if this file belongs to a folder in the previous list. In order to achieve this requirement, I can of course traverse the entire list directly, and then check whether the path of the file I want to query starts with a certain folder path, but this feels that the efficiency is too low, and there are too many invalid searches, so , Is there a better and more efficient strategy to accomplish this task?


updated:

Sorry, I may not be very clear, I only need to check the containment relationship between the paths, and do not need to check whether these files really exist.

Therefore, I think this should be an algorithm problem. Is there any way to find the greatest common divisor of a set of paths? for example:

C:\ProgramData\Adobe
C:\ProgramData\boost_interprocess
C:\ProgramData\dftmp
C:\ProgramData\Microsoft DNX
C:\ProgramData\NVIDIA Corporation

The greatest common divisor of their group of paths is C:\ProgramData\,so I can add them to one package. and then search the package first?

CodePudding user response:

Here's a Trie approach using nested dicts. Basically we can insert each folder into a dict of its parent folder. So a path like C:\dir1\dir2\ would be stored as

{
  "C:": {
    "dir1": {
      "dir2": {}
    }
}

Here's a small class that inserts a given folderPath in our data structure and allows querying for commonPath for any new files/folders. The looping through nested dict might look a bit weird at first.

import json

class PathTrie():
    def __init__(self, *args, **kwargs):
        self.data = {}

    def insertPath(self, folderPath):
        temp = self.data
        for dirName in folderPath.split('\\'):  # ASSUME: dirs are valid and don't contain '\'
            if temp.get(dirName) is None:
                temp[dirName] = {}
            temp = temp[dirName]

    def getCommonPath(self, path):
        commonPath = ""
        temp = self.data
        for dirName in path.split('\\'):
            if temp.get(dirName) is None:
                return commonPath
            commonPath  = dirName   '\\'
            temp = temp[dirName]
        return commonPath  # you can strip last '\'' if needed

    def print(self):
        print(json.dumps(self.data, indent=2))


if __name__ == '__main__':
    trie = PathTrie()
    trie.insertPath("C:\\ProgramData\\dir1")
    trie.insertPath("C:\\ProgramData\\dir2")
    trie.insertPath("C:\\User\\dir3")
    trie.print()
    print(trie.getCommonPath("C:\\ProgramData\\dir2\\file1"))
    print(trie.getCommonPath("C:\\ProgramData\\file1"))

Above prints -

{
  "C:": {
    "ProgramData": {
      "dir1": {},
      "dir2": {}
    },
    "User": {
      "dir3": {}
    }
  }
}


C:\ProgramData\dir2\
C:\ProgramData\

But if the data you are working on is huge(might have 100 MBs of data in memory), might be a good idea to use some Trie libraries written in C instead of this nested dict approach.

CodePudding user response:

thanks everybody. thanks for your sugesstions. I write a C version here. if someone need, you can test it.

#pragma once

#include <string>
#include <functional>
#include <unordered_map>

namespace Tests
{
    template <typename TCharType, bool IgnoreCase>
    struct PathTrieTraits
    {
        typedef typename std::basic_string<TCharType>::size_type SizeType;

        static void CalcHash(SizeType& value, SizeType fnvPrime, char ch) = delete;
    };

    template<>
    struct PathTrieTraits<char, true>
    {
        typedef std::basic_string<char>::size_type SizeType;

        static void CalcHash(SizeType& value, SizeType fnvPrime, char ch)
        {
            value ^= (SizeType)::tolower(ch);
            value *= fnvPrime;
        }
    };

    template<>
    struct PathTrieTraits<char, false>
    {
        typedef std::basic_string<char>::size_type SizeType;

        static void CalcHash(SizeType& value, SizeType fnvPrime, char ch)
        {
            value ^= (SizeType)ch;
            value *= fnvPrime;
        }
    };

    template <>
    struct PathTrieTraits<wchar_t, true>
    {
        typedef std::basic_string<wchar_t>::size_type SizeType;

        static void CalcHash(SizeType& value, SizeType fnvPrime, wchar_t ch)
        {
            ch = (wchar_t)::towlower(ch);
            value ^= (SizeType)ch;
            value *= fnvPrime;
        }
    };

    template <>
    struct PathTrieTraits<wchar_t, false>
    {
        typedef std::basic_string<wchar_t>::size_type SizeType;

        static void CalcHash(SizeType& value, SizeType fnvPrime, wchar_t ch)
        {
            value ^= (SizeType)ch;
            value *= fnvPrime;
        }
    };

    template <typename TCharType,
        typename THasher = std::hash<std::basic_string<TCharType>>,
        typename TPredicate = std::equal_to<std::basic_string<TCharType>>
    >
        class TPathNode
    {
    public:
        typedef TPathNode<TCharType>                                                SelfType;
        typedef std::basic_string<TCharType>                                        StringType;
        typedef typename StringType::size_type                                      SizeType;
        typedef std::unordered_map<SizeType, SelfType>                              ChildrenType;

        enum PathNodeAttribute
        {
            PathNodeAttribute_None = 0,
            PathNodeAttribute_Leaf = 1 << 0,
            PathNodeAttribute_Trunk = 1 << 1
        };
    public:
        TPathNode(const StringType& name) :
            Attribute(PathNodeAttribute_None),
            FileName(name)
        {
        }

        TPathNode(StringType&& name) :
            Attribute(PathNodeAttribute_None),
            FileName(name)
        {
        }

        TPathNode() :
            Attribute(PathNodeAttribute_None)
        {
        }

        SizeType GetCount() const
        {
            return Children.size();
        }

        bool IsEmpty() const
        {
            return Children.empty();
        }

        const SelfType* FindChild(SizeType hash) const
        {
            auto pos = Children.find(hash);

            return pos != Children.end() ? &pos->second : nullptr;
        }

        SelfType* FindChild(SizeType hash)
        {
            auto pos = Children.find(hash);

            return pos != Children.end() ? &pos->second : nullptr;
        }

        SelfType* EmplaceChild(SizeType hash, SelfType&& node)
        {
            auto ret = Children.emplace(std::make_pair(hash, node));
            return ret.second ? &ret.first->second : nullptr;
        }

        bool IsTrunk() const
        {
            return (Attribute & PathNodeAttribute_Trunk) != 0;
        }

        bool IsLeaf() const
        {
            return (Attribute & PathNodeAttribute_Leaf) != 0;
        }

        void AddAttribute(PathNodeAttribute attr)
        {
            Attribute = static_cast<PathNodeAttribute>((int)Attribute | (int)attr);
        }

        void RemoveAttribute(PathNodeAttribute attr)
        {
            Attribute = static_cast<PathNodeAttribute>((~(int)attr) & (int)Attribute);
        }

        void Clear()
        {
            Attribute = PathNodeAttribute_None;
            FileName.clear();            
            Children.clear();
        }

    private:
        PathNodeAttribute           Attribute;
        StringType                  FileName;
        ChildrenType                Children;
    };

    template <typename TCharType,
        typename TTrieTraits = PathTrieTraits<TCharType, true>,
        typename THasher = std::hash<std::basic_string<TCharType>>,
        typename TPredicate = std::equal_to<std::basic_string<TCharType>>
    >
        class TPathTrieTree
    {
    public:
        typedef TPathNode<TCharType, THasher, TPredicate>                           PathNodeType;
        typedef typename PathNodeType::StringType                                   StringType;
        typedef typename PathNodeType::SizeType                                     SizeType;

        struct SearchResult
        {
            SizeType Pos;
            SizeType Hash;
#if _DEBUG
            StringType Text;
#endif

            SearchResult() :
                Pos(StringType::npos),
                Hash(0)
            {
            }

            SearchResult(
                SizeType pos,
                SizeType hash
#if _DEBUG
                , StringType text
#endif

            ) :
                Pos(pos),
                Hash(hash)
#if _DEBUG
                , Text(text)
#endif
            {
            }
        };

    public:
        void Add(const StringType& path)
        {
            if (path.empty())
            {
                return;
            }

            Add(path.c_str(), path.size());
        }

        void Add(const TCharType* pathStr)
        {
            if (pathStr == nullptr)
            {
                return;
            }

            return Add(pathStr, std::char_traits<TCharType>::length(pathStr));
        }

        void Add(const TCharType* pathStr, SizeType length)
        {
            PathNodeType* curNode = &Root;
            SizeType startPos = 0;

            do
            {
                SearchResult Result = SearchAndHash(pathStr, startPos, length);

                if (Result.Pos == startPos)
                {
                    // 连续的分隔符?
                    // 
                      startPos;
                    continue;
                }
                else if (Result.Pos != StringType::npos)
                {
                    PathNodeType* targetNode = curNode->FindChild(Result.Hash);

                    if (targetNode == nullptr)
                    {
                        curNode->AddAttribute(PathNodeType::PathNodeAttribute_Trunk);

                        targetNode = curNode->EmplaceChild(Result.Hash, PathNodeType(StringType(pathStr   startPos, pathStr   Result.Pos)));
                    }

                    curNode = targetNode;
                    startPos = Result.Pos   1;
                    continue;
                }
                else if (startPos < length)
                {
                    // 没有分隔符了?那这是最后一个节点元素
                    curNode->AddAttribute(PathNodeType::PathNodeAttribute_Trunk);

                    curNode = curNode->EmplaceChild(Result.Hash, PathNodeType(StringType(pathStr   startPos, pathStr   length)));

                    curNode->AddAttribute(PathNodeType::PathNodeAttribute_Leaf);
                    return;
                }
                break;
            } while (true);

            if (curNode != nullptr)
            {
                curNode->AddAttribute(PathNodeType::PathNodeAttribute_Leaf);
            }
        }

        bool IsChild(const StringType& path) const
        {
            if (path.empty())
            {
                return false;
            }

            return IsChild(path.c_str(), path.size());
        }

        bool IsChild(const TCharType* pathStr) const
        {
            return IsChild(pathStr, std::char_traits<TCharType>::length(pathStr));
        }

        bool IsChild(const TCharType* pathStr, SizeType length) const
        {
            const PathNodeType* curNode = &Root;
            SizeType startPos = 0;

            do
            {
                SearchResult Result = SearchAndHash(pathStr, startPos, length);

                if (Result.Pos == startPos)
                {
                    // 连续的分隔符?
                    // 
                      startPos;
                    continue;
                }
                else if (Result.Pos != StringType::npos)
                {
                    // 说明这个segment可能是也可能不是最后一个元素
                    const PathNodeType* targetNode = curNode->FindChild(Result.Hash);

                    // 当前节点下没有这个名字的子节点?
                    if (targetNode == nullptr)
                    {
                        return curNode->IsLeaf();
                    }

                    curNode = targetNode;
                    startPos = Result.Pos   1;
                    continue;
                }
                else if (startPos < length)
                {
                    // 没有分隔符了?那这是最后一个节点元素
                    if (curNode->IsLeaf())
                    {
                        return true;
                    }

                    const PathNodeType* targetNode = curNode->FindChild(Result.Hash);
                    if (targetNode == nullptr)
                    {
                        return curNode->IsLeaf();
                    }

                    return targetNode->IsLeaf();
                }
                else
                {
                    break;
                }
            } while (true);

            return false;
        }

        bool IsEmpty() const
        {
            return Root.IsEmpty();
        }

        void Clear()
        {
            Root.Clear();
        }

    private:
        static SearchResult SearchAndHash(const TCharType* str, SizeType startPos, SizeType maxLength) 
        {
            static const SizeType WalkIter = sizeof(TCharType) / sizeof(char);

#if _WIN64
            static_assert(sizeof(SizeType) == 8, "This code is for 64-bit SizeType.");

            const SizeType FNVOffsetBasis = 14695981039346656037ULL;
            const SizeType FNVPrime = 1099511628211ULL;

#else
            static_assert(sizeof(SizeType) == 4, "This code is for 32-bit SizeType.");

            const SizeType FNVOffsetBasis = 2166136261U;
            const SizeType FNVPrime = 16777619U;
#endif

            SizeType Value = FNVOffsetBasis;

            for (SizeType i = startPos; i < maxLength;   i)
            {
                TCharType ch = str[i];

                if (str[i] == '\\' || str[i] == '/')
                {
#if _WIN64
                    Value ^= Value >> 32;
#endif
                    return SearchResult(
                        i,
                        Value
#if _DEBUG
                        , StringType(str   startPos, str   i)
#endif
                    );
                }

                TTrieTraits::CalcHash(Value, FNVPrime, ch);
            }

#if _WIN64
            Value ^= Value >> 32;
#endif

            return SearchResult(
                StringType::npos,
                Value
#if _DEBUG
                , StringType(str   startPos, str   maxLength)
#endif
            );
        }
    private:
        PathNodeType                Root;
    };
}

tests example:

   Tree.Add("C:\\Program Files\\XXX");
    Tree.Add("C:\\Program Files\\Ymal");
    Tree.Add("D:\\Tests\\XXX/");
    Tree.Add(R"(Art_Resources\Characters\Hero\105\Textures)");
    Tree.Add(R"(Art_Resources\Characters\Hero\107\Textures)");

    assert(Tree.IsChild("C:\\Program Files\\XXX"));
    assert(Tree.IsChild("C:\\Program Files\\XXX/1.log"));
    assert(Tree.IsChild("C:\\Program Files\\XXX/XXX2"));
    assert(Tree.IsChild("C:\\Program Files\\XXX/XXX2/7.exe"));
    assert(!Tree.IsChild("C:\\Program Files\\XXXX/1.log"));
    assert(!Tree.IsChild("C:\\Program Data\\XXXX/1.log"));
    assert(!Tree.IsChild("D:\\Program Data\\XXXX/1.log"));
    assert(!Tree.IsChild("D:\\Tests\\XXXX/1.log"));
    assert(Tree.IsChild("D:\\Tests\\XXX/1.log"));
    assert(Tree.IsChild("D:\\Tests\\XXX/X2"));

    assert(!Tree.IsChild(R"(Art_Resources\Characters\Hero\101\Ani)"));
    assert(!Tree.IsChild(R"(Art_Resources\Characters\Hero\107\10700.FBX)"));
    assert(Tree.IsChild(R"(Art_Resources\Characters\Hero\107\Textures\10700.FBX)"));
  •  Tags:  
  • Related