299 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			299 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#pragma once
 | 
						|
#include <QString>
 | 
						|
#include <QMap>
 | 
						|
#include <QStringList>
 | 
						|
 | 
						|
template <char Tseparator>
 | 
						|
class SeparatorPrefixTree
 | 
						|
{
 | 
						|
public:
 | 
						|
    SeparatorPrefixTree(QStringList paths)
 | 
						|
    {
 | 
						|
        insert(paths);
 | 
						|
    }
 | 
						|
 | 
						|
    SeparatorPrefixTree(bool contained = false)
 | 
						|
    {
 | 
						|
        m_contained = contained;
 | 
						|
    }
 | 
						|
 | 
						|
    void insert(QStringList paths)
 | 
						|
    {
 | 
						|
        for(auto &path: paths)
 | 
						|
        {
 | 
						|
            insert(path);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// insert an exact path into the tree
 | 
						|
    SeparatorPrefixTree & insert(QString path)
 | 
						|
    {
 | 
						|
        auto sepIndex = path.indexOf(Tseparator);
 | 
						|
        if(sepIndex == -1)
 | 
						|
        {
 | 
						|
            children[path] = SeparatorPrefixTree(true);
 | 
						|
            return children[path];
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            auto prefix = path.left(sepIndex);
 | 
						|
            if(!children.contains(prefix))
 | 
						|
            {
 | 
						|
                children[prefix] = SeparatorPrefixTree(false);
 | 
						|
            }
 | 
						|
            return children[prefix].insert(path.mid(sepIndex + 1));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// is the path fully contained in the tree?
 | 
						|
    bool contains(QString path) const
 | 
						|
    {
 | 
						|
        auto node = find(path);
 | 
						|
        return node != nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    /// does the tree cover a path? That means the prefix of the path is contained in the tree
 | 
						|
    bool covers(QString path) const
 | 
						|
    {
 | 
						|
        // if we found some valid node, it's good enough. the tree covers the path
 | 
						|
        if(m_contained)
 | 
						|
        {
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        auto sepIndex = path.indexOf(Tseparator);
 | 
						|
        if(sepIndex == -1)
 | 
						|
        {
 | 
						|
            auto found = children.find(path);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            return (*found).covers(QString());
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            auto prefix = path.left(sepIndex);
 | 
						|
            auto found = children.find(prefix);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            return (*found).covers(path.mid(sepIndex + 1));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// return the contained path that covers the path specified
 | 
						|
    QString cover(QString path) const
 | 
						|
    {
 | 
						|
        // if we found some valid node, it's good enough. the tree covers the path
 | 
						|
        if(m_contained)
 | 
						|
        {
 | 
						|
            return QString("");
 | 
						|
        }
 | 
						|
        auto sepIndex = path.indexOf(Tseparator);
 | 
						|
        if(sepIndex == -1)
 | 
						|
        {
 | 
						|
            auto found = children.find(path);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return QString();
 | 
						|
            }
 | 
						|
            auto nested = (*found).cover(QString());
 | 
						|
            if(nested.isNull())
 | 
						|
            {
 | 
						|
                return nested;
 | 
						|
            }
 | 
						|
            if(nested.isEmpty())
 | 
						|
                return path;
 | 
						|
            return path + Tseparator + nested;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            auto prefix = path.left(sepIndex);
 | 
						|
            auto found = children.find(prefix);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return QString();
 | 
						|
            }
 | 
						|
            auto nested = (*found).cover(path.mid(sepIndex + 1));
 | 
						|
            if(nested.isNull())
 | 
						|
            {
 | 
						|
                return nested;
 | 
						|
            }
 | 
						|
            if(nested.isEmpty())
 | 
						|
                return prefix;
 | 
						|
            return prefix + Tseparator + nested;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// Does the path-specified node exist in the tree? It does not have to be contained.
 | 
						|
    bool exists(QString path) const
 | 
						|
    {
 | 
						|
        auto sepIndex = path.indexOf(Tseparator);
 | 
						|
        if(sepIndex == -1)
 | 
						|
        {
 | 
						|
            auto found = children.find(path);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            auto prefix = path.left(sepIndex);
 | 
						|
            auto found = children.find(prefix);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            return (*found).exists(path.mid(sepIndex + 1));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// find a node in the tree by name
 | 
						|
    const SeparatorPrefixTree * find(QString path) const
 | 
						|
    {
 | 
						|
        auto sepIndex = path.indexOf(Tseparator);
 | 
						|
        if(sepIndex == -1)
 | 
						|
        {
 | 
						|
            auto found = children.find(path);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return nullptr;
 | 
						|
            }
 | 
						|
            return &(*found);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            auto prefix = path.left(sepIndex);
 | 
						|
            auto found = children.find(prefix);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return nullptr;
 | 
						|
            }
 | 
						|
            return (*found).find(path.mid(sepIndex + 1));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// is this a leaf node?
 | 
						|
    bool leaf() const
 | 
						|
    {
 | 
						|
        return children.isEmpty();
 | 
						|
    }
 | 
						|
 | 
						|
    /// is this node actually contained in the tree, or is it purely structural?
 | 
						|
    bool contained() const
 | 
						|
    {
 | 
						|
        return m_contained;
 | 
						|
    }
 | 
						|
 | 
						|
    /// Remove a path from the tree
 | 
						|
    bool remove(QString path)
 | 
						|
    {
 | 
						|
        return removeInternal(path) != Failed;
 | 
						|
    }
 | 
						|
 | 
						|
    /// Clear all children of this node tree node
 | 
						|
    void clear()
 | 
						|
    {
 | 
						|
        children.clear();
 | 
						|
    }
 | 
						|
 | 
						|
    QStringList toStringList() const
 | 
						|
    {
 | 
						|
        QStringList collected;
 | 
						|
        // collecting these is more expensive.
 | 
						|
        auto iter = children.begin();
 | 
						|
        while(iter != children.end())
 | 
						|
        {
 | 
						|
            QStringList list = iter.value().toStringList();
 | 
						|
            for(int i = 0; i < list.size(); i++)
 | 
						|
            {
 | 
						|
                list[i] = iter.key() + Tseparator + list[i];
 | 
						|
            }
 | 
						|
            collected.append(list);
 | 
						|
            if((*iter).m_contained)
 | 
						|
            {
 | 
						|
                collected.append(iter.key());
 | 
						|
            }
 | 
						|
            iter++;
 | 
						|
        }
 | 
						|
        return collected;
 | 
						|
    }
 | 
						|
private:
 | 
						|
    enum Removal
 | 
						|
    {
 | 
						|
        Failed,
 | 
						|
        Succeeded,
 | 
						|
        HasChildren
 | 
						|
    };
 | 
						|
    Removal removeInternal(QString path = QString())
 | 
						|
    {
 | 
						|
        if(path.isEmpty())
 | 
						|
        {
 | 
						|
            if(!m_contained)
 | 
						|
            {
 | 
						|
                // remove all children - we are removing a prefix
 | 
						|
                clear();
 | 
						|
                return Succeeded;
 | 
						|
            }
 | 
						|
            m_contained = false;
 | 
						|
            if(children.size())
 | 
						|
            {
 | 
						|
                return HasChildren;
 | 
						|
            }
 | 
						|
            return Succeeded;
 | 
						|
        }
 | 
						|
        Removal remStatus = Failed;
 | 
						|
        QString childToRemove;
 | 
						|
        auto sepIndex = path.indexOf(Tseparator);
 | 
						|
        if(sepIndex == -1)
 | 
						|
        {
 | 
						|
            childToRemove = path;
 | 
						|
            auto found = children.find(childToRemove);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return Failed;
 | 
						|
            }
 | 
						|
            remStatus = (*found).removeInternal();
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            childToRemove = path.left(sepIndex);
 | 
						|
            auto found = children.find(childToRemove);
 | 
						|
            if(found == children.end())
 | 
						|
            {
 | 
						|
                return Failed;
 | 
						|
            }
 | 
						|
            remStatus = (*found).removeInternal(path.mid(sepIndex + 1));
 | 
						|
        }
 | 
						|
        switch (remStatus)
 | 
						|
        {
 | 
						|
            case Failed:
 | 
						|
            case HasChildren:
 | 
						|
            {
 | 
						|
                return remStatus;
 | 
						|
            }
 | 
						|
            case Succeeded:
 | 
						|
            {
 | 
						|
                children.remove(childToRemove);
 | 
						|
                if(m_contained)
 | 
						|
                {
 | 
						|
                    return HasChildren;
 | 
						|
                }
 | 
						|
                if(children.size())
 | 
						|
                {
 | 
						|
                    return HasChildren;
 | 
						|
                }
 | 
						|
                return Succeeded;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return Failed;
 | 
						|
    }
 | 
						|
 | 
						|
private:
 | 
						|
    QMap<QString,SeparatorPrefixTree<Tseparator>> children;
 | 
						|
    bool m_contained = false;
 | 
						|
};
 |