145

I would like to get the starting position of the 2nd occurrence of ABC with something like this:

var string = "XYZ 123 ABC 456 ABC 789 ABC";
getPosition(string, 'ABC', 2) // --> 16

How would you do it?

Adam Halasz
  • 57,421
  • 66
  • 149
  • 213

16 Answers16

207

const string = "XYZ 123 ABC 456 ABC 789 ABC";

function getPosition(string, subString, index) {
  return string.split(subString, index).join(subString).length;
}

console.log(
  getPosition(string, 'ABC', 2) // --> 16
)
mplungjan
  • 169,008
  • 28
  • 173
  • 236
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • 47
    I actually don't like this answer. Given an unbounded length input, it needlessly creates an unbounded length array, and then throws most of it away. It would be faster and more efficient just to iteratively use the `fromIndex` argument to `String.indexOf` – Alnitak Jan 23 '13 at 13:31
  • If performances matter a lot, then you could iterate and add the lengths after slicing instead of joining. In some cases, depending on the application real data, you might even cache the array. But none of those optimizations has any weight comparing to the maintainability gain of a non verbose function if you don't measure performances and if there is no performance problem... – Denys Séguret Jan 23 '13 at 13:38
  • 4
    `function getPosition(str, m, i) { return str.split(m, i).join(m).length; }` – copy Jan 23 '13 at 13:42
  • 9
    I would have been good if you specified what each parameter meant. – Foreever Jun 10 '14 at 08:49
  • 2
    @Foreever I've simply implemented the function defined by OP – Denys Séguret Jun 10 '14 at 08:50
  • 6
    This is going to give you the length of the string if there's < `i` occurrences of `m`. That is, `getPosition("aaaa","a",5)` gives `4`, as does `getPosition("aaaa","a",72)`! I think you want -1 in those cases. `var ret = str.split(m, i).join(m).length; return ret >= str.length ? -1 : ret;` You might also want to catch `i <= 0` with `return ret >= str.length || i <= 0 ? -1 : ret;` – ruffin May 04 '15 at 20:43
  • If only wanting 2nd instead of nth, then see this shorter answer: https://stackoverflow.com/questions/19035893/finding-second-occurrence-of-a-substring-in-a-string-in-java/35155037 – Mike Nov 24 '18 at 03:59
104

You can also use the string indexOf without creating any arrays.

The second parameter is the index to start looking for the next match.

function nthIndex(str, pat, n){
    var L= str.length, i= -1;
    while(n-- && i++<L){
        i= str.indexOf(pat, i);
        if (i < 0) break;
    }
    return i;
}

var s= "XYZ 123 ABC 456 ABC 789 ABC";

nthIndex(s,'ABC',3)

/*  returned value: (Number)
24
*/
kennebec
  • 102,654
  • 32
  • 106
  • 127
  • I like this version because of length caching and not extending the String prototype. – Christophe Roussy Jul 23 '15 at 09:46
  • 9
    according to [jsperf](http://jsperf.com/asfsdfv234vwsy-cvwdasvcx/2) this method is way faster than the accepted answer – boop Jan 13 '16 at 17:01
  • The incrementing of `i` can be made less confusing: `var i; for (i = 0; n > 0 && i !== -1; n -= 1) { i = str.indexOf(pat, /* fromIndex */ i ? (i + 1) : i); } return i;` – hlfcoding Apr 03 '16 at 20:41
  • 1
    I prefer this one to the *accepted* answer as when I tested for a second instance which did not exist the other answer returned the length of the first string where this one returned -1. An up-vote and thank you. – John May 13 '18 at 19:25
  • 3
    It's absurd that this isn't a built in feature of JS. – Sinister Beard Jan 07 '19 at 12:09
  • Note that `n` in this answer is the `nth occurrence` rather than an index. So the match at index 0 is number 1 (n=1) – David Mulder Apr 14 '23 at 04:42
20

Working off of kennebec's answer, I created a prototype function which will return -1 if the nth occurence is not found rather than 0.

String.prototype.nthIndexOf = function(pattern, n) {
    var i = -1;

    while (n-- && i++ < this.length) {
        i = this.indexOf(pattern, i);
        if (i < 0) break;
    }

    return i;
}
ilovett
  • 3,240
  • 33
  • 39
  • 2
    **Never** *ever* use camelCase as the eventual adaptation of features natively could unintentionally become overwritten by this prototype. In this case I'd recommend all lower case and underscores (dashes for URLs): `String.prototype.nth_index_of`. Even if you think your name is unique and crazy enough the world will prove that it can and will do crazier. – John May 13 '18 at 18:58
  • *Especially* that when doing prototyping. Sure, no one may ever use *that* specific method name though by allowing yourself to do that you create a bad habit. A different though critical example: *always* enclose data when doing an SQL `INSERT` as `mysqli_real_escape_string` does *not* protect against single quote hacks. Much of professional coding is not just having good habits though also understanding *why* such habits are important. :-) – John May 31 '18 at 05:29
  • 3
    Do not extend the string prototype. –  Oct 18 '18 at 10:32
