422

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

Ofeargall
  • 5,340
  • 5
  • 28
  • 33

21 Answers21

607

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

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Mark
  • 39,169
  • 11
  • 42
  • 48
  • 13
    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. – Alan Moore Jan 05 '11 at 20:04
  • 1
    As stated by @MarkF «You don't even need `?`» and even more: `?` is not a greedy operator, but AFAIK the opposite (lazy/ungreedy). `+` is the greedy one. – Alessandro Fazzi Jan 17 '14 at 14:28
  • 1
    It's the fact that `.+` is in parentheses that really makes this the solution. From [mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split): *"If separator is a regular expression that contains **capturing parentheses**, then each time separator is matched, the results (including any undefined results) of the capturing parentheses are **spliced into the output array**. However, not all browsers support this capability."* – nmclean Jan 17 '14 at 15:02
  • 34
    Anyone know why I get an extra empty string element with this: in: `"Aspect Ratio: 16:9".split(/:(.+)/)` out: `["Aspect Ratio", " 16:9", ""]` – katy lavallee May 08 '14 at 17:42
  • 4
    @katylavallee - This might help: http://stackoverflow.com/questions/12836062/javascript-string-split-returns-an-array-with-two-elements-instead-of-one Since the separator is `": 16:9"`, there is nothing after the separator, thus creating the empty string at the end. – Derek 朕會功夫 Jun 21 '14 at 06:31
  • 2
    No need for ? and you better use `/_(.*)/` (* instead of +) to support string that ends with _ – oriadam Oct 25 '15 at 10:09
  • @PioneerSkies is right. `?` is **not** greedy operator but totally the opposite - it's an operator for a **non-greedy** match. – falconepl Oct 26 '15 at 19:15
  • I would replace `(.+)` wtih `([^/s/S]*)` in case the separator is something that is not captured by `.` (e.g. `\n`). – Yan Foto Jul 22 '16 at 13:52
  • What would the expression be If we want to split form "/" character instead of "_" character? I tied using "split(/(.+)?/)[1]" but it's not working.. – Lucy Nov 25 '16 at 12:56
  • 1
    @Lucy: split(/\/(.+)/)[1] – Greg Feb 27 '17 at 21:05
  • 2
    @YanFoto you can use `/_([^]+)/` specifically `[^]` to capture all characters including new lines – Salami Jul 07 '17 at 01:45
  • 1
    @katylavallee The reason is that the second list value is the captured part of the separator. The third list value then is the second fragment of the split string after the separator, and therefore empty (since the separator eats the whole string). – mak Aug 02 '17 at 13:41
  • @katylavallee The reason for the extra "" at the end is because the regex acts like a split separator, the string is split by the separator as "Aspect Ratio" and "", and the result puts the separator into the middle. you can pass in limit=2 to remove the empty string if you really don't want it. – Comtaler Jul 27 '18 at 20:06
  • Can this be extended to split at the nth occurrence of a string? – Melab Aug 31 '18 at 15:22
  • How would i use this method to split using a string? e.g "good_luck_buddy".split(/"buddy"(.+)/)[1] – Frederik Petersen May 21 '20 at 18:54
  • 1
    What a shame JavaScript's designers didn't look at [Perl's split function](https://perldoc.perl.org/functions/split) which, if you say you want to split a string in two, splits it once and gives you the two halves, which is always what you want, rather than JS's stupid split it in N parts and just give you the first two, which you could have done with slice anyway. – Denis Howe Oct 02 '20 at 15:12
  • Could this approach be vulnerable to regular expression denial of service (ReDoS)? I assume not, but just asking to be sure. – Marcus Jul 24 '21 at 14:22
  • only in JavaScript is the easiest solution to make an array with an extra dummy element – Krusty the Clown Apr 06 '23 at 20:23
295

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)
danday74
  • 52,471
  • 49
  • 232
  • 283
kennebec
  • 102,654
  • 32
  • 106
  • 127
  • 3
    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 )` – muratgozel Oct 27 '16 at 23:42
  • 15
    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]);` –  Nov 15 '16 at 20:29
  • 3
    @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 – Steffan Nov 01 '17 at 12:51
  • So long as the character you're splitting on isn't the end of the string. – eze Jan 24 '23 at 23:48
76

With help of destructuring assignment it can be more readable:

let [first, ...rest] = "good_luck_buddy".split('_')
rest = rest.join('_')
ont.rif
  • 1,068
  • 9
  • 18
71

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

