468

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

21 Answers 21

664

Use capturing parentheses:

'good_luck_buddy'.split(/_(.*)/s)
['good', 'luck_buddy', ''] // ignore the third element

They are defined as

If separator contains capturing parentheses, matched results are returned in the array.

So in this case we want to split at _.* (i.e. split separator being a sub string starting with _) but also let the result contain some part of our separator (i.e. everything after _).

In this example our separator (matching _(.*)) is _luck_buddy and the captured group (within the separator) is luck_buddy. Without the capturing parenthesis the luck_buddy (matching .*) would've not been included in the result array as it is the case with simple split that separators are not included in the result.

We use the s regex flag to make . match on newline (\n) characters as well, otherwise it would only split to the first newline.

Sign up to request clarification or add additional context in comments.

18 Comments

Just to be clear, the reason this solution works is because everything after the first _ is matched inside a capturing group, and gets added to the token list for that reason.
Anyone know why I get an extra empty string element with this: in: "Aspect Ratio: 16:9".split(/:(.+)/) out: ["Aspect Ratio", " 16:9", ""]
@katylavallee - This might help: stackoverflow.com/questions/12836062/… Since the separator is ": 16:9", there is nothing after the separator, thus creating the empty string at the end.
No need for ? and you better use /_(.*)/ (* instead of +) to support string that ends with _
@YanFoto you can use /_([^]+)/ specifically [^] to capture all characters including new lines
|
320

What do you need regular expressions and arrays for?

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

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

6 Comments

i think this is the best answer. it is also possible to get string after second _ by writing: myString.substring( myString.indexOf('_', myString().indexOf('_') + 1) + 1 )
The answer outputs the second part of the string. What if you want the first part, too? With var str = "good_luck_buddy", res = str.split(/_(.+)/);you get all parts: console.log(res[0]); console.log(res[1]);
@PeterLeger let split = [ string.substring(0, string.indexOf(options.divider)), string.substring(string.indexOf(options.divider) + 1) ] There you have it. Also with support of variable needle
So long as the character you're splitting on isn't the end of the string.
A disadvantage of this solution is, you cannot use it for piping/chaining as you can do with map/filter/reduce/flatMap/split because you need to define a variable first. Creating a new variable someone inbetween also looks more complicated to readers. Does not work in expression-only contexts.
|
110

With help of destructuring assignment it can be more readable:

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

5 Comments

best answer for ES6
Well, how perfectly depends on how much separators the rest has.
Wish JavaScript was more like python, this is the best we can do with JavaScript.
Good idea, but practically it differs from split() by creating two string variables instead of providing one array with two strings. I think, you can only use the creation of variables inside a statement context but not inside an expression context. (People can wrap their statement inside a lambda expression of course, but it's quite inconvenient.)
Not the fastest for loops.
79

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

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

9 Comments

One who is afraid of RegExp can never be told how great RegExp is. You need to find the door yourself. Once you're there, you'll never look back. Ask me again in a few years and you will tell mé how great it is.
@yonas Yeah, take the red pill! It will make your life faster, even for short strings: jsperf.com/split-by-first-colon
Nice, this is just what I was trying to do. @bfontaine who cares about how efficiently the computer can do it when what really matters is how efficiently another developer can understand it, which is why RegExp should be avoided. Christiaan Westerbeek has a good point that it can be great in some cases, which is can't agree with avoiding it at ALL costs. The pain you put your fellow developers through usually outweighs the convenience but not always.
Ha! I wrote this comment 4+ years ago. I am definitely on board with RegExp now! :)
@yonas you better don't. RegExp is awesome when you need it. Not the case here. Check updated test: jsperf.com/split-by-first-colon/2
|
33

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

1 Comment

This is an exact copy of @ont.rif's answer... 6 months later... why?
29

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"]

4 Comments

Same here. Seems like PHP is splitting into "first" and the "rest".
This is also how the java String.split() works too.
I actually do not understand how they could needlessly define the split limit as a slice operation? It is so easy to write a .slice(limit) behind it while there still is no simple expression for stopping the split operation after the n-th split.
This combines with top answer for best result 'good_luck_buddy'.split(/_(.*)/s,2)
20

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"

1 Comment

This does not work if the splitter has more than 1 character, however.
17

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

5 Comments

