{x}
blog image

Design File System

Solution Explanation: Design File System

This problem requires designing a file system that supports creating new paths and retrieving their associated values. A Trie data structure is well-suited for this task due to its efficiency in storing and retrieving hierarchical data like file paths.

Approach: Using a Trie

The solution uses a Trie (also known as a prefix tree) to represent the file system. Each node in the Trie represents a directory or file. The path from the root to a node represents the full path to that file or directory. Each node stores:

  • children: A map (dictionary or hash table) to store child nodes (subdirectories or files). The keys are the names of the child directories/files, and the values are pointers to the child nodes.
  • v: An integer representing the value associated with the file (if it's a file node). For directory nodes, this value can be -1 or any other placeholder.

Key Operations:

  1. createPath(path, value): This function attempts to create a new path in the file system. It splits the path into its constituent parts and traverses the Trie. If any parent directory doesn't exist, it returns false. If the path already exists, it also returns false. Otherwise, it creates the necessary nodes and assigns the given value to the leaf node (file) and returns true.

  2. get(path): This function retrieves the value associated with a given path. It traverses the Trie, and if the path exists, it returns the value of the leaf node. If the path doesn't exist, it returns -1.

Time and Space Complexity Analysis

  • Time Complexity:

    • createPath: O(L), where L is the length of the path string. This is because we iterate through the path components once. In the worst case, we might create O(L) new nodes.
    • get: O(L). We iterate through the path components to find the value.
  • Space Complexity: O(N * L), where N is the number of files/directories created, and L is the average length of a path. This is because the Trie stores all the paths in the file system.

Code Implementation (Python3)

class TrieNode:
    def __init__(self):
        self.children = {}  # Dictionary to store child nodes
        self.value = -1     # Value associated with the node (file)
 
 
class FileSystem:
    def __init__(self):
        self.root = TrieNode()
 
    def createPath(self, path: str, value: int) -> bool:
        nodes = path.split('/')[1:]  # Split the path into components
        curr = self.root
        for i, node_name in enumerate(nodes):
            if node_name not in curr.children:
                if i == len(nodes) - 1:  # Create file node
                    curr.children[node_name] = TrieNode()
                    curr.children[node_name].value = value
                    return True
                else:  # Create directory node
                    curr.children[node_name] = TrieNode()
                    curr = curr.children[node_name]
            else:
                if i == len(nodes) - 1:  # Path already exists
                    return False
                else:
                    curr = curr.children[node_name]
        return True  # Should not reach here normally
 
 
    def get(self, path: str) -> int:
        nodes = path.split('/')[1:]
        curr = self.root
        for node_name in nodes:
            if node_name not in curr.children:
                return -1
            curr = curr.children[node_name]
        return curr.value
 

The code in other languages (Java, C++, Go, TypeScript) follows a similar structure, adapting the specific data structures for each language (e.g., HashMap in Java, unordered_map in C++, map in Go). The core Trie logic remains the same.