nines
nines

Reputation: 521

Split a string only the at the first n occurrences of a delimiter

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

Upvotes: 46

Views: 44227

Answers (19)

davin
davin

Reputation: 45525

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

Upvotes: 50

GuTheR
GuTheR

Reputation: 81

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

Upvotes: 0

Marcelo Barros
Marcelo Barros

Reputation: 1048

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

Upvotes: 1

Przemek
Przemek

Reputation: 3975

ES2015

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

Complexity O(n).

Upvotes: 2

Alpha and Omega
Alpha and Omega

Reputation: 104

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!");

Upvotes: 0

Naveed Ul Islam
Naveed Ul Islam

Reputation: 169

var result = [string.split(' ',1).toString(), string.split(' ').slice(1).join(' ')];

Results in:

["Split", "this, but not this"]

Upvotes: 3

Kerem
Kerem

Reputation: 11576

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

Upvotes: 2

Joshua Nathaniel
Joshua Nathaniel

Reputation: 96

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

Upvotes: 0

GingerPlusPlus
GingerPlusPlus

Reputation: 5606

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

Upvotes: 0

Eugene Gluhotorenko
Eugene Gluhotorenko

Reputation: 3164

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

let [str1, str2, ...str3] = string.split(' ');
str3 = str3.join(' ');

Upvotes: 26

mvndaai
mvndaai

Reputation: 3831

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,' ');

Upvotes: 0

mpen
mpen

Reputation: 282845

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

Upvotes: 0

Thomas Bachem
Thomas Bachem

Reputation: 1575

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

Upvotes: 1

Maertz
Maertz

Reputation: 4952

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

Upvotes: 2

clamchoda
clamchoda

Reputation: 4941

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"

Upvotes: 4

Laas
Laas

Reputation: 6068

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

Upvotes: 25

herostwist
herostwist

Reputation: 3958

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

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1074188

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

Upvotes: 2

Pointy
Pointy

Reputation: 413720

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.

Upvotes: 9

Related Questions