[javascript] split string only on first instance of specified character

In my code I split a string based on _ and grab the second item in the array.

var element = $(this).attr('class');
var field = element.split('_')[1];

Takes good_luck and provides me with luck. Works great!

But, now I have a class that looks like good_luck_buddy. How do I get my javascript to ignore the second _ and give me luck_buddy?

I found this var field = element.split(new char [] {'_'}, 2); in a c# stackoverflow answer but it doesn't work. I tried it over at jsFiddle...

This question is related to javascript jquery regex

The answer is


Javascript's String.split unfortunately has no way of limiting the actual number of splits. It has a second argument that specifies how many of the actual split items are returned, which isn't useful in your case. The solution would be to split the string, shift the first item off, then rejoin the remaining items::

var element = $(this).attr('class');
var parts = element.split('_');

parts.shift(); // removes the first item from the array
var field = parts.join('_');

I need the two parts of string, so, regex lookbehind help me with this.

_x000D_
_x000D_
const full_name = 'Maria do Bairro';_x000D_
const [first_name, last_name] = full_name.split(/(?<=^[^ ]+) /);_x000D_
console.log(first_name);_x000D_
console.log(last_name);
_x000D_
_x000D_
_x000D_


What do you need regular expressions and arrays for?

myString = myString.substring(myString.indexOf('_')+1)

_x000D_
_x000D_
var myString= "hello_there_how_are_you"_x000D_
myString = myString.substring(myString.indexOf('_')+1)_x000D_
console.log(myString)
_x000D_
_x000D_
_x000D_


I avoid RegExp at all costs. Here is another thing you can do:

"good_luck_buddy".split('_').slice(1).join('_')

For beginner like me who are not used to Regular Expression, this workaround solution worked:

   var field = "Good_Luck_Buddy";
   var newString = field.slice( field.indexOf("_")+1 );

slice() method extracts a part of a string and returns a new string and indexOf() method returns the position of the first found occurrence of a specified value in a string.


You can use the regular expression like:

var arr = element.split(/_(.*)/)
You can use the second parameter which specifies the limit of the split. i.e: var field = element.split('_', 1)[1];

This should be quite fast

function splitOnFirst (str, sep) {
  const index = str.indexOf(sep);
  return index < 0 ? [str] : [str.slice(0, index), str.slice(index + sep.length)];
}

This worked for me on Chrome + FF:

"foo=bar=beer".split(/^[^=]+=/)[1] // "bar=beer"
"foo==".split(/^[^=]+=/)[1] // "="
"foo=".split(/^[^=]+=/)[1] // ""
"foo".split(/^[^=]+=/)[1] // undefined

If you also need the key try this:

"foo=bar=beer".split(/^([^=]+)=/) // Array [ "", "foo", "bar=beer" ]
"foo==".split(/^([^=]+)=/) // [ "", "foo", "=" ]
"foo=".split(/^([^=]+)=/) // [ "", "foo", "" ]
"foo".split(/^([^=]+)=/) // [ "foo" ]

//[0] = ignored (holds the string when there's no =, empty otherwise)
//[1] = hold the key (if any)
//[2] = hold the value (if any)

A simple ES6 way to get both the first key and remaining parts in a string would be:

 const [key, ...rest] = "good_luck_buddy".split('_')
 const value = rest.join('_')
 console.log(key, value) // good, luck_buddy

Use the string replace() method with a regex:

_x000D_
_x000D_
var result = "good_luck_buddy".replace(/.*?_/, "");_x000D_
console.log(result);
_x000D_
_x000D_
_x000D_

This regex matches 0 or more characters before the first _, and the _ itself. The match is then replaced by an empty string.


Nowadays String.prototype.split does indeed allow you to limit the number of splits.

str.split([separator[, limit]])

...

limit Optional

A non-negative integer limiting the number of splits. If provided, splits the string at each occurrence of the specified separator, but stops when limit entries have been placed in the array. Any leftover text is not included in the array at all.

The array may contain fewer entries than limit if the end of the string is reached before the limit is reached. If limit is 0, no splitting is performed.

caveat

It might not work the way you expect. I was hoping it would just ignore the rest of the delimiters, but instead, when it reaches the limit, it splits the remaining string again, omitting the part after the split from the return results.

let str = 'A_B_C_D_E'
const limit_2 = str.split('_', 2)
limit_2
(2) ["A", "B"]
const limit_3 = str.split('_', 3)
limit_3
(3) ["A", "B", "C"]

I was hoping for:

let str = 'A_B_C_D_E'
const limit_2 = str.split('_', 2)
limit_2
(2) ["A", "B_C_D_E"]
const limit_3 = str.split('_', 3)
limit_3
(3) ["A", "B", "C_D_E"]


With help of destructuring assignment it can be more readable:

let [first, ...rest] = "good_luck_buddy".split('_')
rest = rest.join('_')

Mark F's solution is awesome but it's not supported by old browsers. Kennebec's solution is awesome and supported by old browsers but doesn't support regex.

