Design an in-memory file system with the following operations:
ls(path)
: Lists the contents (files and subdirectories) at a given path. If the path is a file, it should return a list containing only the file's name. If it is a directory, it should return a sorted list of the names of the files and subdirectories in this directory.mkdir(path)
: Creates a new directory at the given path. Assumes that the parent directories already exist.addContentToFile(filePath, content)
: Appends the given content to the file at the specified path. If the file does not exist, create it. If the parent directories do not exist, create them as well.readContentFromFile(filePath)
: Returns the content of the file at the given path.You may assume the root directory exists and is represented by "/".
Example:
FileSystem fs = new FileSystem();
fs.ls("/"); // []
fs.mkdir("/a/b/c");
fs.addContentToFile("/a/b/c/d", "hello");
fs.readContentFromFile("/a/b/c/d"); // "hello"
fs.addContentToFile("/a/b/c/d", " world");
fs.readContentFromFile("/a/b/c/d"); // "hello world"
fs.ls("/a/b"); // ["c"]
fs.ls("/a/b/c"); // ["d"]
Discuss the time and space complexity of each operation. Consider edge cases such as invalid paths, adding content to directories, etc.
A naive solution involves using a HashMap
to store the file system structure. Each path can be a key in the HashMap, and the value can be either a string (for files) or another HashMap (for directories). The ls
command would involve traversing the HashMap based on the path and returning the contents. mkdir
would create a new entry in the HashMap for the directory. addContentToFile
would create or append to a file's string value in the HashMap, and readContentFromFile
would simply return the string associated with the file path.
ls
: O(N) in the worst case, where N is the number of directories and files, as we might have to traverse a long path.mkdir
: O(N), where N is the length of the path (number of directories in the path).addContentToFile
: O(N + L), where N is the length of the path and L is the length of the content being added.readContentFromFile
: O(N + M), where N is the length of the path and M is the size of the file's content.O(S), where S is the total size of all paths and file contents stored in the system.
A more optimal solution involves using a tree-like structure to represent the file system. Each node in the tree represents either a directory or a file. A directory node would contain a HashMap of its children (other directories or files). A file node would contain the file's content.
We can define a Node
class:
class Node {
String name;
boolean isFile;
String content;
HashMap<String, Node> children;
public Node(String name, boolean isFile) {
this.name = name;
this.isFile = isFile;
this.content = "";
this.children = new HashMap<>();
}
}
The FileSystem
class would contain a root Node
representing the root directory.
ls(path)
: Split the path into directory names. Traverse the tree to the node represented by the path. If it's a file, return a list containing only the file's name. If it's a directory, return a sorted list of the names of its children.mkdir(path)
: Split the path into directory names. Traverse the tree, creating new directory nodes as needed. If a node already exists, move to the next segment of the path.addContentToFile(path, content)
: Split the path into directory names. Traverse the tree, creating new directory nodes as needed. When you reach the final node (file), append the content to the file's content. If the file does not exist, create it.readContentFromFile(path)
: Split the path into directory names. Traverse the tree to the file node and return its content.ls
: O(M + KlogK), where M is the length of the path, and K is the number of files/directories in the directory being listed. The KlogK comes from sorting the result. If we did not require sorting it would be O(M + K).mkdir
: O(M), where M is the length of the path.addContentToFile
: O(M + L), where M is the length of the path and L is the length of the content being added.readContentFromFile
: O(M + N), where M is the length of the path and N is the size of the file's content.O(T), where T is the total size of all paths and file contents stored in the system. The space is used to store the tree structure and the file contents.
import java.util.*;
class FileSystem {
class Node {
String name;
boolean isFile;
String content;
HashMap<String, Node> children;
public Node(String name, boolean isFile) {
this.name = name;
this.isFile = isFile;
this.content = "";
this.children = new HashMap<>();
}
}
Node root;
public FileSystem() {
root = new Node("", false);
}
public List<String> ls(String path) {
String[] paths = path.split("/");
Node curr = root;
for (int i = 1; i < paths.length; i++) {
String p = paths[i];
if (!curr.children.containsKey(p)) {
return new ArrayList<>(); // Path does not exist
}
curr = curr.children.get(p);
}
if (curr.isFile) {
return Arrays.asList(curr.name);
} else {
List<String> result = new ArrayList<>(curr.children.keySet());
Collections.sort(result);
return result;
}
}
public void mkdir(String path) {
String[] paths = path.split("/");
Node curr = root;
for (int i = 1; i < paths.length; i++) {
String p = paths[i];
if (!curr.children.containsKey(p)) {
curr.children.put(p, new Node(p, false));
}
curr = curr.children.get(p);
}
}
public void addContentToFile(String filePath, String content) {
String[] paths = filePath.split("/");
Node curr = root;
for (int i = 1; i < paths.length - 1; i++) {
String p = paths[i];
if (!curr.children.containsKey(p)) {
curr.children.put(p, new Node(p, false));
}
curr = curr.children.get(p);
}
String fileName = paths[paths.length - 1];
if (!curr.children.containsKey(fileName)) {
curr.children.put(fileName, new Node(fileName, true));
}
curr = curr.children.get(fileName);
curr.content += content;
}
public String readContentFromFile(String filePath) {
String[] paths = filePath.split("/");
Node curr = root;
for (int i = 1; i < paths.length; i++) {
String p = paths[i];
if (!curr.children.containsKey(p)) {
return ""; // Or throw an exception, file not found
}
curr = curr.children.get(p);
}
return curr.content;
}
public static void main(String[] args) {
FileSystem fs = new FileSystem();
fs.mkdir("/a/b/c");
fs.addContentToFile("/a/b/c/d", "hello");
System.out.println(fs.readContentFromFile("/a/b/c/d")); // Output: hello
fs.addContentToFile("/a/b/c/d", " world");
System.out.println(fs.readContentFromFile("/a/b/c/d")); // Output: hello world
System.out.println(fs.ls("/a/b")); // Output: [c]
}
}