4
\$\begingroup\$

I wrote a program for solving the Attribute Parser challenge on HackerRank. The challenge is to read some specified number of lines of HTML-like markup (each line no more than 200 characters; for any attributes, the = sign will be preceded and followed by a space), and perform some specified number of attribute queries (each query no more than 200 characters).

Sample Input

4 3
<tag1 value = "HelloWorld">
<tag2 name = "Name1">
</tag2>
</tag1>
tag1.tag2~name
tag1~name
tag1~value

Sample Output

Name1
Not Found!
HelloWorld

All comments and suggestions are welcome and appreciated.


#include<iostream>
#include<string>
#include<vector>
#include<climits>
#include<algorithm>
#include<map>
#include<sstream>

enum TagType { OPENING_TAG, CLOSING_TAG };

class Tag
{
private:
    std::string tag_name;
    std::map<std::string, std::string> attributes;
    TagType type;
public:
    Tag(const std::string);
    std::string search_for_attribute(const std::string);
    TagType get_type();
    std::string get_name();
};

std::vector<std::string> tokenize_string(const std::string string,
                                         const std::string delimiters)
{
    std::stringstream stream(string);
    std::string line;
    std::vector<std::string> result;
    while (std::getline(stream, line))
    {
        std::size_t previous = 0, pos;
        while ((pos = line.find_first_of(delimiters, previous)) != std::string::npos)
        {
            if (pos > previous)
                result.push_back(line.substr(previous, pos - previous));
            previous = pos + 1;
        }
        if (previous < line.length())
            result.push_back(line.substr(previous, std::string::npos));
    }
    return result;
}
/*
 * Maps the tag to its level of nesting. 0 - outermost tag, above 0 - nesting level
 * Returns a map containing the tag name as a key, and it's nesting level as a value
 */
std::map<std::string, int> map_tag_depths(std::vector<Tag>& tags)
{
    int current_level = -1;
    std::map<std::string, int> tag_depths;

    for (auto tag : tags)
    {

        if (tag.get_type() == OPENING_TAG)
        {
            current_level++;
            tag_depths.insert(std::make_pair(tag.get_name(), current_level));

        }
        else
        {
            current_level--;
        }
    }
    return tag_depths;
}

Tag::Tag(const std::string line)
{
    std::vector<std::string> tokens = tokenize_string(line,"< =\"/>");
    this->tag_name = tokens[0];
    for (auto it = tokens.begin() + 1; it != tokens.end(); it += 2)
    {
        attributes.insert(std::make_pair(*it, *(it + 1)));
    }

    type = (line[1] == '/') ? CLOSING_TAG : OPENING_TAG;
}

std::string Tag::search_for_attribute(const std::string attribute_name)
{
    auto iterator = this->attributes.find(attribute_name);
    if (iterator != attributes.end())
        return iterator->second;
    else
    {
        return "Not Found!";
    }
}
TagType Tag::get_type()
{
    return this->type;
}
std::string Tag::get_name()
{
    return tag_name;
}
void process_queries(std::vector<Tag>& tags,
                     std::vector<std::string>& queries)
{
    std::map<std::string, int> tag_depths = map_tag_depths(tags);
    for (auto query : queries)
    {
        std::vector<std::string> query_tokens = tokenize_string(query,".~");
        bool path_found = true;
        //Check if the path is valid
        for (int i = 0; i < query_tokens.size() - 1; i++)
        {
            auto it = tag_depths.find(query_tokens[i]);
            if (it == tag_depths.end() || it->second != i)
                path_found = false;
        }
        if (path_found)
        {
            //We know that the path is correct, so we can use
            //the tag name referenced last by the query
            std::string tag_name = query_tokens[query_tokens.size() - 2];

            //Name of the attribute in the query
            std::string attribute_name = query_tokens[query_tokens.size() - 1];

            //Find the tag
            for (auto it = tags.begin(); it != tags.end(); it++)
            {
                if (it->get_name() == tag_name)
                {
                    std::cout << it->search_for_attribute(attribute_name)<<'\n';
                    break;

                }
            }
        }
        else
            std::cout << "Not Found!\n";
    }
}
int main()
{
    int N, Q;
    std::cin >> N >> Q;
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    std::vector<Tag> tags;
    while (N--)
    {
        std::string line;
        std::getline(std::cin, line);
        Tag current_tag(line);
        tags.push_back(current_tag);
    }
    std::vector<std::string> queries;
    while (Q--)
    {
        std::string query;
        std::cin >> query;
        queries.push_back(query);
    }
    process_queries(tags, queries);
    return 0;
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Wonderful Mate. Very neatly written. I understood it while debugging it myself but will be great for others if you could add few comments as well around the conditions so that its more easier to understand for everyone.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ It doesn't really need more comments, that is the point of self documenting code. \$\endgroup\$
    – pacmaninbw
    May 5, 2020 at 11:17
  • \$\begingroup\$ Welcome to code review. You made an insightful observation which was good, however, it wasn't really necessary to duplicate the code in the question. \$\endgroup\$
    – pacmaninbw
    May 5, 2020 at 11:19
  • \$\begingroup\$ Thank you guys for the input. I agree about the comments, it would make understanding the code easier. \$\endgroup\$
    – M.R.
    May 6, 2020 at 10:41
  • \$\begingroup\$ No worries Mate. You did a fantastic job. \$\endgroup\$
    – J. Snow
    May 6, 2020 at 10:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.