Increment a Name in Typescript
Published: 2023-08-10 10:11:00

### Introduction

In this blog post, I want to write on a topic, which is based on a problem not originally mine, but which I stumbled upon while reading a blog post of the same title. The orignal [post](https://www.steveruiz.me/posts/incrementing-name) is by [Steve Ruiz](https://twitter.com/steveruizok), As someone interested in multiplayer canvases, I do love what he is doing with tldraw; a multiplayer white board web application and that was what got me to follow him on twitter and interests in his blog. By the way, you should check him out too!

This is a problem he experienced while working on tldraw, and was kind enough to blog about it, as he is also open to improving the code as written on the blog post, I took the shot and I came up with a better performing algorithm. I will describe the problem below with links to the original post containing his solution. This post is how I would solve it And in the spirit of open knowledge sharing, If you think you can improve the code even further, you can take a shot.  


---

### The Problem

> In tldraw, when you create a new page, and if there's already a page with that name, then we want the second page's name to end with an incremented value, e.g. "Blog Post (1)". 

This is an exact quote of the problem as described on the original [post](https://www.steveruiz.me/posts/incrementing-name). The idea is assuming we create a new page with name "Blog Post" and then create a second page without changing the name then it gets a default "Blog Post (1)" page name. Here is an example of what he wants to achieve as described on the post.


```javascript
const existing = ["Blog Post"]

function getName("Blog Post", existing) {
    return "Blog Post (1)" // 0 -> 1
}
```
Even more test cases

```javascript
const existing = ["Blog Post", "Blog Post (1)"]

function getName("Blog Post (1)", existing) {
    return "Blog Post (1) (1)" // 0 -> 1
}
```

```javascript
const existing = ["Blog Post", "Blog Post (1)!"]

function getName("Blog Post (1)!", existing) {
    return "Blog Post (1)! (1)" // 0 -> 1
}
```

```javascript
const existing = ["Blog Post", "Blog Post (1)!"]

function getName("Blog Post (1)", existing) {
    return "Blog Post (1)" // no change here
}
```

### The Solution

Here is how I would solve it.

```typescript
export function getNameMap(name: string, others: Map<string, number>) {
  let key = name;

  if (!others.has(key)) {
    others.set(key, 1);
  } else {
    let currentCount = Number(others.get(key));
    others.set(key, currentCount + 1);
    key = `${key} (${currentCount})`;
    others.set(key, 1);
  }

  return key;
}
```

To explain what is happening here, The function `getNameMap` accepts two argument, the name of the page to create and the existing names already created, which unlike in the original solution is a `Map`. I used a `Map` because it is a good data structure for what we are trying to achieve. We want to track the names that exist and the key value nature of the `Map` makes it easy for us to do so.

Next I check if the page name we want to get exists on the `Map`, if not, I simply add it to the `Map`, using the name as the key for the map and a value of 1. 

But if the page name already exists, I get the current value; which for the first duplicate would be 1, I increment it by 1 and update the existing name with this incremented value. 

Then using the existing name, I append the previous value to it and set it as key in the map with initial value of 1. Eg. For an existing name `Blog` appending the previous value to this name means we get `Blog (1)`


### Summary

The original solution uses a `Set`, which by design ensures items are unique, but in this solution, uses a linear operation to find and update existing name. This operation could cost at worst case `O(N)`. My solution uses a `Map` and lookup and updates are done in constant time `O(1)`. Also, I tend to avoid using regex as I find them complicated and hard to read and I try not to use it when possible. 

Here is a [link](https://codesandbox.io/s/epic-sanne-m8gtcs?file=/src/index.ts:708-730) to my solution and the original solution with tests.