Composite Pattern: Folder Sizes

Problem

A file system is described by lines: depth dir name or depth file name size. The depth tells where an item is: depth 0 is the root, and an item with depth d + 1 that follows an item with depth d belongs to the nearest earlier directory with depth d. The size of a file is given; the size of a directory is the sum of all files inside it, also in sub-directories. Print name: size for every directory, in the order they appear in the input. Use the Composite pattern: files and directories share one interface with getSize().

Constraints

  • 1 <= lines <= 10^4
  • The first line has depth 0 and is a dir
  • |size| <= 10^6

Examples

Input

0 dir root
1 file a 10
1 dir sub
2 file b 5

Output

root: 15
sub: 5

Solutions

Best approachTime O(n * depth)Space O(n)

The Composite pattern lets a group of objects be used like a single object. The directory does not ask if a child is a file or a directory: it just calls getSize(). The recursion happens by itself. The same idea is used for GUI components, menus and organisation charts.

Same code works in Java810172125Basic features only, so nothing changes between versions.
/*
 * Problem : Process lines.
 * Input   : Several lines.
 * Output  : One size per directory.
 * Approach: Tree of files and directories.
 * Time    : O(n * depth)
 * Space   : O(n)
 * Java    : 8 or newer.
 */
import java.util.Scanner;

public class Main {
        // Step 1: Files and directories share one interface. The caller does not need to know which one it has.
        interface Entry {
            long getSize();
        }

        static class FileEntry implements Entry {
            private final long size;

            FileEntry(long size) {
                this.size = size;
            }

            public long getSize() {
                return size;
            }
        }

        // Step 2: A directory is also an Entry. Its size is the sum of the sizes of its children (files or directories).
        static class DirEntry implements Entry {
            final String name;
            final java.util.List<Entry> children = new java.util.ArrayList<>();

            DirEntry(String name) {
                this.name = name;
            }

            public long getSize() {
                long total = 0;
                for (Entry child : children) {
                    total += child.getSize();
                }
                return total;
            }
        }

    // This method solves the task. It receives the input and returns the answer.
    static String[] solve(String[] lines) {
        // Step 3: A stack of directories holds the path to the current place. stack[d] is the directory at depth d.
        java.util.List<DirEntry> stack = new java.util.ArrayList<>();
        java.util.List<DirEntry> all = new java.util.ArrayList<>();
        for (String line : lines) {
            String[] part = line.split(" ");
            int depth = Integer.parseInt(part[0]);
            while (stack.size() > depth) {
                stack.remove(stack.size() - 1);
            }
            if (part[1].equals("dir")) {
                DirEntry directory = new DirEntry(part[2]);
                if (depth > 0) {
                    stack.get(depth - 1).children.add(directory);
                }
                stack.add(directory);
                all.add(directory);
            } else {
                stack.get(depth - 1).children.add(new FileEntry(Long.parseLong(part[3])));
            }
        }
        String[] output = new String[all.size()];
        for (int i = 0; i < output.length; i++) {
            output[i] = all.get(i).name + ": " + all.get(i).getSize();
        }
        return output;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        java.util.List<String> all = new java.util.ArrayList<>();
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine().trim();
            if (!line.isEmpty()) {
                all.add(line);
            }
        }
        String[] lines = all.toArray(new String[0]);
        System.out.println(String.join("\n", solve(lines)));
    }
}
Hints (1)
  1. What makes the recursion possible without an if for the child type?