107

How can I split a string only once, i.e. make:

"1|Ceci n'est pas une pipe: | Oui"

parse to:

["1", "Ceci n'est pas une pipe: | Oui"]

The limit in split doesn't seem to help.

1
  • 1
    i don't know how it was in 2010, but today most solutions with .split are much worse than s.indexOf(...); s.slice(); /* OR: s.substring() */. "much worse" === in Chrome and NodeJS they are about 90% slower Commented Apr 14, 2023 at 6:08

17 Answers 17

156

You'd want to use String.indexOf('|') to get the index of the first occurrence of '|'.

var i = s.indexOf('|');
var splits = [s.slice(0,i), s.slice(i+1)];
Sign up to request clarification or add additional context in comments.

5 Comments

Not as "fun" as Nick's, but probably more efficient. Also note the +1 should actually be the length of the separator string, if more than one character.
Why is this solution less "fun"?
For those wondering, to do this with 2 or more chunks, you could use indexOf in a loop or: var i = s.split('|',2).join('|').length;//2nd index var splits = [s.slice(0,i).split('|'), s.slice(i+1)];
This does NOT work like split when IndexOf returns -1 (E.g. if var s='string' it would return ['strin','string']). You would have to write a conditional.
Functioin looks nice, but fails when indexOf returns -1, which it does if the | character is not found in string s.
88

This isn't a pretty approach, but works with decent efficiency:

var string = "1|Ceci n'est pas une pipe: | Oui";
var components = string.split('|');
alert([components.shift(), components.join('|')]​);​​​​​

Here's a quick demo of it

5 Comments

This is actually better than the regex, I think. Selected as correct, thanks!
To clarify, he's splitting into N parts then popping the first one onto a list and joining the rest onto the same list.
If you want to get k tokens off the string instead of 1, do the same as above except use components.splice(0,k) instead of components.shift().
For those wondering how to do this with 2 or more chucks, You could use shift in a loop or: components.splice(0,2).slice(0,2)
Splitting and joining is wasteful. Better to use the indexOf solution.
18

ES6 syntax allows a different approach:

function splitOnce(s, on) {
   [first, ...rest] = s.split(on)
   return [first, rest.length > 0? rest.join(on) : null]
}

Which also handles the eventuality of the string not having a | by returning null rather than an empty string, which is more explicit.

splitOnce("1|Ceci n'est pas une pipe: | Oui", "|")
>>> ["1", "Ceci n'est pas une pipe: | Oui"]

splitOnce("Celui-ci n'a pas de pipe symbol!", "|")
>>> ["Celui-ci n'a pas de pipe symbol!", null]

Pas de pipe? C'est null!

I added this reply primarily so I could make a pun on the pipe symbol, but also to show off es6 syntax - its amazing how many people still don't use it...

1 Comment

to be consistent with split(), it would be better to return the original string instead of null in case no splitting sequence matches.
13

You can use:

var splits = str.match(/([^|]*)\|(.*)/);
splits.shift();

The regex splits the string into two matching groups (parenthesized), the text preceding the first | and the text after. Then, we shift the result to get rid of the whole string match (splits[0]).

2 Comments

properly, that should probably anchor at beginning. however javascript regexen seem to be greedy.
isn't it overkill? string.indexOf('|') with string.substring() can do this at the speed of light
6

one liner and imo, simpler:

var str = 'I | am super | cool | yea!';
str.split('|').slice(1).join('|');

This returns " am super | cool | yea!"

1 Comment

This isn't what was asked for, they are looking for the missing first portion as well.
5

More effective method:

const str = "1|Ceci n'est pas une pipe: | Oui"

const [head] = str.split('|', 1);

const result = [head, str.substr(head.length + 1)]

console.log(result);

Comments

4

If the string doesn't contain the delimiter @NickCraver's solution will still return an array of two elements, the second being an empty string. I prefer the behavior to match that of split. That is, if the input string does not contain the delimiter return just an array with a single element.

var splitOnce = function(str, delim) {
    var components = str.split(delim);
    var result = [components.shift()];
    if(components.length) {
        result.push(components.join(delim));
    }
    return result;
};

splitOnce("a b c d", " "); // ["a", "b c d"]
splitOnce("a", " "); // ["a"]

Comments

4