4

Because recursion is always the answer.

function getPosition(input, search, nth, curr, cnt) {
    curr = curr || 0;
    cnt = cnt || 0;
    var index = input.indexOf(search);
    if (curr === nth) {
        if (~index) {
            return cnt;
        }
        else {
            return -1;
        }
    }
    else {
        if (~index) {
            return getPosition(input.slice(index + search.length),
              search,
              nth,
              ++curr,
              cnt + index + search.length);
        }
        else {
            return -1;
        }
    }
}
Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
  • 1
    @RenanCoelho The tilde (`~`) is the bitwise NOT operator in JavaScript : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT – Sébastien Oct 16 '19 at 11:23
3

Here's my solution, which just iterates over the string until n matches have been found:

String.prototype.nthIndexOf = function(searchElement, n, fromElement) {
    n = n || 0;
    fromElement = fromElement || 0;
    while (n > 0) {
        fromElement = this.indexOf(searchElement, fromElement);
        if (fromElement < 0) {
            return -1;
        }
        --n;
        ++fromElement;
    }
    return fromElement - 1;
};

var string = "XYZ 123 ABC 456 ABC 789 ABC";
console.log(string.nthIndexOf('ABC', 2));

>> 16
Alnitak
  • 334,560
  • 70
  • 407
  • 495
2

This method creates a function that calls for the index of nth occurrences stored in an array

function nthIndexOf(search, n) { 
    var myArray = []; 
    for(var i = 0; i < myString.length; i++) { //loop thru string to check for occurrences
        if(myStr.slice(i, i + search.length) === search) { //if match found...
            myArray.push(i); //store index of each occurrence           
        }
    } 
    return myArray[n - 1]; //first occurrence stored in index 0 
}
Sharon Choe
  • 409
  • 3
  • 8
2

a simple solution just add string, character and idx:

function getCharIdx(str,char,n){
  let r = 0
  for (let i = 0; i<str.length; i++){
    if (str[i] === char){
      r++
      if (r === n){
        return i
      }

    }
   
  }
}
Camille Basbous
  • 299
  • 5
  • 11
  • 34
1

Shorter way and I think easier, without creating unnecessary strings.

const findNthOccurence = (string, nth, char) => {
  let index = 0
  for (let i = 0; i < nth; i += 1) {
    if (index !== -1) index = string.indexOf(char, index + 1)
  }
  return index
}
Piotr
  • 19
  • 2
  • 2
    This function was providing invalid indexes for me in some scenarios if the character was at the beginning/end of the string. – Andrew McOlash Dec 17 '20 at 04:46
0

Using indexOf and Recursion:

First check if the nth position passed is greater than the total number of substring occurrences. If passed, recursively go through each index until the nth one is found.

var getNthPosition = function(str, sub, n) {
    if (n > str.split(sub).length - 1) return -1;
    var recursePosition = function(n) {
        if (n === 0) return str.indexOf(sub);
        return str.indexOf(sub, recursePosition(n - 1) + 1);
    };
    return recursePosition(n);
};
Eric Amshukov
  • 223
  • 3
  • 9
0

Using String.indexOf:

var stringToMatch = "XYZ 123 ABC 456 ABC 789 ABC";

function yetAnotherGetNthOccurance(string, seek, occurance) {
    var index = 0, i = 1;

    while (index !== -1) {
        index = string.indexOf(seek, index + 1);
        if (occurance === i) {
           break;
        }
        i++;
    }
    if (index !== -1) {
        console.log('Occurance found in ' + index + ' position');
    }
    else if (index === -1 && i !== occurance) {
        console.log('Occurance not found in ' + occurance + ' position');
    }
    else {
        console.log('Occurance not found');
    }
}

yetAnotherGetNthOccurance(stringToMatch, 'ABC', 2);
// Output: Occurance found in 16 position

yetAnotherGetNthOccurance(stringToMatch, 'ABC', 20);
// Output: Occurance not found in 20 position

yetAnotherGetNthOccurance(stringToMatch, 'ZAB', 1)
// Output: Occurance not found
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
sk8terboi87 ツ
  • 3,396
  • 3
  • 34
  • 45
