File naming (two files cannot have equal names)

This is a challenge I completed at CodeSignal.

You are given an array of desired filenames in the order of their creation. Since two files cannot have equal names, the one which comes later will have an addition to its name in a form of (k), where k is the smallest positive integer such that the obtained name is not used yet.

Return an array of names that will be given to the files.

Example

For names = ["doc", "doc", "image", "doc(1)", "doc"], the output should be fileNaming(names) = ["doc", "doc(1)", "image", "doc(1)(1)", "doc(2)"].

function fileNaming(names) {
arr = []
for (let i = 0; i < names.length; i++) {
if (!arr.includes(names[i])) {
arr.push(names[i])
} else {
for (let j=1; ; j++) {
const str = ${names[i]}(${j})
if (!arr.includes(str)) {
arr.push(str)
break
}
}
}
}
return arr
}


The code passed all tests at CodeSignal.

Seems to be some edge cases to take care of here, so good job on that :)

• You could use a Set instead of an array since you're doing lots of checking the whole array for if an item exists. It would also hammer in the fact that the result should be a unique list of names
• First for loop could be replaced with for...of or forEach() to avoid the index variable.
• Readability could be improved by renaming arr => newNames, str => candidateName, fileNaming => deduplicateFileNames, or something like that. All in all it was pretty readable though.
• Missing const before arr

There are some good points in the answer by Magnus Jeffs Tovslid already.

• it is wise to include semi-colons unless you are intimately familiar with the rules of Automatic semicolon insertion or are using a tool like Babel

• the nested for loop could be re-written as a while loop that increments the counter and breaks when it finds a string that isn't in the output array already. With this approach the call to push the new name is outside the loop, and there is no need to manually call break.

let str = ${names[i]}(1), j = 1; while (arr.includes(str)) { str = ${names[i]}(${++j}); } arr.push(str);  And that could be simplified to a for loop- this would allow the counter variable to be scoped to the loop: let str = ${names[i]}(1);
for (let j = 1; arr.includes(str); str = ${names[i]}(${++j})) ; //intentional no-op
arr.push(str);

• A functional approach with names.reduce() could simplify the code for readability, though be aware performance would be affected because of the extra functions being called for each iteration. The example below uses a Set and the spread syntax ... to put the values in the Set into an array.

function fileNaming(names) {
return [...names.reduce((set, name) => {
if (!set.has(name)) {
let str = ${name}(1); for (let j = 1; set.has(str); str = ${name}(\${++j})); //intentional no
console.log('output: ', fileNaming(names));