You could split the string into an array, then concatenate all but the first element into a string.

const text = 'a=b=c=d'
const [key, ...values] = text.split('=')
const value = values.join('=')
console.log(key, value)

Comments

3

Try this:

function splitOnce(input, splitBy) {
    var fullSplit = input.split(splitBy);
    var retVal = [];
    retVal.push( fullSplit.shift() );
    retVal.push( fullSplit.join( splitBy ) );
    return retVal;
}

var whatever = splitOnce("1|Ceci n'est pas une pipe: | Oui", '|');

Comments

3

You can also split on a regex that matches everything

const str = "one | two | three | four"
const [first, second] = str.split(/\|(.*)/s) // [ 'one ', ' two | three | four', '' ]

I don't know why there's an empty string, you can just ignore it. Just don't forget to add s (see documentation) to the end of the regular expression, otherwise it will split until the first newline.

Comments

2

An alternate, short approach, besides the goods ones elsewhere, is to use replace()'s limit to your advantage.

var str = "1|Ceci n'est pas une pipe: | Oui";
str.replace("|", "aUniquePhraseToSaySplitMe").split("aUniquePhraseToSaySplitMe");

As @sreservoir points out in the comments, the unique phrase must be truly unique--it cannot be in the source you're running this split over, or you'll get the string split into more pieces than you want. An unprintable character, as he says, may do if you're running this against user input (i.e., typed in a browser).

3 Comments

that only works if the 'unique' phrase is uninputable. if reading from a text file, this is impossible. if reading from browser, any unprintable control character if probably fine. tab works wonders, too. in any case, "aUniquePhraseToSaySplitMe" is almost definitely possibly part of the input, and is thus dangerous.
You are correct, of course. My example is just an example with an eye toward explaining what is being done concisely. I'll incorporate your point into the answer itself. Thanks!
If you use str for your unique phrase, you know it can't be in there again! :evil grin
1

Just as evil as most of the answers so far:

var splits = str.split('|');
splits.splice(1, splits.length - 1, splits.slice(1).join('|'));

Comments

1
var str = "1|Ceci n'est pas une pipe: | Oui";
str.split(/(?<!\|.*)\|/s);

With this regex, it will find "|" before which there is no "|", so only the first "|".

Comments

0

This one's a little longer, but it works like I believe limit should:

function split_limit(inString, separator, limit){
    var ary = inString.split(separator);
    var aryOut = ary.slice(0, limit - 1);
    if(ary[limit - 1]){
        aryOut.push(ary.slice(limit - 1).join(separator));
    }
    return aryOut;
}
console.log(split_limit("1|Ceci n'est pas une pipe: | Oui","|", 1));
console.log(split_limit("1|Ceci n'est pas une pipe: | Oui","|", 2));
console.log(split_limit("1|Ceci n'est pas une pipe: | Oui","|", 3));
console.log(split_limit("1|Ceci n'est pas une pipe: | Oui","|", 7));

https://jsfiddle.net/2gyxuo2j/

limit of Zero returns funny results, but in the name of efficiency, I left out the check for it. You can add this as the first line of the function if you need it:

if(limit < 1) return [];

Comments

0

if you wanna use a "pipeline", reduce is your friend

const separator = '|'
jsonNode.split(separator)
   .reduce((previous, current, index) =>
    {
        if (index < 2) previous.push(current)
        else previous[1] += `${separator}${current}`
        return previous
    }, [])
    .map((item: string) => (item.trim()))
    .filter((item: string) => (item != ''))

Comments

0

This is an old question, but if u need to loop through strings, and have multiple separators, use regexp for your match cases, like this:

let exampleRegexp = />|<|=|\||(?:and|not|etc)/

let strings = ["left | middle | right", "yes and not yes"]

function splitOnce(str, regexp){
    let check = regexp.exec(str)
    let tail = str.slice(check.index + check.toString().length)
    let head = str.substring(0, check.index)
    return [head, tail]
}

for(let str of strings){
    let [head, tail] = splitOnce(str, exampleRegexp)
    console.log(head + ":::" + tail)
}

Comments

-1

use the javascript regular expression functionality and take the first captured expression.

the RE would probably look like /^([^|]*)\|/.

actually, you only need /[^|]*/ if you validated that the string is formatted in such a way, due to javascript regex greediness.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.