44

I'd like to split a string only the at the first n occurrences of a delimiter. I know, I could add them together using a loop, but isn't there a more straight forward approach?

var string = 'Split this, but not this';    
var result = new Array('Split', 'this,', 'but not this');
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
nines
  • 501
  • 1
  • 6
  • 7
  • see my latest edit, I think that its exactly what your looking for. – clamchoda Apr 07 '11 at 14:48
  • Possible duplicate of [split string only on first instance of specified character](https://stackoverflow.com/questions/4607745/split-string-only-on-first-instance-of-specified-character) –  Oct 12 '17 at 10:01

19 Answers19

45

As per MDN:

string.split(separator, limit);

Update:

(Because using split with limit will not include the remaining part of the given string.)

var string = 'Split this, but not this',
    arr = string.split(' '),
    result = arr.slice(0,2);

result.push(arr.slice(2).join(' ')); // ["Split", "this,", "but not this"]

Update version 2 (one slice shorter):

var string = 'Split this, but not this',
    arr = string.split(' '),
    result = arr.splice(0,2);

result.push(arr.join(' ')); // result is ["Split", "this,", "but not this"]
jox
  • 2,218
  • 22
  • 32
davin
  • 44,863
  • 9
  • 78
  • 78
  • 6
    This will remove the third part. – nines Apr 07 '11 at 14:01
  • 1
    Hi davin. Your answer helped me. What do you think about: `var string = 'Split this, but not this', result = string.split(' '), result.push(result.splice(2).join(' '));`? – Jayen Feb 22 '16 at 01:36
  • 2
    While concise, this does not handle the case where the string only has one space - the result array will contain an extra empty string. – riv Jan 01 '18 at 00:15
  • @riv Good point. For anyone else, this can simply be fixed by only pushing the last part if `arr.length > 0`. – tobloef Aug 19 '19 at 00:39
24

Using Array.slice:

function splitWithTail(str,delim,count){
  var parts = str.split(delim);
  var tail = parts.slice(count).join(delim);
  var result = parts.slice(0,count);
  result.push(tail);
  return result;
}

Results:

splitWithTail(string," ",2)
// => ["Split", "this,", "but not this"]
Laas
  • 5,978
  • 33
  • 52
20

Combination of split and join with ES6 features does this pretty neat:

let [str1, str2, ...str3] = string.split(' ');
str3 = str3.join(' ');
Eugene Gluhotorenko
  • 3,094
  • 2
  • 33
  • 52
  • This is nice! But be aware about compatibility in IE and Safari: **[MDN Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Browser_compatibility)** see "Rest in arrays" – icl7126 May 07 '18 at 08:32
9

The JavaScript ".split()" function already accepts a second parameter giving the maximum number of splits to perform. However, it doesn't retain the tail end of your original string; you'd have to glue it back on.

Another approach would be to iteratively shave off a leading portion of the string with a regex, stopping when you've gotten your limit.

var str = "hello out there cruel world";
var parts = [];
while (parts.length < 3) { // "3" is just an example
  str = str.replace(/^(\w+)\s*(.*)$/, function(_, word, remainder) {
    parts.push(word);
    return remainder;
  });
}
parts.push(str);

edit — and it just occurs to me that another simple way would be to just use plain ".split()", pluck off the first few parts, and then just ".slice()" and ".join()" the rest.

Pointy
  • 405,095
  • 59
  • 585
  • 614
4

For this you could use Split(delimiter) and choose a delimiter.

var testSplit = "Split this, but not this";
var testParts= testSplit.Split(",");

var firstPart = testParts[1];

// firstPart = "Split this"

Not 100% on my syntax I havent used javascript in quite some time. But I know this is how its done...

EDIT** Sorry, my mistake. Now I believe I know what your asking and I think the easiest way to do this is using substr. Very easy, no loops required. Just made an example, works perfect

// so first, we want to get everything from 0 - the first occurence of the comma.
// next, we want to get everything after the first occurence of the comma.  (if you only define one parameter, substr will take everything after that parameter.

var testString = "Split this, but this part, and this part are one string";
var part1 = testString.substr(0,testString.indexOf(',')); 
var part2 = testString.substr(testString.indexOf(','));

//part1 = "Split this"
//part2= "but this part, and this part are one string"
clamchoda
  • 4,411
  • 2
  • 36
  • 74
  • In this example it would work, but actually there could be more occurrences of commas, etc. afterwards. – nines Apr 07 '11 at 14:06
  • @nines But isn't this what you are trying to do? "only the at the first n occurrences of a delimiter" – clamchoda Apr 07 '11 at 14:30
  • My example has been stupid, sorry. Actually I a have some kind of simple protocol:`command + delimiter + options + delimiter + data`. The first two parts are fixed, so there won't be a character, but the data part is of variable length and could contain anything. I just thought there would be a short standard way of doing this, since javascript split seems to be different from some other languages cutting off the rest instead of putting it into an other element. – nines Apr 07 '11 at 15:20
  • @nines can you edit your question, and post an example with a realstic string and expected output. – clamchoda Apr 07 '11 at 15:32
3
var result = [string.split(' ',1).toString(), string.split(' ').slice(1).join(' ')];

Results in:

["Split", "this, but not this"]
Community
  • 1
  • 1
Naveed Ul Islam
  • 159
  • 1
  • 3
2

Yet another implementation with limit;

// takes string input only
function split(input, separator, limit) {
    input = input.split(separator);
    if (limit) {
        input = input.slice(0, limit - 1).concat(input.slice(limit - 1).join(separator));
    }
    return input;
}
Kerem
  • 11,377
  • 5
  • 59
  • 58
2

Although you can give split a limit, you won't get back what you've said you want. Unfortunately, you will have to roll your own on this, e.g.:

var string = 'Split this, but not this';
var result = string.split(' ');

if (result.length > 3) {
    result[2] = result.slice(2).join(' ');
    result.length = 3;
}

But even then, you end up modifying the number of spaces in the latter parts of it. So I'd probably just do it the old-fashioned write-your-own-loop way:

function splitWithLimit(str, delim, limit) {
  var index,
      lastIndex = 0,
      rv = [];

  while (--limit && (index = str.indexOf(delim, lastIndex)) >= 0) {
    rv.push(str.substring(lastIndex, index));
    lastIndex = index + delim.length;
  }
  if (lastIndex < str.length) {
    rv.push(str.substring(lastIndex));
  }
  return rv;
}

Live copy

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
2

Hi there i had the same problem wanted to split only several times, couldnt find anything so i just extended the DOM - just a quick and dirty solution but it works :)

