For example if I had "scissors"
in variable and wanted to know the position of all occurrences of the letter "s"
, it should print out 1, 4, 5, 8
How can I do this in JavaScript in most efficient way? I don't think looping through the whole is terribly efficient
A simple loop works well:
var str = "scissors";
var indices = [];
for(var i=0; i<str.length;i++) {
if (str[i] === "s") indices.push(i);
}
Now, you indicate that you want 1,4,5,8. This will give you 0, 3, 4, 7 since indexes are zero-based. So you could add one:
if (str[i] === "s") indices.push(i+1);
and now it will give you your expected result.
A fiddle can be see here.
I don't think looping through the whole is terribly efficient
As far as performance goes, I don't think this is something that you need to be gravely worried about until you start hitting problems.
Here is a jsPerf test comparing various answers. In Safari 5.1, the IndexOf performs the best. In Chrome 19, the for loop is the fastest.
Using the native String.prototype.indexOf
method to most efficiently find each offset.
function locations(substring,string){
var a=[],i=-1;
while((i=string.indexOf(substring,i+1)) >= 0) a.push(i);
return a;
}
console.log(locations("s","scissors"));
//-> [0, 3, 4, 7]
This is a micro-optimization, however. For a simple and terse loop that will be fast enough:
// Produces the indices in reverse order; throw on a .reverse() if you want
for (var a=[],i=str.length;i--;) if (str[i]=="s") a.push(i);
In fact, a native loop is faster on chrome that using indexOf
!
More functional fun, and also more general: This finds the starting indexes of a substring of any length in a string
const length = (x) => x.length
const sum = (a, b) => a+b
const indexesOf = (substr) => ({
in: (str) => (
str
.split(substr)
.slice(0, -1)
.map(length)
.map((_, i, lengths) => (
lengths
.slice(0, i+1)
.reduce(sum, i*substr.length)
))
)
});
console.log(indexesOf('s').in('scissors')); // [0,3,4,7]
console.log(indexesOf('and').in('a and b and c')); // [2,8]
indices = (c, s) => s
.split('')
.reduce((a, e, i) => e === c ? a.concat(i) : a, []);
indices('?', 'a?g??'); // [1, 3, 4]
I loved the question and thought to write my answer by using the reduce()
method defined on arrays.
function getIndices(text, delimiter='.') {
let indices = [];
let combined;
text.split(delimiter)
.slice(0, -1)
.reduce((a, b) => {
if(a == '') {
combined = a + b;
} else {
combined = a + delimiter + b;
}
indices.push(combined.length);
return combined; // Uncommenting this will lead to syntactical errors
}, '');
return indices;
}
let indices = getIndices(`Ab+Cd+Pk+Djb+Nice+One`, '+');
let indices2 = getIndices(`Program.can.be.done.in.2.ways`); // Here default delimiter will be taken as `.`
console.log(indices); // [ 2, 5, 8, 12, 17 ]
console.log(indices2); // [ 7, 11, 14, 19, 22, 24 ]
// To get output as expected (comma separated)
console.log(`${indices}`); // 2,5,8,12,17
console.log(`${indices2}`); // 7,11,14,19,22,24
When i benchmarked everything it seemed like regular expressions performed the best, so i came up with this
function indexesOf(string, regex) {
var match,
indexes = {};
regex = new RegExp(regex);
while (match = regex.exec(string)) {
if (!indexes[match[0]]) indexes[match[0]] = [];
indexes[match[0]].push(match.index);
}
return indexes;
}
you can do this
indexesOf('ssssss', /s/g);
which would return
{s: [0,1,2,3,4,5]}
i needed a very fast way to match multiple characters against large amounts of text so for example you could do this
indexesOf('dddddssssss', /s|d/g);
and you would get this
{d:[0,1,2,3,4], s:[5,6,7,8,9,10]}
this way you can get all the indexes of your matches in one go
function charPos(str, char) {
return str
.split("")
.map(function (c, i) { if (c == char) return i; })
.filter(function (v) { return v >= 0; });
}
charPos("scissors", "s"); // [0, 3, 4, 7]
Note that JavaScript counts from 0. Add +1 to i
, if you must.
You could probably use the match() function of javascript as well. You can create a regular expression and then pass it as a parameter to the match().
stringName.match(/s/g);
This should return you an array of all the occurrence of the the letter 's'.
Just for further solution, here is my solution: you can find character's indexes which exist in a string:
findIndex(str, char) {
const strLength = str.length;
const indexes = [];
let newStr = str;
while (newStr && newStr.indexOf(char) > -1) {
indexes.push(newStr.indexOf(char) + strLength- newStr.length);
newStr = newStr.substring(newStr.indexOf(char) + 1);
}
return indexes;
}
findIndex('scissors', 's'); // [0, 3, 4, 7]
findIndex('Find "s" in this sentence', 's'); // [6, 15, 17]
Here is a short solution using a function expression (with ES6 arrow functions). The function accepts a string and the character to find as parameters. It splits the string into an array of characters and uses a reduce
function to accumulate and return the matching indices as an array.
const findIndices = (str, char) =>
str.split('').reduce((indices, letter, index) => {
letter === char && indices.push(index);
return indices;
}, [])
Testing:
findIndices("Hello There!", "e");
// ? [1, 8, 10]
findIndices("Looking for new letters!", "o");
// ? [1, 2, 9]
Here is a compact (one-line) version:
const findIndices = (str, char) => str.split('').reduce( (indices, letter, index) => { letter === char && indices.push(index); return indices }, [] );
using while loop
let indices = [];
let array = "scissors".split('');
let element = 's';
let idx = array.indexOf(element);
while (idx !== -1) {
indices.push(idx+1);
idx = array.indexOf(element, idx + 1);
}
console.log(indices);
©2020 All rights reserved.