631. Design Excel Sum Formula


Problem Explanation

The problem is about creating a simplified Excel spreadsheet using a 2D array as the backend data structure. The height, H, and the width, W, of the Excel is given at the time of construction where H is a positive integer, and W is an uppercase letter denoting the maximum width of the Excel sheet. The user should be able to set a value to any cell in the spreadsheet by specifying the row and column. Along with that, the user should be able to get the value stored in any cell. Additionally, the program should support the capability to calculate the sum of a list of cells or a range of cells and store the sum in a particular cell.

Walkthrough of an Example

Let's go through a scenario to understand how a solution might look like.

If we create an Excel using Excel(3, 'C'), it means we have an Excel spreadsheet with 3 rows and 3 columns, and all the cells have an initial value of 0.



   A B C 
1 0 0 0 
2 0 0 0 
3 0 0 0

If we execute Set(1, "A", 2), it means we are setting the cell at row 1, column 'A' to 2.



   A B C 
1 2 0 0 
2 0 0 0 
3 0 0 0

Solution Explanation

The approach to solve this problem is by using a 2D array to simulate the Excel spreadsheet. Each cell in this 2D array is an object of type Cell. Each Cell object stores the value of the cell and an unordered map of cells that contribute to its value. If a sum operation is being performed, then the posCount map in the cell where the sum is being stored, will keep track of each cell's position that contributes to its value, along with the corresponding contribution count.

For example, in the initial scenario where Excel of size (3, 'C') was created, the 2D array would be initialized to all zeros:



[
 [0, 0, 0], 
 [0, 0, 0], 
 [0, 0, 0]
]

Python Solution


python
class Cell:
    def __init__(self):
        self.val = 0
        self.posCount = {}
    
class Excel:

    def __init__(self, H: int, W: str):
        self.width = ord(W) - ord('A') + 1 
        self.sheet = [[Cell() for _ in range(self.width)] for _ in range(H)]

    def set(self, r: int, c: str, v: int) -> None:
        self.sheet[r-1][ord(c)-ord('A')].val = v
        self.sheet[r-1][ord(c)-ord('A')].posCount = {}

    def get(self, r: int, c: str) -> int:
        cell = self.sheet[r-1][ord(c)-ord('A')]
        return cell.val if not cell.posCount else self.sum(r, c, [f"{k[0]}{k[1]}" for k in cell.posCount.keys()])

    def sum(self, r: int, c: str, strs: List[str]) -> int:
        self.sheet[r-1][ord(c)-ord('A')].posCount = {}
        total = 0
        for st in strs:
            if ':' not in st:
                val = self.get(int(st[1:]), st[0])
                self.sheet[r-1][ord(c)-ord('A')].posCount[(st[0], int(st[1:]))] = 1
                total += val
            else:
                st_split = st.split(':')
                r1, c1, r2, c2 = int(st_split[0][1:]),st_split[0][0], int(st_split[1][1:]), st_split[1][0]
                for row in range(r1, r2+1):
                    for col in range(ord(c1)-ord('A'), ord(c2)-ord('A')+1):
                        total += self.sheet[row][col].val
                        self.sheet[r-1][ord(c)-ord('A')].posCount[(chr(ord('A') + col), row+1)] = 1 
        self.sheet[r-1][ord(c)-ord('A')].val = total
        return total

In this code we use a Cell class which will store the value of the cell along with a map posCount of cells that contribute to its value. In the constructor we initialize width and sheet. In the set method we are setting a specific cell's value and resetting its posCount. For the get method, if a cell's value is being calculated from other cells, we have to calculate it, otherwise return the value of the cell directly. The sum method updates the posCount for a cell whose value will be computed by the sum of other cells or ranges of cells. It returns this sum.

JavaScript Solution


javascript
class Cell {
    constructor() {
        this.val = 0;
        this.posCount = {};;
    }
}

class Excel {
    constructor(H, W) {
        this.width = W.charCodeAt(0) - 'A'.charCodeAt(0) + 1;
        this.sheet = new Array(H).fill(0).map(() => new Array(this.width).fill(new Cell()));
    }

