class FileNode:
def __init__(self):
self.is_file = False
self.children = {} # name: FileNode
self.content = ""
class FileSystem:
def __init__(self):
self.root = FileNode()
def ls(self, path: str) -> list:
curr = self.root
if path == "/":
return sorted(curr.children.keys())
parts = path.split("/")[1:]
for part in parts:
curr = curr.children[part]
if curr.is_file:
return [parts[-1]]
return sorted(curr.children.keys())
def mkdir(self, path: str) -> None:
curr = self.root
parts = path.split("/")[1:]
for part in parts:
if part not in curr.children:
curr.children[part] = FileNode()
curr = curr.children[part]
def addContentToFile(self, filePath: str, content: str) -> None:
curr = self.root
parts = filePath.split("/")[1:]
for part in parts[:-1]:
if part not in curr.children:
curr.children[part] = FileNode()
curr = curr.children[part]
file_name = parts[-1]
if file_name not in curr.children:
curr.children[file_name] = FileNode()
curr = curr.children[file_name]
curr.is_file = True
curr.content += content
def readContentFromFile(self, filePath: str) -> str:
curr = self.root
parts = filePath.split("/")[1:]
for part in parts:
curr = curr.children[part]
return curr.content
class FileNode {
public:
bool isFile;
string content;
unordered_map<string, FileNode*> children;
FileNode() : isFile(false), content("") {}
};
class FileSystem {
FileNode* root;
public:
FileSystem() {
root = new FileNode();
}
vector<string> ls(string path) {
FileNode* curr = root;
if (path == "/") {
vector<string> res;
for (auto& kv : curr->children)
res.push_back(kv.first);
sort(res.begin(), res.end());
return res;
}
vector<string> parts = split(path, '/');
for (string& part : parts)
curr = curr->children[part];
if (curr->isFile)
return {parts.back()};
vector<string> res;
for (auto& kv : curr->children)
res.push_back(kv.first);
sort(res.begin(), res.end());
return res;
}
void mkdir(string path) {
FileNode* curr = root;
vector<string> parts = split(path, '/');
for (string& part : parts) {
if (!curr->children.count(part))
curr->children[part] = new FileNode();
curr = curr->children[part];
}
}
void addContentToFile(string filePath, string content) {
FileNode* curr = root;
vector<string> parts = split(filePath, '/');
for (int i = 0; i < parts.size() - 1; ++i) {
string part = parts[i];
if (!curr->children.count(part))
curr->children[part] = new FileNode();
curr = curr->children[part];
}
string fileName = parts.back();
if (!curr->children.count(fileName))
curr->children[fileName] = new FileNode();
curr = curr->children[fileName];
curr->isFile = true;
curr->content += content;
}
string readContentFromFile(string filePath) {
FileNode* curr = root;
vector<string> parts = split(filePath, '/');
for (string& part : parts)
curr = curr->children[part];
return curr->content;
}
private:
vector<string> split(const string& s, char delim) {
vector<string> res;
stringstream ss(s);
string item;
while (getline(ss, item, delim)) {
if (!item.empty())
res.push_back(item);
}
return res;
}
};
class FileNode {
boolean isFile = false;
String content = "";
Map<String, FileNode> children = new HashMap<>();
}
class FileSystem {
private FileNode root;
public FileSystem() {
root = new FileNode();
}
public List<String> ls(String path) {
FileNode curr = root;
if (path.equals("/")) {
List<String> res = new ArrayList<>(curr.children.keySet());
Collections.sort(res);
return res;
}
String[] parts = path.split("/");
for (int i = 1; i < parts.length; i++)
curr = curr.children.get(parts[i]);
if (curr.isFile)
return Arrays.asList(parts[parts.length - 1]);
List<String> res = new ArrayList<>(curr.children.keySet());
Collections.sort(res);
return res;
}
public void mkdir(String path) {
FileNode curr = root;
String[] parts = path.split("/");
for (int i = 1; i < parts.length; i++) {
curr.children.putIfAbsent(parts[i], new FileNode());
curr = curr.children.get(parts[i]);
}
}
public void addContentToFile(String filePath, String content) {
FileNode curr = root;
String[] parts = filePath.split("/");
for (int i = 1; i < parts.length - 1; i++) {
curr.children.putIfAbsent(parts[i], new FileNode());
curr = curr.children.get(parts[i]);
}
String fileName = parts[parts.length - 1];
curr.children.putIfAbsent(fileName, new FileNode());
curr = curr.children.get(fileName);
curr.isFile = true;
curr.content += content;
}
public String readContentFromFile(String filePath) {
FileNode curr = root;
String[] parts = filePath.split("/");
for (int i = 1; i < parts.length; i++)
curr = curr.children.get(parts[i]);
return curr.content;
}
}
class FileNode {
constructor() {
this.isFile = false;
this.content = "";
this.children = new Map();
}
}
class FileSystem {
constructor() {
this.root = new FileNode();
}
ls(path) {
let curr = this.root;
if (path === "/") {
return Array.from(curr.children.keys()).sort();
}
let parts = path.split("/").filter(Boolean);
for (let part of parts) {
curr = curr.children.get(part);
}
if (curr.isFile) {
return [parts[parts.length - 1]];
}
return Array.from(curr.children.keys()).sort();
}
mkdir(path) {
let curr = this.root;
let parts = path.split("/").filter(Boolean);
for (let part of parts) {
if (!curr.children.has(part)) {
curr.children.set(part, new FileNode());
}
curr = curr.children.get(part);
}
}
addContentToFile(filePath, content) {
let curr = this.root;
let parts = filePath.split("/").filter(Boolean);
for (let i = 0; i < parts.length - 1; i++) {
let part = parts[i];
if (!curr.children.has(part)) {
curr.children.set(part, new FileNode());
}
curr = curr.children.get(part);
}
let fileName = parts[parts.length - 1];
if (!curr.children.has(fileName)) {
curr.children.set(fileName, new FileNode());
}
curr = curr.children.get(fileName);
curr.isFile = true;
curr.content += content;
}
readContentFromFile(filePath) {
let curr = this.root;
let parts = filePath.split("/").filter(Boolean);
for (let part of parts) {
curr = curr.children.get(part);
}
return curr.content;
}
}
You are asked to design an in-memory file system that supports the following operations:
ls(path)
: If the path is a file, return its name; if it's a directory, return a sorted list of files and directories inside.mkdir(path)
: Create a directory at the given path, including any intermediate directories if they do not exist.addContentToFile(filePath, content)
: Add content to a file. If the file does not exist, create it.readContentFromFile(filePath)
: Return the content of the file at the given path.'/'
. Paths are always absolute (start with '/'
). All file and directory names consist of lowercase letters and numbers.
At first glance, the problem resembles building a tree structure, where each node represents either a file or a directory. Since directories can contain files or other directories, a tree (specifically, a trie-like structure) is a natural fit.
A brute-force approach might use a flat hash map from paths to contents, but this would make directory navigation and listing inefficient and error-prone. Instead, representing the file system as a nested hierarchy allows us to efficiently traverse, create, and modify files and directories.
The main challenge is to distinguish files from directories and efficiently walk the path for each operation. We also need to handle cases where intermediate directories/files do not exist, especially for mkdir
and addContentToFile
.
We model the file system as a tree, where each node represents either a file or a directory.
is_file
).'/'
and walk the tree from the root, creating nodes as needed (for mkdir
and addContentToFile
).Let's walk through an example sequence of operations:
mkdir("/a/b/c")
: Creates directories a
, b
, and c
under root. The tree now has a path /a/b/c
.addContentToFile("/a/b/c/d", "hello")
: Navigates to /a/b/c
, creates file d
, and sets its content to "hello".ls("/a/b/c")
: Returns ["d"]
because d
is the only child of c
.readContentFromFile("/a/b/c/d")
: Returns "hello".ls("/")
: Returns ["a"]
since a
is the only child of root.ls("/a/b/c/d")
: Since this is a file, returns ["d"]
.Brute-force Approach:
ls
or mkdir
could require scanning all keys, which is O(N) in the number of files/directories.K
components, the time complexity is O(K).ls
on a directory, we also sort the child names, which is O(M log M) where M is the number of children.By modeling the in-memory file system as a tree structure with nodes for files and directories, we achieve efficient and intuitive operations for all required commands. Traversing and modifying the tree is straightforward, and the design naturally supports hierarchical file organization. The main insight is to treat the file system as a trie-like structure, enabling O(K) navigation where K is the path length, and to clearly distinguish files from directories in each node.