That only specifies how many of the split items are returned, not how many times it splits. 'good_luck_buddy'.split('_', 1); returns just ['good']
Thanks made an assumption on that. Updated the post to use a regular expression.
Was (:?.*) supposed to be a non-capturing group? If so, it should be (?:.*), but if you correct it you'll find it doesn't work any more. (:?.*) matches an optional : followed by zero or more of any character. This solution ends up working for the same reason @MarkF's does: everything after the first _ is added to the token list because it was matched in a capturing group. (Also, the g modifier has no effect when used in a split regex.)
Thanks, didn't realize it. Updated the Regex and tried it over couple of scenarion...
It doesnt work in ie8 and i switch back to indexOf and substring
15

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.

6 Comments

Come on... It's 2019... Are people out there really still microbenchmarking this kind of thing?
I agree. Although microbenchmarking is slightly interesting, you should rely on a compiler or translator for optimizations.Who knows. Mb someone reading this is building a compiler or using ejs / embedded and can't use regex. However, this looks nicer for my specific case than a regex. (I'd remove the "fastest solution")
I have no doubt that the JIT compiler helps somewhat. However, these tests show that performance gains can still be made (and complicated, bug-prone regexes avoided) using simple string functions.
How complex is a simple regex really, compared to multiple nested loops, branches, and index tracking? Baby, meet bathwater. You'll be taking a trip together.
This is one-liner I was looking for. As for regex benchmarking, did you try to move regex itself out of the testing loop into (module) globals? This helps usually, so the difference won't be that impressive. And as for people microbenchmarking things, it's nice to have a working, well-tested and a fast version of split-with-a-limit, which Javascript notably lacks but other languages often have in their stdlibs. Sometimes these things outweigh simplicity.
|
12

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

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

2 Comments

best answer here!
just in case it's not clear to anyone, if you want to split on a different character, you have to make two edits to that reges. like to split on dot: const [first, rest] = 'a.b.c'.split(/(?<=^[^\.]+)\./)
11

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.

6 Comments

This puts an unnecessary constraint on the string.
@YanFoto you mean by using '&'? It could be anything.
@sebjwallace Whatever you choose, it means you can't have that character in the string. E.g. "fish&chips_are_great" gives [fish, chips, are_great] I think.
@Joe You could use anything instead of '&' - it was just an example. You could replace the first occurrence of _ with ¬ if you wanted. So "fish&chips_are_great" would replace the first occurrence of _ with ¬ to give "fish&chips¬are_great" then split by ¬ to get ["fish&chips","are_great"]
Although smart, but like others said cannot have that character in string for this method to work.
|
7

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

console.log(splitOnFirst('good_luck', '_')[1])
console.log(splitOnFirst('good_luck_buddy', '_')[1])

1 Comment

I was going to give this exact answer before noticing this was already here. I'm as big a fan of regex as anyone, but this is a job for indexOf for sure. This should be the accepted answer. Absolutely crazy the amount of unnecessary complication in the other answers here.
4

Use the string replace() method with a regex:

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

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

4 Comments

The document.body.innerHTML part here is completely useless.
@VictorSchröder how do you expect to see the output of the snippet without document.body.innerHTML?
document.body depends on the DOM to be present and it won't work on a pure JavaScript environment. console.log is enough for this purpose or simply leave the result in a variable for inspection.
@VictorSchröder I don't think it would have caused much confusion, but I've edited nonetheless.
3

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('_');

3 Comments

I see that the split function doesn't help, but using a regex seems to achieve this. It should specify that you are referring to the Split function itself, natively.
Interesting, This solution distills the problem down to a more readable/manageable solution. In my case of converting a full name into first and last (yes our requirements forced this logic) this solution worked best and was more readable then the others. Thanks
This is not true anymore :)
2

if you are looking for a more modern way of doing this:

let raw = "good_luck_buddy"

raw.split("_")
    .filter((part, index) => index !== 0)
    .join("_")

Comments

1

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:

String.prototype.splitOnce = function(regex)
{
    var match = this.match(regex);
    if(match)
    {
        var match_i = this.indexOf(match[0]);
        
        return [this.substring(0, match_i),
        this.substring(match_i + match[0].length)];
    }
    else
    { return [this, ""]; }
}

var str = "something/////another thing///again";

alert(str.splitOnce(/\/+/)[1]);

Comments

1

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.

Comments

1

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().

Comments

1

You could also use non-greedy match, it's just a single, simple line:

a = "good_luck_buddy"
const [,g,b] = a.match(/(.*?)_(.*)/)
console.log(g,"and also",b)

Comments

0

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)

Comments

0

a simple es6 one statement solution to get the first key and remaining parts

let raw = 'good_luck_buddy'

raw.split('_')
   .reduce((p, c, i) => i === 0 ? [c] : [p[0], [...p.slice(1), c].join('_')], [])

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.