Ageis
Ageis

Reputation: 2315

Finding all indexes of a specified character within a string

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

Upvotes: 92

Views: 160547

Answers (15)

Sarl sbeer
Sarl sbeer

Reputation: 55

Using recursion function:

let indcies=[];
     function findAllIndecies(str,substr,indexToStart=0) {
            if (indexToStart<str.length) {
               var index= str.indexOf(substr,indexToStart)
               indcies.push(index)
                findAllIndecies(str,substr,index+1)
            }
        }
findAllIndecies("scissors","s")

Upvotes: 0

S Gabale
S Gabale

Reputation: 11

function countClaps(str) {
     const re = new RegExp(/C/g);

    // matching the pattern
    const count = str.match(re).length;

    return count;
}
//countClaps();

console.log(countClaps("CCClaClClap!Clap!ClClClap!"));

Upvotes: -1

Joulss
Joulss

Reputation: 1189

In modern browsers matchAll do the job :

const string = "scissors";
const matches = [...string.matchAll(/s/g)];

You can get the values in several ways. For example :

const indexes = matches.map(match => match.index);

Upvotes: 18

Penny Liu
Penny Liu

Reputation: 17408

Another alternative could be using flatMap.

var getIndices = (s, t) => {
  return [...s].flatMap((char, i) => (char === t ? i + 1 : []));
};

console.log(getIndices('scissors', 's'));
console.log(getIndices('kaios', '0'));

Upvotes: 1

Mamunur Rashid
Mamunur Rashid

Reputation: 1185

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);

Upvotes: 2

Richard
Richard

Reputation: 129

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 }, [] );

Upvotes: 2

alireza
alireza

Reputation: 11

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]

Upvotes: 0

hygull
hygull

Reputation: 8740

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

Upvotes: 0

Yash Kalwani
Yash Kalwani

Reputation: 443

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'.

Upvotes: -2

tpdi
tpdi

Reputation: 35141

indices = (c, s) => s
          .split('')
          .reduce((a, e, i) => e === c ? a.concat(i) : a, []);

indices('?', 'a?g??'); // [1, 3, 4]

Upvotes: 5

davnicwil
davnicwil

Reputation: 30957

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]

Upvotes: 7

Chad
Chad

Reputation: 9859

benchmark

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

Upvotes: 15

vcsjones
vcsjones

Reputation: 141598

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.

enter image description here

Upvotes: 131

Phrogz
Phrogz

Reputation: 303188

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!

Graph of performance results from the link

Upvotes: 36

Tomalak
Tomalak

Reputation: 338148

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.

Upvotes: 14

Related Questions