So, if you're looking for a solution that splits your string only once, that is supported by old browsers and supports regex, here's my solution:

_x000D_
_x000D_
String.prototype.splitOnce = function(regex)_x000D_
{_x000D_
    var match = this.match(regex);_x000D_
    if(match)_x000D_
    {_x000D_
        var match_i = this.indexOf(match[0]);_x000D_
        _x000D_
        return [this.substring(0, match_i),_x000D_
        this.substring(match_i + match[0].length)];_x000D_
    }_x000D_
    else_x000D_
    { return [this, ""]; }_x000D_
}_x000D_
_x000D_
var str = "something/////another thing///again";_x000D_
_x000D_
alert(str.splitOnce(/\/+/)[1]);
_x000D_
_x000D_
_x000D_


Here's one RegExp that does the trick.

'good_luck_buddy' . split(/^.*?_/)[1] 

First it forces the match to start from the start with the '^'. Then it matches any number of characters which are not '_', in other words all characters before the first '_'.

The '?' means a minimal number of chars that make the whole pattern match are matched by the '.*?' because it is followed by '_', which is then included in the match as its last character.

Therefore this split() uses such a matching part as its 'splitter' and removes it from the results. So it removes everything up till and including the first '_' and gives you the rest as the 2nd element of the result. The first element is "" representing the part before the matched part. It is "" because the match starts from the beginning.

There are other RegExps that work as well like /_(.*)/ given by Chandu in a previous answer.

The /^.*?_/ has the benefit that you can understand what it does without having to know about the special role capturing groups play with replace().


Replace the first instance with a unique placeholder then split from there.

"good_luck_buddy".replace(/\_/,'&').split('&')

["good","luck_buddy"]

This is more useful when both sides of the split are needed.


Non-regex solution

I ran some benchmarks, and this solution won hugely:1

str.slice(str.indexOf(delim) + delim.length)

// as function
function gobbleStart(str, delim) {
    return str.slice(str.indexOf(delim) + delim.length);
}

// as polyfill
String.prototype.gobbleStart = function(delim) {
    return this.slice(this.indexOf(delim) + delim.length);
};

Performance comparison with other solutions

The only close contender was the same line of code, except using substr instead of slice.

Other solutions I tried involving split or RegExps took a big performance hit and were about 2 orders of magnitude slower. Using join on the results of split, of course, adds an additional performance penalty.

Why are they slower? Any time a new object or array has to be created, JS has to request a chunk of memory from the OS. This process is very slow.

Here are some general guidelines, in case you are chasing benchmarks:

  • New dynamic memory allocations for objects {} or arrays [] (like the one that split creates) will cost a lot in performance.
  • RegExp searches are more complicated and therefore slower than string searches.
  • If you already have an array, destructuring arrays is about as fast as explicitly indexing them, and looks awesome.

Removing beyond the first instance

Here's a solution that will slice up to and including the nth instance. It's not quite as fast, but on the OP's question, gobble(element, '_', 1) is still >2x faster than a RegExp or split solution and can do more:

/*
`gobble`, given a positive, non-zero `limit`, deletes
characters from the beginning of `haystack` until `needle` has
been encountered and deleted `limit` times or no more instances
of `needle` exist; then it returns what remains. If `limit` is
zero or negative, delete from the beginning only until `-(limit)`
occurrences or less of `needle` remain.
*/
function gobble(haystack, needle, limit = 0) {
  let remain = limit;
  if (limit <= 0) { // set remain to count of delim - num to leave
    let i = 0;
    while (i < haystack.length) {
      const found = haystack.indexOf(needle, i);
      if (found === -1) {
        break;
      }
      remain++;
      i = found + needle.length;
    }
  }

  let i = 0;
  while (remain > 0) {
    const found = haystack.indexOf(needle, i);
    if (found === -1) {
      break;
    }
    remain--;
    i = found + needle.length;
  }
  return haystack.slice(i);
}

With the above definition, gobble('path/to/file.txt', '/') would give the name of the file, and gobble('prefix_category_item', '_', 1) would remove the prefix like the first solution in this answer.


  1. Tests were run in Chrome 70.0.3538.110 on macOSX 10.14.

This solution worked for me

var str = "good_luck_buddy";
var index = str.indexOf('_');
var arr = [str.slice(0, index), str.slice(index + 1)];

//arr[0] = "good"
//arr[1] = "luck_buddy"

OR

var str = "good_luck_buddy";
var index = str.indexOf('_');
var [first, second] = [str.slice(0, index), str.slice(index + 1)];

//first = "good"
//second = "luck_buddy"

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to regex

Why my regexp for hyphenated words doesn't work? grep's at sign caught as whitespace Preg_match backtrack error regex match any single character (one character only) re.sub erroring with "Expected string or bytes-like object" Only numbers. Input number in React Visual Studio Code Search and Replace with Regular Expressions Strip / trim all strings of a dataframe return string with first match Regex How to capture multiple repeated groups?