String.prototype.split = function(seperator,limit) {
    var value = "";
    var hops  = [];

    // Validate limit
    limit = typeof(limit)==='number'?limit:0;

    // Join back given value
    for ( var i = 0; i < this.length; i++ ) { value += this[i]; }

    // Walkthrough given hops
    for ( var i = 0; i < limit; i++ ) {
        var pos = value.indexOf(seperator);
        if ( pos != -1 ) {
            hops.push(value.slice(0,pos));
            value = value.slice(pos + seperator.length,value.length)

        // Done here break dat
        } else {
            break;
        }
    }
    // Add non processed rest and return
    hops.push(value)
    return hops;
}

In your case would look like that

>>> "Split this, but not this".split(' ',2)
["Split", "this,", "but not this"]
Maertz
  • 4,952
  • 2
  • 18
  • 27
1

Improved version of a sane limit implementation with proper RegEx support:

function splitWithTail(value, separator, limit) {
    var pattern, startIndex, m, parts = [];

    if(!limit) {
        return value.split(separator);
    }

    if(separator instanceof RegExp) {
        pattern = new RegExp(separator.source, 'g' + (separator.ignoreCase ? 'i' : '') + (separator.multiline ? 'm' : ''));
    } else {
        pattern = new RegExp(separator.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1'), 'g');
    }

    do {
        startIndex = pattern.lastIndex;
        if(m = pattern.exec(value)) {
            parts.push(value.substr(startIndex, m.index - startIndex));
        }
    } while(m && parts.length < limit - 1);
    parts.push(value.substr(pattern.lastIndex));

    return parts;
}

Usage example:

splitWithTail("foo, bar, baz", /,\s+/, 2); // -> ["foo", "bar, baz"]

Built for & tested in Chrome, Firefox, Safari, IE8+.

Thomas Bachem
  • 1,545
  • 1
  • 16
  • 10
1

ES2015

const splitAndAppend = (str, delim, count) => {
    const arr = str.split(delim);
    return [...arr.splice(0, count), arr.join(delim)];
}

Complexity O(n).

Przemek
  • 3,855
  • 2
  • 25
  • 33
  • use `count - 1` instead of `count` to have a split [Java](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#split(java.lang.String,int)) has. – tom May 12 '20 at 15:00
1

For the specific case of splitting only the first occurrence, the code below can also be useful. In my tests it performed a lot better (about 40% in Chrome and 200% in Firefox) than the most voted answer.

https://jsbench.me/nckrqnwcry/1

function split(term = '', sep = ',') {
    const split = term.split(sep, 1);
    return [split[0], term.substr(split[0].length + sep.length)];
}
Marcelo Barros
  • 930
  • 9
  • 16
0

Yet another implementation I just wrote:

export function split(subject, separator, limit=undefined, pad=undefined) {
    if(!limit) {
        return subject.split(separator);
    }
    if(limit < 0) {
        throw new Error(`limit must be non-negative`);
    }
    let result = [];
    let fromIndex = 0;
    for(let i=1; i<limit; ++i) {
        let sepIdx = subject.indexOf(separator, fromIndex);
        if(sepIdx < 0) {
            break;
        }
        let substr = subject.slice(fromIndex, sepIdx);
        result.push(substr);
        fromIndex = sepIdx + separator.length;
    }
    result.push(subject.slice(fromIndex));
    while(result.length < limit) {
        result.push(pad);
    }
    return result;
}

Doesn't use regexes, nor does it over-split and re-join.

This version guarantees exactly limit elements (will pad with undefineds if there aren't enough separators); this makes it safe to do this kind of ES6 stuff:

let [a,b,c] = split('a$b','$',3,null);
// a = 'a', b = 'b', c = null
mpen
  • 272,448
  • 266
  • 850
  • 1,236
0

I like using shift.

function splitFirstN(str,n,delim){
    var parts = str.split(delim);
    var r = [];
    for(var i = 0; i < n; i++){
        r.push(parts.shift());
    }
    r.push(parts.join(delim));
    return r;
}

var str = 'Split this, but not this';    
var result = splitFirstN(str,2,' ');
mvndaai
  • 3,453
  • 3
  • 30
  • 34
0

Nothing a one simple regex can't do:

const string = 'Split this, but not this';
console.log(string.match(/^(\S+)\s*(\S+)?\s*([\s\S]+)?$/).slice(1));
GingerPlusPlus
  • 5,336
  • 1
  • 29
  • 52
0

In my case I was trying to parse git grep stdout. So I had a {filename}:{linenumber}:{context}. I don't like splitting and then joining. We should be able to parse the string one time. You could simply step through each letter and split on the first two colons. A quicker way to do it out of the box is by using the match method and regex.

Hence,

txt.match(/(.+):(\d+):(.*)/)

Works great

0

My version, universal, supports RegExp and non-RegExp delimiters. Highly optimized. Tests provided. Why: since other RegExp versions are full of bugs and this is not a trivial function.

Usage:

"a b  c   d".split_with_tail(/ +/,3) = ['a','b','c   d']
"a b  c   d".split_with_tail(' ',3) = ['a','b',' c   d']

Code

String.prototype.split_with_tail = function(delimiter,limit)
{
    if( typeof(limit) !== 'number' || limit < 1 ) return this.split(delimiter,limit);

    var parts = this.split(delimiter,limit+1);
    if( parts.length <= limit ) return parts;
    parts.splice(-2,2);

    limit = Math.floor(limit) - 1; // used later as index, speed optimization; limit can be float ..
    if( delimiter instanceof RegExp ) {
        // adds 'g' flag to any regexp:
        delimiter += '';
        var len = delimiter.lastIndexOf('/');
        delimiter = new RegExp(delimiter.slice(1, len), delimiter.slice(len + 1)+'g');

        len = 0;
        while(limit--) len += parts[limit].length + (delimiter.exec(this))[0].length;
    }
    else {
        var len = limit * (''+delimiter).length;
        while(limit--) len += parts[limit].length;
    }

    parts.push(this.substring(len)); // adds tail, finally
    return parts;
}

Tests

function test(str,delimiter,limit,result) {
    if( JSON.stringify(result) !== JSON.stringify(str.split_with_tail(delimiter,limit)) ) {
        console.log(arguments);
        console.log(str.split_with_tail(delimiter,limit));
        throw "lol";
    }
}
test('',/ +/,undefined,['']);
test('',/ +/,3,['']);
test('a',/ +/,0.1,[]);
test('a',/ +/,1,['a']);
test('a a',/ +/,1,['a a']);
test('a a',/ +/,2.1,['a','a']);
test('a a a',/ +/,2.9,['a','a a']);
test('aaaaa aa a',/ +/,1,['aaaaa aa a']);
test('aaaaa aa a',/ +/,2,['aaaaa', 'aa a']);
test('a a',/ +/,2,['a','a']);
test('a',/ +/,3,['a']);
test('a a',/ +/,3,['a','a']);
test('a a  a',/ +/,3,['a','a','a']);
test('a a  a  a',/ +/,3,['a','a','a  a']);
test('a a  a  a',/ +/,4,['a','a','a','a']);
test('a aa  aaa  ',/ +/,4,['a','aa','aaa','']);
test('a a  a  a',/ +/,2,['a','a  a  a']);
test('a a  a  a',/ +/,1,['a a  a  a']);
test('a a  a  a',/ +/,0,[]);
test('a a  a  a',/ +/,undefined,['a','a','a','a']);
test('a a  a  a',/ +/,-1,['a','a','a','a']);

test('a',' ',3,['a']);
test('aaaaa aa a',' ',2,['aaaaa', 'aa a']);
test('aaaaa  aa  a','  ',2,['aaaaa','aa  a']);
test('a a a',' ',3,['a','a','a']);
test('a a a a',' ',3,['a','a','a a']);
test('a a  a a',' ',3,['a','a',' a a']);
test('a a  a a',' ',2,['a','a  a a']);
test('a a  a a',' ',1,['a a  a a']);
test('a a  a a',' ',0,[]);
test('a a  a a',' ',undefined,['a','a','','a','a']);
test('a a  a a',' ',-1,['a','a','','a','a']);
test('1232425',2,3,['1','3','425']);
console.log("good!");
0
var s='Split this, but not this', a=s.split(','), b=a[0].split(' ');
b.push(a[1]);
alert(b);

alerts ['Split', 'this', 'but not this']

herostwist
  • 3,778
  • 1
  • 26
  • 34
0

This typescript version avoid to create a extra part in the end of array when the number of parts is greater than number of spplited parts.

const splitWithTail = (str: string, delim: string, count: number) => {
    const parts = str.split(delim);
    count = count < parts.length ? count : parts.length - 1;
    const tail = parts.slice(count).join(delim);
    const result = parts.slice(0, count);
    result.push(tail);
    return result;
}

splitWithTail("abc|def|ghi", "|", 9999);
// ["abc", "def", "ghi"]
GuTheR
  • 61
  • 1
  • 3