DOJO Links:
- Kata: https://www.codewars.com/kata/first-non-repeating-character
- Discussion: https://www.codewars.com/kata/first-non-repeating-character/discuss
- Solutions: https://www.codewars.com/kata/first-non-repeating-character/solutions
checks if a character is non-repeating by looking for the 2nd match in a string and ensuring that this 2nd match exists. Based on
String.prototype.indexOf
(check the second usage in the Syntax section on MDN).
const firstNonRepeatingLetter = function (string) {
const stringLower = string.toLowerCase();
for (var i = 0; i < string.length; i++)
if (stringLower.indexOf(stringLower[i], stringLower.indexOf(stringLower[i]) + 1) == -1)
return string[i];
return '';
}
Solution is based on creating a new string
newStr
with the non-repeating characters only, then returning the first character from such string if it exsts, otherwise return an empty string.
This solution uses two nestedfor
loops and is based onString.prototype.replace
to delete repeated characters from a string.
const firstNonRepeatingLetter = function (str) {
let newStr = str;
for (let i = 0; i < str.length; i++)
for (let j = 0; j < str.length; j++)
if (i === j)
continue;
else if (str[i].toUpperCase() === str[j].toUpperCase())
newStr = newStr.replace(`${str[i]}`, '');
return newStr.length > 0 ? newStr[0] : '';
}
These solutions are all based on the
Array.prototype.reduce
method and incorporate an array of smart ideas and tricks related to mutating the array during a reduce, or using a right reduce instead of a standard (i.e. left) reduce. In one of the variants, an exception is used to escape a standard (i.e. left) reduce. All of these solutions use the formulatext.indexOf(letter) === text.lastIndexOf(letter)
to detect a non-repeating character (i.e.letter
) in a string (i.e.text
), which utilizes the two methods:String.prototype.indexOf
andString.prototype.lastIndexOf
// Uses exceptions to escape a reduce
const firstNonRepeatingLetter = function (param) {
try {
param.split('')
.reduce((a, e) => {
if (param.indexOf(e) === param.lastIndexOf(e)) throw e;
}, '')
} catch (solution) {
return solution
}
return ''
}
// Does not work for case insensitive test cases
const firstNonRepeatingLetter = (param) =>
param.split('')
.reduceRight((a, e) =>
(param.indexOf(e) === param.lastIndexOf(e) ? e : a)
, '')
// Modified for case insensitive test cases
const firstNonRepeatingLetter = (param) =>
param.split('')
.reduceRight((a, e) =>
([...param.matchAll(RegExp(e, 'gi'))].length === 1 ? e : a)
, '');
// Modified for case insensitive test cases
const firstNonRepeatingLetter = (param) =>
param.split('')
.reduceRight((a, e1) =>
( param.split('').filter( e2 => e1.toLowerCase() === e2.toLowerCase() )).length === 1 ? e1 : a
, '');
// Modified for case insensitive test cases
const firstNonRepeatingLetter = (param) =>
param.split('')
.reduceRight((a, e1) =>
( param.split('')
.filter( e2 => e1.toLowerCase() === e2.toLowerCase() )
).length === 1 ? e1 : a
, '');
// Mutate array during reduce, does not work in case insensitive test cases
const firstNonRepeatingLetter = (param) =>
(arr = param.split(''))
.reduce((a, e) =>
(param.indexOf(e) === param.lastIndexOf(e) ? (e + ((arr.length = 0) || '')) : a)
, '')
// Mutate array during reduce, works for case insensitive test cases
const firstNonRepeatingLetter = (param) =>
(arr = (paramLower = param.toLowerCase())
.split(''))
.reduce((a, e, i) =>
(paramLower.indexOf(e) === paramLower.lastIndexOf(e) ?
(param[i] + ((arr.length = 0) || '')) : a)
, '');
This solution is based on the
Set()
constructor in Javascript, theArray.prototype.find
method, case-insensitive regular expressions, and theString.prototype.matchAll
method. It is a neat solution and highly readable.
const firstNonRepeatingLetter = text =>
[...new Set(text.split(''))]
.find(letter => [...text.matchAll(RegExp(letter, 'gi'))].length === 1)
// Improved
const firstNonRepeatingLetter = text =>
[...new Set(text.split(''))]
.find(letter => text.match(RegExp(letter, 'gi')).length === 1) || ''
// Simplified
const firstNonRepeatingLetter = text =>
text.split('').find(letter => text.match(RegExp(letter, 'gi')).length === 1) || ''