0
function getStringReminder(str, substr, occ) {
   let index = str.indexOf(substr);
   let preindex = '';
   let i = 1;
   while (index !== -1) {
      preIndex = index;
      if (occ == i) {
        break;
      }
      index = str.indexOf(substr, index + 1)
      i++;
   }
   return preIndex;
}
console.log(getStringReminder('bcdefgbcdbcd', 'bcd', 3));
matthias_h
  • 11,356
  • 9
  • 22
  • 40
Arul Benito
  • 99
  • 1
  • 2
0

I needed a function that could search from the end of the string too so I wrote this:

function getPos(str, char, index, backwards) {
  var split = str.split(char);
  var result = 0;
  var done = false;

  split.forEach(function (item, i) {
    if (done) {return}
    result += item.length
    if (!backwards && i === index) {
      done = true
      return
    } else if (backwards && i === split.length - index - 2) {
      done = true
      return
    }  
    result += char.length
  })
  return result
}

Usage:

getPos('x x x', 'x', 1, false) // 2
getPos('x x x', 'x', 0, true) // 4
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
T Mack
  • 950
  • 3
  • 12
  • 27
0
var getPosition = function(string, subStr, index) {
    if(!string.includes(subStr)) return null;

    let arrs = string.split(subStr);
    if(arrs.length < index) return null;

    let result = 0;
    for (let i = 0; i < index; i++) {
        result += arrs[i].length;
    }
    result += (index - 1) * subStr.length;
    return result;
}
11zuna
  • 11
  • 1
0
function findNthOccurrence(text, searchText, startIndex, n) {
  let occurrence = 0;
  let index = 0;

  for (let i = 0; i < n; i++) {
    index = text.indexOf(searchText, startIndex);
    if (index === -1) {
      break;
    }
    startIndex = index + 1;
    occurrence++;
  }

  return (occurrence === n) ? index : -1;
}
0

I know this thread is old but I would have done it this way:

function getPosition (string, symbol, nth) {
  return string.indexOf(symbol, (string.indexOf(symbol) + nth));
}

console.log(getPosition('XYZ 123 ABC 456 ABC 789 ABC', 'ABC', 2)); // 16
console.log(getPosition('AbdullA', 'A', 2)); // 6
console.log(getPosition('James Taylor', 'a', 2)); // 7
Asplund
  • 2,254
  • 1
  • 8
  • 19
gindev77
  • 1
  • 3
-2

I was playing around with the following code for another question on StackOverflow and thought that it might be appropriate for here. The function printList2 allows the use of a regex and lists all the occurrences in order. (printList was an attempt at an earlier solution, but it failed in a number of cases.)

<html>
<head>
<title>Checking regex</title>
<script>
var string1 = "123xxx5yyy1234ABCxxxabc";
var search1 = /\d+/;
var search2 = /\d/;
var search3 = /abc/;
function printList(search) {
   document.writeln("<p>Searching using regex: " + search + " (printList)</p>");
   var list = string1.match(search);
   if (list == null) {
      document.writeln("<p>No matches</p>");
      return;
   }
   // document.writeln("<p>" + list.toString() + "</p>");
   // document.writeln("<p>" + typeof(list1) + "</p>");
   // document.writeln("<p>" + Array.isArray(list1) + "</p>");
   // document.writeln("<p>" + list1 + "</p>");
   var count = list.length;
   document.writeln("<ul>");
   for (i = 0; i < count; i++) {
      document.writeln("<li>" +  "  " + list[i] + "   length=" + list[i].length + 
          " first position=" + string1.indexOf(list[i]) + "</li>");
   }
   document.writeln("</ul>");
}
function printList2(search) {
   document.writeln("<p>Searching using regex: " + search + " (printList2)</p>");
   var index = 0;
   var partial = string1;
   document.writeln("<ol>");
   for (j = 0; j < 100; j++) {
       var found = partial.match(search);
       if (found == null) {
          // document.writeln("<p>not found</p>");
          break;
       }
       var size = found[0].length;
       var loc = partial.search(search);
       var actloc = loc + index;
       document.writeln("<li>" + found[0] + "  length=" + size + "  first position=" + actloc);
       // document.writeln("  " + partial + "  " + loc);
       partial = partial.substring(loc + size);
       index = index + loc + size;
       document.writeln("</li>");
   }
   document.writeln("</ol>");

}
</script>
</head>
<body>
<p>Original string is <script>document.writeln(string1);</script></p>
<script>
   printList(/\d+/g);
   printList2(/\d+/);
   printList(/\d/g);
   printList2(/\d/);
   printList(/abc/g);
   printList2(/abc/);
   printList(/ABC/gi);
   printList2(/ABC/i);
</script>
</body>
</html>
Bradley Ross
  • 445
  • 2
  • 8