"good_luck_buddy".split('_').slice(1).join('_')
yonas
  • 815
  • 7
  • 4
  • 1
    This is really inefficient. You split the *whole* string in parts, remove one element, then join *all* them again. It’s ok on small strings, but don’t do that on large ones. – bfontaine Dec 20 '13 at 11:43
  • 1
    @bfontaine - yeah, I agree. For really long strings, I'd have to just suck it up and use RegExp. – yonas Feb 27 '14 at 19:00
  • 46
    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. – Christiaan Westerbeek Aug 20 '14 at 08:28
  • 3
    @yonas Yeah, take the red pill! It will make your life faster, even for short strings: http://jsperf.com/split-by-first-colon – Julian F. Weinert Oct 11 '15 at 08:39
  • 2
    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. – Patrick Graham Feb 28 '16 at 05:36
  • 1
    @PatrickGraham It really depends on the context. Simple regexps are not that hard to understand; on large inputs you care about the computer efficiency. When you run code like this hundreds of times per second and you can significantly speedup it using regexps, you do it. If it’s one-shot on a small input you can go with whatever you want. – bfontaine Feb 29 '16 at 10:12
  • 1
    In my tests this is actually considerably slower than even running a regex. Kennebec's method is the fastest. https://jsperf.com/regex-vs-string-funcs/1 – marcusds Sep 29 '17 at 15:26
  • 64
    Ha! I wrote this comment 4+ years ago. I am definitely on board with RegExp now! :) – yonas Oct 05 '17 at 15:01
  • 8
    @yonas you better don't. RegExp is awesome when **you need it**. Not the case here. Check updated test: https://jsperf.com/split-by-first-colon/2 – metalim Apr 18 '19 at 16:40
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
Phoghelius
  • 476
  • 4
  • 5
25

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

Kraken
  • 5,043
  • 3
  • 25
  • 46
17

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"
Clifford Fajardo
  • 1,357
  • 1
  • 17
  • 28
Darren Lee
  • 387
  • 3
  • 11
16

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];
Chandu
  • 81,493
  • 19
  • 133
  • 134
  • 6
    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']` – Alex Vidal Jan 05 '11 at 18:29
  • Thanks made an assumption on that. Updated the post to use a regular expression. – Chandu Jan 05 '11 at 18:35
  • 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.) – Alan Moore Jan 05 '11 at 19:59
  • Thanks, didn't realize it. Updated the Regex and tried it over couple of scenarion... – Chandu Jan 05 '11 at 20:08
  • 1
    It doesnt work in ie8 and i switch back to indexOf and substring – Igor Alekseev Aug 09 '12 at 11:32
  • 10 years later, this helped me. thanks op – Padua Dec 15 '21 at 15:41
12

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.

sebjwallace
  • 753
  • 1
  • 8
  • 17
  • 6
    This puts an unnecessary constraint on the string. – Yan Foto Jul 22 '16 at 09:46
  • 1
    @YanFoto you mean by using '&'? It could be anything. – sebjwallace Jul 26 '17 at 18:12
  • 3
    @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 Jan 17 '19 at 10:12
  • @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"] – sebjwallace Jan 18 '19 at 14:09
  • Although smart, but like others said cannot have that character in string for this method to work. – Suraj Jain Feb 14 '20 at 14:34
  • Then use `\u0000` which is for various reasons usually bad to have in a string, so you will have the least chance to encounter it in the wild... – CherryDT Apr 05 '20 at 14:44
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);
  • best answer here! – RedGuy11 Sep 01 '21 at 15:14
  • 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(/(?<=^[^\.]+)\./)` – Kip Jun 09 '22 at 13:02
12

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.
Chaim Leib Halbert
  • 2,194
  • 20
  • 23
  • 1
    Come on... It's 2019... Are people out there really still microbenchmarking this kind of thing? – Victor Schröder Feb 07 '19 at 00:02
  • 3
    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") – TamusJRoyce Feb 11 '19 at 14:06
  • 4
    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. – Chaim Leib Halbert Nov 30 '20 at 17:11
  • 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. – HonoredMule Dec 03 '20 at 13:20
  • 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. – nponeccop Jul 02 '21 at 21:03
5

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.

James T
  • 795
  • 9
  • 11
  • The `document.body.innerHTML` part here is completely useless. – Victor Schröder Feb 07 '19 at 00:05
  • @VictorSchröder how do you expect to see the output of the snippet without `document.body.innerHTML`? – James T Feb 08 '19 at 21:52
  • 2
    `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. – Victor Schröder Feb 10 '19 at 13:58
  • @VictorSchröder I don't think it would have caused much confusion, but I've edited nonetheless. – James T Feb 11 '19 at 03:06
4

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('_');
Alex Vidal
  • 4,080
  • 20
  • 23
  • 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. – Dan Hanly Feb 02 '12 at 11:14
  • 1
    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 – Sukima Dec 14 '17 at 19:20
  • This is not true anymore :) – Kraken Feb 13 '20 at 14:13
2

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

Panu Logic
  • 2,193
  • 1
  • 17
  • 21
2

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])
Jan
  • 8,011
  • 3
  • 38
  • 60
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("_")
Moe Singh
  • 809
  • 7
  • 12
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]);
pmrotule
  • 9,065
  • 4
  • 50
  • 58
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.

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)
oriadam
  • 7,747
  • 2
  • 50
  • 48
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('_')], [])
Manan Mehta
  • 5,501
  • 1
  • 18
  • 18
-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)
Mah Neh
  • 84
  • 6