Sparse Vector Class

Problem

A sparse vector has many zeros, so it keeps only the nonzero entries as index:value pairs (indexes 0 to 99). Commands: vec NAME [i:v ...] (create; entries with value 0 are not kept; there may be no entries), dot A B (print the dot product), add C A B (store the sum A + B as C, dropping zeros), norm2 NAME (the sum of squares of the entries), show NAME (print the entries as index:value in increasing index order, separated by spaces; print empty if none).

Constraints

  • 1 <= commands <= 100
  • 0 <= index < 100
  • |value| <= 1000

Examples

Input

vec a 0:1 3:2
vec b 3:4 5:1
dot a b

Output

8

Solutions

Best approachTime O(n x nnz)Space O(nnz)

Only the nonzero entries are kept, so a vector with 3 nonzero numbers out of a million uses 3 map entries. The dot product loops over one map and looks up the other, so the cost depends on the nonzero count, not on the length.

Same code works in Java810172125Basic features only, so nothing changes between versions.
/*
 * Problem : Run the commands.
 * Input   : Command lines.
 * Output  : One line per answer.
 * Approach: Sparse vector class
 * Time    : O(n x nnz)
 * Space   : O(nnz)
 * Java    : 8 or newer.
 */
import java.util.Scanner;

public class Main {
        // Step 1: Only the nonzero entries are stored, in a sorted map from index to value.
        static class SparseVector {
            private final java.util.TreeMap<Integer, Long> entries = new java.util.TreeMap<Integer, Long>();

            void set(int index, long value) {
                if (value == 0) {
                    entries.remove(index);
                } else {
                    entries.put(index, value);
                }
            }

            // Step 2: The dot product only needs indexes that exist in both vectors. Loop over the smaller map.
            long dot(SparseVector other) {
                long total = 0;
                for (java.util.Map.Entry<Integer, Long> e : entries.entrySet()) {
                    Long match = other.entries.get(e.getKey());
                    if (match != null) {
                        total += e.getValue() * match;
                    }
                }
                return total;
            }

            // Step 3: The sum starts as a copy of this vector, then the other entries are added. Zero results are dropped by set.
            SparseVector plus(SparseVector other) {
                SparseVector result = new SparseVector();
                for (java.util.Map.Entry<Integer, Long> e : entries.entrySet()) {
                    result.set(e.getKey(), e.getValue());
                }
                for (java.util.Map.Entry<Integer, Long> e : other.entries.entrySet()) {
                    long old = result.entries.containsKey(e.getKey()) ? result.entries.get(e.getKey()) : 0;
                    result.set(e.getKey(), old + e.getValue());
                }
                return result;
            }

            String text() {
                StringBuilder sb = new StringBuilder();
                for (java.util.Map.Entry<Integer, Long> e : entries.entrySet()) {
                    sb.append(sb.length() == 0 ? "" : " ").append(e.getKey()).append(":").append(e.getValue());
                }
                return sb.length() == 0 ? "empty" : sb.toString();
            }
        }

    // This method solves the task. It receives the input and returns the answer.
    static String[] solve(String[] lines) {
        java.util.Map<String, SparseVector> named = new java.util.HashMap<String, SparseVector>();
        java.util.List<String> out = new java.util.ArrayList<String>();
        for (String line : lines) {
            String[] part = line.split(" ");
            if (part[0].equals("vec")) {
                SparseVector v = new SparseVector();
                for (int i = 2; i < part.length; i++) {
                    String[] pair = part[i].split(":");
                    v.set(Integer.parseInt(pair[0]), Long.parseLong(pair[1]));
                }
                named.put(part[1], v);
            } else if (part[0].equals("dot")) {
                out.add(String.valueOf(named.get(part[1]).dot(named.get(part[2]))));
            } else if (part[0].equals("add")) {
                named.put(part[1], named.get(part[2]).plus(named.get(part[3])));
            } else if (part[0].equals("norm2")) {
                out.add(String.valueOf(named.get(part[1]).dot(named.get(part[1]))));
            } else {
                out.add(named.get(part[1]).text());
            }
        }
        return out.toArray(new String[0]);
    }

    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 (2)
  1. Why are zero entries removed in set?
  2. What is the cost of dot when both vectors have few nonzero entries?