    set(r, c, v) {
        this.sheet[r][c.charCodeAt(0) - 'A'.charCodeAt(0)] = new Cell();
        this.sheet[r][c.charCodeAt(0) -'A'.charCodeAt(0)].val = v;
    }

    get(r, c) {
        let cell = this.sheet[r][c.charCodeAt(0) - 'A'.charCodeAt(0)];
        if (!cell.posCount) {
            return cell.val;
        } else {
            return this.sum(r, c, Object.keys(cell.posCount));
        }
    }

    sum(r, c, strs) {
        this.sheet[r][c.charCodeAt(0) - 'A'.charCodeAt(0)] = new Cell();
        let total = 0;
        for (let str of strs) {
            if (!str.includes(":")) {
                let val = this.get(Number(str.slice(1)), str[0]);
                this.sheet[r][c.charCodeAt(0) - 'A'.charCodeAt(0)].posCount[str] = 1;
                total += val;
            } else {
                let [start, end] = str.split(":");
                let [r1, c1, r2, c2] = [Number(start.slice(1)), start[0], Number(end.slice(1)), end[0]];
                for (let row = r1; row <= r2; row++) {
                    for (let col = c1.charCodeAt(0) - 'A'.charCodeAt(0); col <= c2.charCodeAt(0) - 'A'.charCodeAt(0); col++) {
                        total += this.sheet[row][col].val;
                        let cellPos = `${String.fromCharCode(65 + col)}${row+1}`;
                        this.sheet[r][c.charCodeAt(0) - 'A'.charCodeAt(0)].posCount[cellPos] = 1;
                    }
                }
            }
        }
        this.sheet[r][c.charCodeAt(0) - 'A'.charCodeAt(0)].val = total;
        return total;
    }
}

Another similar approach used except in JavaScript. Notice that the conversion between char and ASCII value is done using charCodeAt(0).

Java Solution


java
class Excel {
    class Cell {
        int val = 0;
        Map<String, Integer> cells = new HashMap<>();
    }

    Cell[][] table;

    public Excel(int H, char W) {
        table = new Cell[H+1][W-'A'+1];
        for(int i=0; i <= H; i++){
            for(int j=0; j <= W-'A'; j++){
                table[i][j] = new Cell();
            }
        }
    }

    public void set(int r, String c, int v) {
        Cell cell = table[r][c.charAt(0)-'A'];
        cell.val = v;
        cell.cells.clear();
    }

    public int get(int r, String c) {
        if(table[r][c.charAt(0)-'A'].cells.isEmpty()){
            return table[r][c.charAt(0)-'A'].val;
        } else {
            return sum(r, c, new String[]{c});
        }
    }

    public int sum(int r, String c, String[] strs) {
        Cell cell = table[r][c.charAt(0)-'A'];
        cell.cells.clear();
        int total = 0;
        for(String s : strs){
            if(s.indexOf(":") < 0){
                total += get(Integer.parseInt(s.substring(1)), s.substring(0,1));
                cell.cells.put(s, cell.cells.getOrDefault(s, 0) + 1);
            } else {
                String[] range = s.split(":");
                int rowStart = Integer.parseInt(range[0].substring(1));
                char colStart = range[0].charAt(0);
                int rowEnd = Integer.parseInt(range[1].substring(1));
                char colEnd = range[1].charAt(0);
                for(int i=rowStart; i<=rowEnd; i++){
                    for(char j=colStart; j<=colEnd; j++){
                        total += get(i, String.valueOf(j));
                        String key = j + String.valueOf(i);
                        cell.cells.put(key, cell.cells.getOrDefault(key, 0) + 1);
                    }
                }
            }
        }
        cell.val = total;
        return total;
    }
}

The Java solution uses a Cell class similar to the previous solutions, with a val attribute to store the value of the cell and a cells HashMap to store any cells that contribute to its value. The set and get methods are straightforward, while the sum method involves more complex logic to deal with ranges. If a range is provided, the range is parsed and a nested loop is used to loop through the cells in the range, while updating the total and the cell HashMap as needed. The final total sum is then stored in the cell and returned.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator
Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More