Multisplit: Difference between revisions

From Rosetta Code
Content added Content deleted
m (Fix Perl 6 -> Raku in comments)
imported>Spikeysnack
(Added C++23 version)
(28 intermediate revisions by 18 users not shown)
Line 24: Line 24:


'''Extra Credit:''' provide information that indicates which separator was matched at each separation point and where in the input string that separator was matched.
'''Extra Credit:''' provide information that indicates which separator was matched at each separation point and where in the input string that separator was matched.

=={{header|11l}}==
{{trans|Python}}

<syntaxhighlight lang="11l">F multisplit(text, sep)
V lastmatch = 0
V i = 0
V matches = ‘’
L i < text.len
L(s) sep
V j = L.index
I text[i..].starts_with(s)
I i > lastmatch
matches ‘’= text[lastmatch .< i]
matches ‘’= ‘{’s‘}’
lastmatch = i + s.len
i += s.len
L.break
L.was_no_break
i++
I i > lastmatch
matches ‘’= text[lastmatch .< i]
R matches

print(multisplit(‘a!===b=!=c’, [‘==’, ‘!=’, ‘=’]))</syntaxhighlight>

{{out}}
<pre>
a{!=}{==}b{=}{!=}c
</pre>


=={{header|Ada}}==
=={{header|Ada}}==
multisplit.adb:
multisplit.adb:
<lang Ada>with Ada.Containers.Indefinite_Doubly_Linked_Lists;
<syntaxhighlight lang="ada">with Ada.Containers.Indefinite_Doubly_Linked_Lists;
with Ada.Text_IO;
with Ada.Text_IO;


Line 107: Line 137:
Pos := String_Lists.Next (Pos);
Pos := String_Lists.Next (Pos);
end loop;
end loop;
end Multisplit;</lang>
end Multisplit;</syntaxhighlight>


{{out}}
{{out}}
Line 114: Line 144:


=={{header|ALGOL 68}}==
=={{header|ALGOL 68}}==
<lang algol68># split a string based on a number of separators #
<syntaxhighlight lang="algol68"># split a string based on a number of separators #


# MODE to hold the split results #
# MODE to hold the split results #
Line 179: Line 209:
SPLITINFO token = test tokens[ t ];
SPLITINFO token = test tokens[ t ];
print( ( "token: [", text OF token, "] at: ", whole( position OF token, 0 ), " delimiter: (", delimiter OF token, ")", newline ) )
print( ( "token: [", text OF token, "] at: ", whole( position OF token, 0 ), " delimiter: (", delimiter OF token, ")", newline ) )
OD</lang>
OD</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 188: Line 218:
token: [c] at: 10 delimiter: ()
token: [c] at: 10 delimiter: ()
</pre>
</pre>

=={{header|Arturo}}==

<syntaxhighlight lang="rebol">print split.by:["==" "!=" "="] "a!===b=!=c"</syntaxhighlight>

{{out}}

<pre>a b c</pre>


=={{header|AutoHotkey}}==
=={{header|AutoHotkey}}==
<lang AutoHotkey>Str := "a!===b=!=c"
<syntaxhighlight lang="autohotkey">Str := "a!===b=!=c"
Sep := ["==","!=", "="]
Sep := ["==","!=", "="]
Res := StrSplit(Str, Sep)
Res := StrSplit(Str, Sep)
Line 198: Line 236:
for k, v in Sep
for k, v in Sep
N .= (N?"|":"") "\Q" v "\E"
N .= (N?"|":"") "\Q" v "\E"
MsgBox % RegExReplace(str, "(.*?)(" N ")", "$1 {$2}")</lang>
MsgBox % RegExReplace(str, "(.*?)(" N ")", "$1 {$2}")</syntaxhighlight>
{{out}}
{{out}}
<pre>a,,b,,c
<pre>a,,b,,c
Line 204: Line 242:


=={{header|AWK}}==
=={{header|AWK}}==
<syntaxhighlight lang="awk">
<lang AWK>
# syntax: GAWK -f MULTISPLIT.AWK
# syntax: GAWK -f MULTISPLIT.AWK
BEGIN {
BEGIN {
Line 228: Line 266:
exit(0)
exit(0)
}
}
</syntaxhighlight>
</lang>
{{out}}
{{out}}
<pre>
<pre>
Line 242: Line 280:


=={{header|BBC BASIC}}==
=={{header|BBC BASIC}}==
<lang bbcbasic> DIM sep$(2)
<syntaxhighlight lang="bbcbasic"> DIM sep$(2)
sep$() = "==", "!=", "="
sep$() = "==", "!=", "="
PRINT "String splits into:"
PRINT "String splits into:"
Line 265: Line 303:
ENDIF
ENDIF
UNTIL m% = LEN(s$)
UNTIL m% = LEN(s$)
= o$ + """" + MID$(s$, p%) + """"</lang>
= o$ + """" + MID$(s$, p%) + """"</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 276: Line 314:
=={{header|Bracmat}}==
=={{header|Bracmat}}==
This is a surprisingly difficult task to solve in Bracmat, because in a naive solution using a alternating pattern ("=="|"!="|"=") the shorter pattern <code>"="</code> would have precedence over <code>"=="</code>. In the solution below the function <code>oneOf</code> iterates (by recursion) over the operators, trying to match the start of the current subject string <code>sjt</code> with one operator at a time, until success or reaching the end of the list with operators, whichever comes first. If no operator is found at the start of the current subject string, the variable <code>nonOp</code> is extended with one byte, thereby shifting the start of the current subject string one byte to the right. Then a new attempt is made to find an operator. This is repeated until either an operator is found, in which case the unparsed string is restricted to the part of the input after the found operator, or no operator is found, in which case the <code>whl</code> loop terminates.
This is a surprisingly difficult task to solve in Bracmat, because in a naive solution using a alternating pattern ("=="|"!="|"=") the shorter pattern <code>"="</code> would have precedence over <code>"=="</code>. In the solution below the function <code>oneOf</code> iterates (by recursion) over the operators, trying to match the start of the current subject string <code>sjt</code> with one operator at a time, until success or reaching the end of the list with operators, whichever comes first. If no operator is found at the start of the current subject string, the variable <code>nonOp</code> is extended with one byte, thereby shifting the start of the current subject string one byte to the right. Then a new attempt is made to find an operator. This is repeated until either an operator is found, in which case the unparsed string is restricted to the part of the input after the found operator, or no operator is found, in which case the <code>whl</code> loop terminates.
<lang bracmat>( ( oneOf
<syntaxhighlight lang="bracmat">( ( oneOf
= operator
= operator
. !arg:%?operator ?arg
. !arg:%?operator ?arg
Line 293: Line 331:
& put$!unparsed
& put$!unparsed
& put$\n
& put$\n
);</lang>
);</syntaxhighlight>
{{out}}
{{out}}
<pre>a {!=} {==} b {=} {!=} c</pre>
<pre>a {!=} {==} b {=} {!=} c</pre>
Line 299: Line 337:
=={{header|C}}==
=={{header|C}}==
What kind of silly parsing is this?
What kind of silly parsing is this?
<lang C>#include <stdio.h>
<syntaxhighlight lang="c">#include <stdio.h>
#include <string.h>
#include <string.h>


Line 322: Line 360:


return 0;
return 0;
}</lang>
}</syntaxhighlight>
{{out}}<lang>a{!=}{==}b{=}{!=}c</lang>
{{out}}<syntaxhighlight lang="text">a{!=}{==}b{=}{!=}c</syntaxhighlight>


=={{header|C sharp}}==
=={{header|C sharp}}==
Line 329: Line 367:
'''Extra Credit Solution'''
'''Extra Credit Solution'''


<lang csharp>using System;
<syntaxhighlight lang="csharp">using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
Line 385: Line 423:
}
}
}
}
}</lang>
}</syntaxhighlight>


{{out}}
{{out}}
Line 393: Line 431:
=={{header|C++}}==
=={{header|C++}}==
using the Boost library tokenizer!
using the Boost library tokenizer!
<lang cpp>#include <iostream>
<syntaxhighlight lang="cpp">#include <iostream>
#include <boost/tokenizer.hpp>
#include <boost/tokenizer.hpp>
#include <string>
#include <string>
Line 411: Line 449:
std::cout << '\n' ;
std::cout << '\n' ;
return 0 ;
return 0 ;
}</lang>
}</syntaxhighlight>
{{out}}
{{out}}
<PRE>a b c</PRE>
<PRE>a b c</PRE>

===Without external libraries===
<syntaxhighlight lang="c++">
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

struct Split_data {
std::string segment;
int32_t index;
std::string separator;
};

std::vector<Split_data> multi_split(const std::string& text, const std::vector<std::string>& separators) {
std::vector<Split_data> result;
uint64_t i = 0;
std::string segment = "";
while ( i < text.length() ) {
bool found = false;
for ( std::string separator : separators ) {
if ( text.substr(i, separator.length()) == separator ) {
found = true;
result.emplace_back(segment, i, separator);
i += separator.length();
segment = "";
break;
}
}

if ( ! found ) {
segment += text[i];
i += 1;
}
}
result.emplace_back(segment, i, "");
return result;
}

int main() {
for ( Split_data splits : multi_split("a!===b=!=c", { "==", "!=", "=" } ) ) {
std::cout << std::left << std::setw(3) << "\"" + splits.segment + "\""
<< std::setw(18) << " ( split with \"" + splits.separator + "\""
<< " at index " << splits.index << " )" << std::endl;
}
}
</syntaxhighlight>
{{ out }}
<pre>
"a" ( split with "!=" at index 1 )
"" ( split with "==" at index 3 )
"b" ( split with "=" at index 6 )
"" ( split with "!=" at index 7 )
"c" ( split with "" at index 10 )
</pre>


===C++23===
<syntaxhighlight lang="c++">
/* multisplit.cpp */
#include <features.h>
#include <iostream>
#include <string>
#include <vector>
#include <format>

/* C++23 example for Multisplit 6 Jan 2024
email:
spikeysnack@gmail.com

compile:
g++-13 -std=c++23 -Wall -o multisplit multisplit.cpp
*/

// extra info
#define _EXTRA

// aliases
using std::string;
using std::vector;
using str_vec = vector<string>;
using std::cout;


// constants
constexpr static const size_t npos = -1;

// function signatures
string replace_all(string& str, string& remove, string& insert );

str_vec split_on_delim(string& str, const string& delims);

str_vec Multisplit( string& input, const str_vec& seps);

// functions

// replace all substrings in string
// a = "dogs and cats and dogs and cats and birds"
// replace(a, "cats" , "fish");
// ==> "dogs and fish and dogs and fish and birds"

string replace_all(string& str,
const string& remove,
const string& insert ){
string s{str};
string::size_type pos = 0;

#ifdef _EXTRA
const string rightarrow{"\u2B62"}; //unicode arrow
auto ex = std::format("match: {}\t{} ", remove, rightarrow);
std::cerr << ex;
#endif
while ((pos = s.find(remove, pos)) != npos){
s.replace(pos, remove.size(), insert);
pos++;
}

return s;
}


// create a string vector from a string,
// split on a delimiter string
// x = "ab:cde:fgh:ijk"
// split_on_delim( x, ":");
// ==> { "ab", "cde", "fgh", "ijk" }

str_vec split_on_delim(string& str, const string& delims) {
string::size_type beg, pos = 0;
str_vec sv;
string tmp;
while ( (beg = str.find_first_not_of(delims, pos)) != npos ){

pos = str.find_first_of(delims, beg + 1);

tmp = { str.substr(beg, pos - beg) };

sv.push_back(tmp);
}
return sv;
}


str_vec Multisplit( string& input, const str_vec& seps) {

string s1{input};
str_vec sv;

for( auto sep : seps){
s1 = replace_all(s1, sep, "^"); // space sep

#ifdef _EXTRA
std::cerr << s1 << "\n";
#endif
sv = split_on_delim(s1, "^"); // split
}
return sv;
}


/* main program */

int main(){
string sample{"a!===b=!=c"};

const str_vec seps {"!=", "==", "="};

auto s = std::format("sample: \t{}\n", sample);

cout << s;

auto sv = Multisplit(sample, seps);

for( auto s : sv){
auto out = std::format( "{}\t" , s);
cout << out;
}
cout << "\n";
return 0;
}

// end
</syntaxhighlight>

{{ out }}
<pre>
sample: a!===b=!=c
match: != ⭢ a^==b=^c
match: == ⭢ a^^b=^c
match: = ⭢ a^^b^^c
a b c

</pre>


=={{header|CoffeeScript}}==
=={{header|CoffeeScript}}==
<lang coffeescript>
<syntaxhighlight lang="coffeescript">
multi_split = (text, separators) ->
multi_split = (text, separators) ->
# Split text up, using separators to break up text and discarding
# Split text up, using separators to break up text and discarding
Line 447: Line 683:
console.log multi_split 'a!===b=!=c', ['==', '!=', '='] # [ 'a', '', 'b', '', 'c' ]
console.log multi_split 'a!===b=!=c', ['==', '!=', '='] # [ 'a', '', 'b', '', 'c' ]
console.log multi_split '', ['whatever'] # [ '' ]
console.log multi_split '', ['whatever'] # [ '' ]
</syntaxhighlight>
</lang>


=={{header|D}}==
=={{header|D}}==
<lang d>import std.stdio, std.array, std.algorithm;
<syntaxhighlight lang="d">import std.stdio, std.array, std.algorithm;


string[] multiSplit(in string s, in string[] divisors) pure nothrow {
string[] multiSplit(in string s, in string[] divisors) pure nothrow {
Line 487: Line 723:
.join(" {} ")
.join(" {} ")
.writeln;
.writeln;
}</lang>
}</syntaxhighlight>
{{out}} (separator locations indicated by braces):
{{out}} (separator locations indicated by braces):
<pre>a {} {} b {} {} c</pre>
<pre>a {} {} b {} {} c</pre>
=={{header|Delphi}}==
{{libheader| System.SysUtils}}
<syntaxhighlight lang="delphi">
program Multisplit;


{$APPTYPE CONSOLE}

uses
System.SysUtils;

begin
write('[');
for var s in 'a!===b=!=c'.Split(['==', '!=', '=']) do
write(s.QuotedString('"'), ' ');
write(']');
readln;
end.</syntaxhighlight>
{{out}}
<pre>["a" "" "b" "" "c" ]</pre>
=={{header|Elixir}}==
=={{header|Elixir}}==
{{trans|Erlang}}
{{trans|Erlang}}
<lang elixir>iex(1)> Regex.split(~r/==|!=|=/, "a!====b=!=c")
<syntaxhighlight lang="elixir">iex(1)> Regex.split(~r/==|!=|=/, "a!====b=!=c")
["a", "", "", "b", "", "c"]</lang>
["a", "", "", "b", "", "c"]</syntaxhighlight>


=={{header|Erlang}}==
=={{header|Erlang}}==
Line 501: Line 755:
["a",[],"b",[],"c"]
["a",[],"b",[],"c"]
</pre>
</pre>



=={{header|F_Sharp|F#}}==
=={{header|F_Sharp|F#}}==


If we ignore the "Extra Credit" requirements and skip 'ordered separators' condition (i.e. solving absolute different task), this is exactly what one of the overloads of .NET's <code>String.Split</code> method does. Using F# Interactive:
If we ignore the "Extra Credit" requirements and skip 'ordered separators' condition (i.e. solving absolute different task), this is exactly what one of the overloads of .NET's <code>String.Split</code> method does. Using F# Interactive:
<lang fsharp>> "a!===b=!=c".Split([|"=="; "!="; "="|], System.StringSplitOptions.None);;
<syntaxhighlight lang="fsharp">> "a!===b=!=c".Split([|"=="; "!="; "="|], System.StringSplitOptions.None);;
val it : string [] = [|"a"; ""; "b"; ""; "c"|]
val it : string [] = [|"a"; ""; "b"; ""; "c"|]


> "a!===b=!=c".Split([|"="; "!="; "=="|], System.StringSplitOptions.None);;
> "a!===b=!=c".Split([|"="; "!="; "=="|], System.StringSplitOptions.None);;
val it : string [] = [|"a"; ""; ""; "b"; ""; "c"|]</lang>
val it : string [] = [|"a"; ""; ""; "b"; ""; "c"|]</syntaxhighlight>


<code>System.StringSplitOptions.None</code> specifies that empty strings should be included in the result.
<code>System.StringSplitOptions.None</code> specifies that empty strings should be included in the result.

=={{header|Factor}}==
<syntaxhighlight lang="factor">USING: arrays fry kernel make sequences ;

IN: rosetta-code.multisplit

: first-subseq ( seq separators -- n separator )
tuck
[ [ subseq-index ] dip 2array ] withd map-index sift-keys
[ drop f f ] [ [ first ] infimum-by first2 rot nth ] if-empty ;

: multisplit ( string separators -- seq )
'[
[ dup _ first-subseq dup ] [
length -rot cut-slice [ , ] dip swap tail-slice
] while 2drop ,
] { } make ;</syntaxhighlight>

{{out}}
<pre>> "a!===b=!=c" { "==" "!=" "=" } multisplit [ >string ] map .

{ "a" "" "b" "" "c" }</pre>


=={{header|FreeBASIC}}==
=={{header|FreeBASIC}}==
FreeBASIC does not have a built in 'split' function so we need to write one:
FreeBASIC does not have a built in 'split' function so we need to write one:
<lang freebasic>' FB 1.05.0 Win64
<syntaxhighlight lang="freebasic">' FB 1.05.0 Win64


Sub Split(s As String, sepList() As String, result() As String, removeEmpty As Boolean = False, showSepInfo As Boolean = False)
Sub Split(s As String, sepList() As String, result() As String, removeEmpty As Boolean = False, showSepInfo As Boolean = False)
Line 588: Line 865:
Print
Print
Print "Press any key to quit"
Print "Press any key to quit"
Sleep</lang>
Sleep</syntaxhighlight>


{{out}}
{{out}}
Line 609: Line 886:


=={{header|Go}}==
=={{header|Go}}==
<lang go>package main
<syntaxhighlight lang="go">package main


import (
import (
Line 634: Line 911:
func main() {
func main() {
fmt.Printf("%q\n", ms("a!===b=!=c", []string{"==", "!=", "="}))
fmt.Printf("%q\n", ms("a!===b=!=c", []string{"==", "!=", "="}))
}</lang>
}</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 641: Line 918:


=={{header|Haskell}}==
=={{header|Haskell}}==
<lang Haskell>import Data.List
<syntaxhighlight lang="haskell">import Data.List
(isPrefixOf, stripPrefix, genericLength, intercalate)
( genericLength,
intercalate,
isPrefixOf,
stripPrefix,
)
------------------------ MULTISPLIT ----------------------


trysplit :: String -> [String] -> Maybe (String, String)
multisplit :: [String] -> String -> [(String, String, Int)]
trysplit s delims =
multisplit delims = go [] 0
where
go acc pos [] = [(acc, [], pos)]
go acc pos l@(s : sx) =
case trysplit delims l of
Nothing -> go (s : acc) (pos + 1) sx
Just (d, sxx) ->
(acc, d, pos) :
go [] (pos + genericLength d) sxx

trysplit :: [String] -> String -> Maybe (String, String)
trysplit delims s =
case filter (`isPrefixOf` s) delims of
case filter (`isPrefixOf` s) delims of
[] -> Nothing
[] -> Nothing
(d:_) -> Just (d, (\(Just x) -> x) $ stripPrefix d s)
(d : _) -> Just (d, (\(Just x) -> x) $ stripPrefix d s)

multisplit :: String -> [String] -> [(String, String, Int)]
multisplit list delims =
let ms [] acc pos = [(acc, [], pos)]
ms l@(s:sx) acc pos =
case trysplit l delims of
Nothing -> ms sx (s : acc) (pos + 1)
Just (d, sxx) -> (acc, d, pos) : ms sxx [] (pos + genericLength d)
in ms list [] 0


--------------------------- TEST -------------------------
main :: IO ()
main :: IO ()
main = do
main = do
let parsed = multisplit "a!===b=!=c" ["==", "!=", "="]
let parsed = multisplit ["==", "!=", "="] "a!===b=!=c"
mapM_
mapM_
putStrLn
putStrLn
[ "split string:"
[ "split string:",
, intercalate "," $ map (\(a, _, _) -> a) parsed
intercalate "," $ map (\(a, _, _) -> a) parsed,
, "with [(string, delimiter, offset)]:"
"with [(string, delimiter, offset)]:",
, show parsed
show parsed
]</lang>
]</syntaxhighlight>
{{out}}
{{out}}
<pre>split string:
<pre>split string:
Line 677: Line 963:
Or as a fold:
Or as a fold:


<lang haskell>import Data.List (find, isPrefixOf, foldl') --'
<syntaxhighlight lang="haskell">import Data.List (find, isPrefixOf, foldl') --'
import Data.Bool (bool)
import Data.Bool (bool)


Line 694: Line 980:
in reverse $ (ts, [], length s) : ps
in reverse $ (ts, [], length s) : ps
main :: IO ()
main :: IO ()
main = print $ multiSplit ["==", "!=", "="] "a!===b=!=c"</lang>
main = print $ multiSplit ["==", "!=", "="] "a!===b=!=c"</syntaxhighlight>
{{Out}}
{{Out}}
<pre>[("a","!=",1),("","==",3),("b","=",6),("","!=",7),("c","",10)]</pre>
<pre>[("a","!=",1),("","==",3),("b","=",6),("","!=",7),("c","",10)]</pre>


=={{header|Icon}} and {{header|Unicon}}==
=={{header|Icon}} and {{header|Unicon}}==
<lang Icon>procedure main()
<syntaxhighlight lang="icon">procedure main()
s := "a!===b=!=c"
s := "a!===b=!=c"
# just list the tokens
# just list the tokens
Line 719: Line 1,005:
procedure arb()
procedure arb()
suspend .&subject[.&pos:&pos <- &pos to *&subject + 1]
suspend .&subject[.&pos:&pos <- &pos to *&subject + 1]
end</lang>
end</syntaxhighlight>


{{out}}
{{out}}
Line 726: Line 1,012:


=={{header|J}}==
=={{header|J}}==
<lang j>multisplit=: 4 :0
<syntaxhighlight lang="j">multisplit=: {{
'sep begin'=. |: t=. y /:~&.:(|."1)@;@(i.@#@[ ,.L:0"0 I.@E.L:0) x
'begin sep'=. |:bs=. _,~/:~;(,.&.>i.@#) y I.@E.L:0 x NB.
end=. begin + sep { #@>y
len=. #@>y NB.
last=. next=. 0
r=. i.3 0
r=. 2 0$0
j=. k=. 0
while. next<#begin do.
while.j<#x do.
while. j>k{begin do. k=.k+1 end.
r=. r,.(last}.x{.~next{begin);next{t
'b s'=. k{bs NB. character index where separator appears, separator index
last=. next{end
if. _=b do. r,.(j}.x);'';'' return. end.
next=. 1 i.~(begin>next{begin)*.begin>:last
txt=. (j + i. b-j){x
j=. b+s{len
r=.r,.txt;(s{::y);b
end.
end.
}}</syntaxhighlight>
r=. r,.'';~last}.x
)</lang>


Explanation:
Explanation:


First find all potentially relevant separator instances, and sort them in increasing order, by starting location and separator index. <code>sep</code> is separator index, and <code>begin</code> is starting location. <code>end</code> is ending location.
First find all potentially relevant separator instances, and sort them in increasing order, by starting location and separator index. <code>sep</code> is separator index, and <code>begin</code> is starting location.


Then, loop through the possibilities, skipping over those separators which would overlap with previously used separators.
Then, loop through the possibilities, skipping over those separators which would overlap with previously used separators.


The result consists of two rows: The first row is the extracted substrings, the second row is the "extra credit" part -- for each extracted substring, the numbers in the second row are the separator index for the following separator (0 for the first separator, 1 for the second, ...), and the location in the original string where the beginning of the separator appeared (which is the same as where the end of the extracted substring appeared). Note that the very last substring does not have a separator following it, so the extra credit part is blank for that substring.
The result consists of three rows: The first row is the extracted substrings, the second and third rows are the "extra credit" part -- for each extracted substring: the following separator and the position in the original string where that separator started. Note that the very last substring does not have a separator following it, so the extra credit part is blank for that substring.


Example use:
Example use:


<lang j> S=: 'a!===b=!=c'
<syntaxhighlight lang="j"> S multisplit '==';'!=';'='
┌──┬──┬─┬──┬─┐
S multisplit '==';'!=';'='
│a │ │b│ │c│
┌───┬───┬───┬───┬─┐
├──┼──┼─┼──┼─┤
│a │ │b │ │c│
│!=│==│=│!=│ │
├───┼───┼───┼───┼─┤
├──┼──┼─┼──┼─┤
│1 1│0 3│2 6│1 7│ │
│1 │3 │6│7 │ │
└───┴───┴───┴───┴─┘
└──┴──┴─┴──┴─┘
S multisplit '=';'!=';'=='
S multisplit '=';'!=';'=='
┌──┬─┬─┬─┬──┬─┐
┌───┬───┬───┬───┬───┬─┐
│a │ │b │ │c│
│a │ │ │b│ │c│
├──┼─┼─┼─┼──┼─┤
├───┼───┼───┼───┼───┼─┤
│!=│=│=│=│!=│ │
│1 1│0 3│0 4│0 6│1 7│ │
├──┼─┼─┼─┼──┼─┤
└───┴───┴───┴───┴───┴─┘
│1 │3│4│6│7 │ │
└──┴─┴─┴─┴──┴─┘
'X123Y' multisplit '1';'12';'123';'23';'3'
'X123Y' multisplit '1';'12';'123';'23';'3'
┌─┬──┬─┐
┌───┬───┬─┐
│X │ │Y│
│X│ │Y│
├─┼──┼─┤
├───┼───┼─┤
│0 1│3 2│
│1│23│
├─┼──┼─┤
└───┴───┴─┘</lang>
│1│2 │ │
└─┴──┴─┘</syntaxhighlight>


=={{header|Java}}==
=={{header|Java}}==
<lang java>import java.util.*;
<syntaxhighlight lang="java">import java.util.*;


public class MultiSplit {
public class MultiSplit {
Line 802: Line 1,095:
return result;
return result;
}
}
}</lang>
}</syntaxhighlight>


<pre>Regex split:
<pre>Regex split:
Line 814: Line 1,107:
Based on Ruby example.
Based on Ruby example.
{{libheader|Underscore.js}}
{{libheader|Underscore.js}}
<lang JavaScript>RegExp.escape = function(text) {
<syntaxhighlight lang="javascript">RegExp.escape = function(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
}
Line 821: Line 1,114:
var sep_regex = RegExp(_.map(seps, function(sep) { return RegExp.escape(sep); }).join('|'));
var sep_regex = RegExp(_.map(seps, function(sep) { return RegExp.escape(sep); }).join('|'));
return string.split(sep_regex);
return string.split(sep_regex);
}</lang>
}</syntaxhighlight>


===ES6===
===ES6===
Line 827: Line 1,120:


{{Trans|Haskell}} (Multisplit by fold example)
{{Trans|Haskell}} (Multisplit by fold example)
<lang javascript>(() => {
<syntaxhighlight lang="javascript">(() => {


/// Delimiter list -> String -> list of parts, delimiters, offsets
/// Delimiter list -> String -> list of parts, delimiters, offsets
Line 945: Line 1,238:
multiSplit(delims, strTest)
multiSplit(delims, strTest)
);
);
})();</lang>
})();</syntaxhighlight>
{{Out}}
{{Out}}
<pre>[
<pre>[
Line 983: Line 1,276:
Both helper functions could be made inner functions of the main function, but are kept separate here for clarity.
Both helper functions could be made inner functions of the main function, but are kept separate here for clarity.


<lang jq># peeloff(delims) either peels off a delimiter or
<syntaxhighlight lang="jq"># peeloff(delims) either peels off a delimiter or
# a single character from the input string.
# a single character from the input string.
# The input should be a nonempty string, and delims should be
# The input should be a nonempty string, and delims should be
Line 1,029: Line 1,322:
then .[0:length-1] + [ .[length-1] + $x ]
then .[0:length-1] + [ .[length-1] + $x ]
else . + [$x]
else . + [$x]
end ) ;</lang>
end ) ;</syntaxhighlight>
'''Examples'''
'''Examples'''
("a!===b=!=c",
("a!===b=!=c",
Line 1,040: Line 1,333:
=={{header|Julia}}==
=={{header|Julia}}==
From REPL:
From REPL:
<lang julia>
<syntaxhighlight lang="julia">
julia> split(s, r"==|!=|=")
julia> split(s, r"==|!=|=")
5-element Array{SubString{String},1}:
5-element Array{SubString{String},1}:
Line 1,048: Line 1,341:
""
""
"c"
"c"
</syntaxhighlight>
</lang>


=={{header|Kotlin}}==
=={{header|Kotlin}}==
<lang scala>// version 1.0.6
<syntaxhighlight lang="scala">// version 1.0.6


fun main(args: Array<String>) {
fun main(args: Array<String>) {
Line 1,081: Line 1,374:
println("\nThe delimiters matched and the indices at which they occur are:")
println("\nThe delimiters matched and the indices at which they occur are:")
println(matches)
println(matches)
}</lang>
}</syntaxhighlight>


{{out}}
{{out}}
Line 1,094: Line 1,387:
=={{header|Lua}}==
=={{header|Lua}}==
The function I've written here is really excessive for this task but it has historically been hard to find example code for a good Lua split function on the Internet. This one behaves the same way as Julia's Base.split and I've included a comment describing its precise operation.
The function I've written here is really excessive for this task but it has historically been hard to find example code for a good Lua split function on the Internet. This one behaves the same way as Julia's Base.split and I've included a comment describing its precise operation.
<lang Lua>--[[
<syntaxhighlight lang="lua">--[[
Returns a table of substrings by splitting the given string on
Returns a table of substrings by splitting the given string on
occurrences of the given character delimiters, which may be specified
occurrences of the given character delimiters, which may be specified
Line 1,146: Line 1,439:
for k, v in pairs(multisplit) do
for k, v in pairs(multisplit) do
print(k, v)
print(k, v)
end</lang>
end</syntaxhighlight>
{{Out}}
{{Out}}
<pre>Key Value
<pre>Key Value
Line 1,159: Line 1,452:
Code from BBC BASIC with little changes to fit in M2000.
Code from BBC BASIC with little changes to fit in M2000.


<syntaxhighlight lang="m2000 interpreter">
<lang M2000 Interpreter>
Module CheckIt {
Module CheckIt {
DIM sep$()
DIM sep$()
Line 1,189: Line 1,482:
}
}
CheckIt
CheckIt
</syntaxhighlight>
</lang>


=={{header|Mathematica}}==
=={{header|Mathematica}}/{{header|Wolfram Language}}==
Just use the built-in function "StringSplit":
Just use the built-in function "StringSplit":
<lang mathematica>StringSplit["a!===b=!=c", {"==", "!=", "="}]</lang>
<syntaxhighlight lang="mathematica">StringSplit["a!===b=!=c", {"==", "!=", "="}]</syntaxhighlight>
{{Out}}
{{Out}}
<pre>{a,,b,,c}</pre>
<pre>{a,,b,,c}</pre>


=={{header|MiniScript}}==
=={{header|MiniScript}}==
<lang MiniScript>parseSep = function(s, pats)
<syntaxhighlight lang="miniscript">parseSep = function(s, pats)
result = []
result = []
startPos = 0
startPos = 0
Line 1,216: Line 1,509:
end function
end function


print parseSep("a!===b=!=c", ["==", "!=", "="])</lang>
print parseSep("a!===b=!=c", ["==", "!=", "="])</syntaxhighlight>
{{Out}}
{{Out}}
<pre>["a", "{!=}", "", "{==}", "b", "{=}", "", "{!=}"]</pre>
<pre>["a", "{!=}", "", "{==}", "b", "{=}", "", "{!=}"]</pre>


=={{header|Nim}}==
=={{header|Nim}}==
<lang nim>import strutils
<syntaxhighlight lang="nim">import strutils

iterator tokenize(text, sep): tuple[token: string, isSep: bool] =
iterator tokenize(text: string; sep: openArray[string]): tuple[token: string, isSep: bool] =
var i, lastMatch = 0
var i, lastMatch = 0
while i < text.len:
while i < text.len:
for j, s in sep:
for j, s in sep:
if text[i..text.high].startsWith s:
if text[i..text.high].startsWith s:
if i > lastMatch: yield (text[lastMatch .. <i], false)
if i > lastMatch: yield (text[lastMatch ..< i], false)
yield (s, true)
yield (s, true)
lastMatch = i + s.len
lastMatch = i + s.len
Line 1,234: Line 1,527:
break
break
inc i
inc i
if i > lastMatch: yield (text[lastMatch .. <i], false)
if i > lastMatch: yield (text[lastMatch ..< i], false)

for token, isSep in "a!===b=!=c".tokenize(["==", "!=", "="]):
for token, isSep in "a!===b=!=c".tokenize(["==", "!=", "="]):
if isSep: stdout.write '{',token,'}'
if isSep: stdout.write '{',token,'}'
else: stdout.write token
else: stdout.write token
echo ""</lang>
echo ""</syntaxhighlight>

{{out}}
{{out}}
<pre>a{!=}{==}b{=}{!=}c</pre>
<pre>a{!=}{==}b{=}{!=}c</pre>
Line 1,245: Line 1,539:
=={{header|Perl}}==
=={{header|Perl}}==


<lang Perl>sub multisplit {
<syntaxhighlight lang="perl">sub multisplit {
my ($sep, $string, %opt) = @_ ;
my ($sep, $string, %opt) = @_ ;
$sep = join '|', map quotemeta($_), @$sep;
$sep = join '|', map quotemeta($_), @$sep;
Line 1,255: Line 1,549:
print "\n";
print "\n";
print "'$_' " for multisplit ['==','!=','='], "a!===b=!=c", keep_separators => 1;
print "'$_' " for multisplit ['==','!=','='], "a!===b=!=c", keep_separators => 1;
print "\n";</lang>
print "\n";</syntaxhighlight>


{{Out}}
{{Out}}
Line 1,264: Line 1,558:


=={{header|Phix}}==
=={{header|Phix}}==
<!--<syntaxhighlight lang="phix">(phixonline)-->
<lang Phix>procedure multisplit(string text, sequence delims)
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span>
integer k = 1, kdx
<span style="color: #008080;">procedure</span> <span style="color: #000000;">multisplit</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">text</span><span style="color: #0000FF;">,</span> <span style="color: #004080;">sequence</span> <span style="color: #000000;">delims</span><span style="color: #0000FF;">)</span>
while 1 do
<span style="color: #004080;">integer</span> <span style="color: #000000;">k</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">kdx</span>
integer kmin = 0
<span style="color: #008080;">while</span> <span style="color: #004600;">true</span> <span style="color: #008080;">do</span>
for i=1 to length(delims) do
<span style="color: #004080;">integer</span> <span style="color: #000000;">kmin</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span>
integer ki = match(delims[i],text,k)
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">delims</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
if ki!=0 then
<span style="color: #004080;">integer</span> <span style="color: #000000;">ki</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">match</span><span style="color: #0000FF;">(</span><span style="color: #000000;">delims</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">],</span><span style="color: #000000;">text</span><span style="color: #0000FF;">,</span><span style="color: #000000;">k</span><span style="color: #0000FF;">)</span>
if kmin=0 or ki<kmin then
<span style="color: #008080;">if</span> <span style="color: #000000;">ki</span><span style="color: #0000FF;">!=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span>
kmin = ki
<span style="color: #008080;">if</span> <span style="color: #000000;">kmin</span><span style="color: #0000FF;">=</span><span style="color: #000000;">0</span> <span style="color: #008080;">or</span> <span style="color: #000000;">ki</span><span style="color: #0000FF;"><</span><span style="color: #000000;">kmin</span> <span style="color: #008080;">then</span>
kdx = i
<span style="color: #000000;">kmin</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">ki</span>
end if
<span style="color: #000000;">kdx</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">i</span>
end if
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
end for
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
string token = text[k..kmin-1]
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
if kmin=0 then
<span style="color: #004080;">string</span> <span style="color: #000000;">token</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">text</span><span style="color: #0000FF;">[</span><span style="color: #000000;">k</span><span style="color: #0000FF;">..</span><span style="color: #000000;">kmin</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">],</span>
printf(1,"Token: [%s] at %d\n",{token,k})
<span style="color: #000000;">delim</span> <span style="color: #0000FF;">=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #000000;">kmin</span><span style="color: #0000FF;">=</span><span style="color: #000000;">0</span><span style="color: #0000FF;">?</span><span style="color: #008000;">""</span><span style="color: #0000FF;">:</span><span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #008000;">", delimiter (%s) at %d"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">delims</span><span style="color: #0000FF;">[</span><span style="color: #000000;">kdx</span><span style="color: #0000FF;">],</span><span style="color: #000000;">kmin</span><span style="color: #0000FF;">}))</span>
exit
<span style="color: #7060A8;">printf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"Token: [%s] at %d%s\n"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">token</span><span style="color: #0000FF;">,</span><span style="color: #000000;">k</span><span style="color: #0000FF;">,</span><span style="color: #000000;">delim</span><span style="color: #0000FF;">})</span>
end if
<span style="color: #008080;">if</span> <span style="color: #000000;">kmin</span><span style="color: #0000FF;">=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span> <span style="color: #008080;">exit</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
printf(1,"Token: [%s] at %d, delimiter (%s) at %d\n",{token,k,delims[kdx],kmin})
<span style="color: #000000;">k</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">kmin</span><span style="color: #0000FF;">+</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">delims</span><span style="color: #0000FF;">[</span><span style="color: #000000;">kdx</span><span style="color: #0000FF;">])</span>
k = kmin+length(delims[kdx])
<span style="color: #008080;">end</span> <span style="color: #008080;">while</span>
end while
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
end procedure

<span style="color: #000000;">multisplit</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"a!===b=!=c"</span><span style="color: #0000FF;">,{</span><span style="color: #008000;">"=="</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"!="</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"="</span><span style="color: #0000FF;">})</span>
multisplit("a!===b=!=c",{"==","!=","="})</lang>
<!--</syntaxhighlight>-->
{{out}}
{{out}}
<pre>
<pre>
Line 1,298: Line 1,593:


=={{header|PicoLisp}}==
=={{header|PicoLisp}}==
<lang PicoLisp>(de multisplit (Str Sep)
<syntaxhighlight lang="picolisp">(de multisplit (Str Sep)
(setq Sep (mapcar chop Sep))
(setq Sep (mapcar chop Sep))
(make
(make
Line 1,316: Line 1,611:


(println (multisplit "a!===b=!=c" '("==" "!=" "=")))
(println (multisplit "a!===b=!=c" '("==" "!=" "=")))
(println (multisplit "a!===b=!=c" '("=" "!=" "==")))</lang>
(println (multisplit "a!===b=!=c" '("=" "!=" "==")))</syntaxhighlight>
{{out}}
{{out}}
<pre>("a" (1 "!=") NIL (3 "==") "b" (6 "=") NIL (7 "!=") "c")
<pre>("a" (1 "!=") NIL (3 "==") "b" (6 "=") NIL (7 "!=") "c")
Line 1,322: Line 1,617:


=={{header|Pike}}==
=={{header|Pike}}==
<lang Pike>string input = "a!===b=!=c";
<syntaxhighlight lang="pike">string input = "a!===b=!=c";
array sep = ({"==", "!=", "=" });
array sep = ({"==", "!=", "=" });


Line 1,338: Line 1,633:


result;
result;
Result: ({"a", ({"!=", 1}), "", ({"==", 3}), "b", ({"=", 6}), "", ({"!=", 7}), "c"})</lang>
Result: ({"a", ({"!=", 1}), "", ({"==", 3}), "b", ({"=", 6}), "", ({"!=", 7}), "c"})</syntaxhighlight>


=={{header|PowerShell}}==
=={{header|PowerShell}}==
<syntaxhighlight lang="powershell">
<lang PowerShell>
$string = "a!===b=!=c"
$string = "a!===b=!=c"
$separators = [regex]"(==|!=|=)"
$separators = [regex]"(==|!=|=)"
Line 1,353: Line 1,648:


$matchInfo
$matchInfo
</syntaxhighlight>
</lang>
{{Out}}
{{Out}}
<pre>
<pre>
Line 1,365: Line 1,660:
=={{header|Prolog}}==
=={{header|Prolog}}==
Works with SWI-Prolog.
Works with SWI-Prolog.
<lang Prolog>multisplit(_LSep, '') -->
<syntaxhighlight lang="prolog">multisplit(_LSep, '') -->
{!},
{!},
[].
[].
Line 1,405: Line 1,700:
my_sort(<, (N, N1, _), (N, N2, _)) :-
my_sort(<, (N, N1, _), (N, N2, _)) :-
N1 > N2.
N1 > N2.
</syntaxhighlight>
</lang>
{{out}}
{{out}}
<pre>?- multisplit(['==', '!=', '='], 'ax!===b=!=c', Lst, []).
<pre>?- multisplit(['==', '!=', '='], 'ax!===b=!=c', Lst, []).
Line 1,412: Line 1,707:


=={{header|Python}}==
=={{header|Python}}==
===Procedural===

===Using Regular expressions===
Using regular expressions:
<lang python>>>> import re
<syntaxhighlight lang="python">>>> import re
>>> def ms2(txt="a!===b=!=c", sep=["==", "!=", "="]):
>>> def ms2(txt="a!===b=!=c", sep=["==", "!=", "="]):
if not txt or not sep:
if not txt or not sep:
Line 1,428: Line 1,723:
['a', (1, 1), '', (0, 3), 'b', (2, 6), '', (1, 7), 'c']
['a', (1, 1), '', (0, 3), 'b', (2, 6), '', (1, 7), 'c']
>>> ms2(txt="a!===b=!=c", sep=["=", "!=", "=="])
>>> ms2(txt="a!===b=!=c", sep=["=", "!=", "=="])
['a', (1, 1), '', (0, 3), '', (0, 4), 'b', (0, 6), '', (1, 7), 'c']</lang>
['a', (1, 1), '', (0, 3), '', (0, 4), 'b', (0, 6), '', (1, 7), 'c']</syntaxhighlight>


===Not using RE's===
Not using regular expressions:
'''Inspired by C-version'''
'''Inspired by C-version'''
<lang python>def multisplit(text, sep):
<syntaxhighlight lang="python">def multisplit(text, sep):
lastmatch = i = 0
lastmatch = i = 0
matches = []
matches = []
Line 1,454: Line 1,749:
>>> multisplit('a!===b=!=c', ['!=', '==', '='])
>>> multisplit('a!===b=!=c', ['!=', '==', '='])
['a', (0, 1), (1, 3), 'b', (2, 6), (0, 7), 'c']
['a', (0, 1), (1, 3), 'b', (2, 6), (0, 7), 'c']
</syntaxhighlight>
</lang>


'''Alternative version'''
'''Alternative version'''
<lang python>def min_pos(List):
<syntaxhighlight lang="python">def min_pos(List):
return List.index(min(List))
return List.index(min(List))


Line 1,523: Line 1,818:
S = "a!===b=!=c"
S = "a!===b=!=c"
multisplit(S, ["==", "!=", "="]) # output: ['a', [1, 1], '', [0, 3], 'b', [2, 6], '', [1, 7], 'c']
multisplit(S, ["==", "!=", "="]) # output: ['a', [1, 1], '', [0, 3], 'b', [2, 6], '', [1, 7], 'c']
multisplit(S, ["=", "!=", "=="]) # output: ['a', [1, 1], '', [0, 3], '', [0, 4], 'b', [0, 6], '', [1, 7], 'c']</lang>
multisplit(S, ["=", "!=", "=="]) # output: ['a', [1, 1], '', [0, 3], '', [0, 4], 'b', [0, 6], '', [1, 7], 'c']</syntaxhighlight>

===Functional===
In terms of a fold (reduce), without use of regular expressions:
{{Works with|Python|3.7}}
<syntaxhighlight lang="python">'''Multisplit'''


from functools import reduce


# multiSplit :: [String] -> String -> [(String, String, Int)]
def multiSplit(separators):
'''List of triples:
[(token, separator, start index of separator].
'''
def go(s):
def f(tokensPartsOffset, ic):
tokens, parts, offset = tokensPartsOffset
i, c = ic
inDelim = offset > i
return maybe(
(
tokens if inDelim
else c + tokens, parts, offset
)
)(
lambda x: (
'',
[(tokens, x, i)] + parts,
i + len(x)
)
)(
None if inDelim else find(
s[i:].startswith
)(separators)
)
ts, ps, _ = reduce(f, enumerate(s), ('', [], 0))
return list(reversed(ps)) + [(ts, '', len(s))]
return go


# ------------------------- TEST -------------------------
# main :: IO ()
def main():
'''String split on three successive separators.'''
print(
multiSplit(['==', '!=', '='])(
'a!===b=!=c'
)
)


# ------------------ GENERIC FUNCTIONS -------------------

# find :: (a -> Bool) -> [a] -> (a | None)
def find(p):
'''Just the first element in the list that matches p,
or None if no elements match.
'''
def go(xs):
try:
return next(x for x in xs if p(x))
except StopIteration:
return None
return go


# maybe :: b -> (a -> b) -> (a | None) -> b
def maybe(v):
'''Either the default value v, if m is None,
or the application of f to x.
'''
return lambda f: lambda m: v if (
None is m
) else f(m)


# MAIN ---
if __name__ == '__main__':
main()</syntaxhighlight>
{{Out}}
<pre>[('a', '!=', 1), ('', '==', 3), ('b', '=', 6), ('', '!=', 7), ('c', '', 10)]</pre>


=={{header|Racket}}==
=={{header|Racket}}==


<lang racket>
<syntaxhighlight lang="racket">
#lang racket
#lang racket
(regexp-match* #rx"==|!=|=" "a!===b=!=c" #:gap-select? #t #:match-select values)
(regexp-match* #rx"==|!=|=" "a!===b=!=c" #:gap-select? #t #:match-select values)
;; => '("a" ("!=") "" ("==") "b" ("=") "" ("!=") "c")
;; => '("a" ("!=") "" ("==") "b" ("=") "" ("!=") "c")
</syntaxhighlight>
</lang>


=={{header|Raku}}==
=={{header|Raku}}==
(formerly Perl 6)
(formerly Perl 6)
<syntaxhighlight lang="raku" line>sub multisplit($str, @seps) { $str.split: / ||@seps /, :v }
{{Works with|rakudo|2015-11-29}}
<lang perl6>sub multisplit($str, @seps) { $str.split(/ ||@seps /, :v) }


my @chunks = multisplit( 'a!===b=!=c==d', < == != = > );
my @chunks = multisplit 'a!===b=!=c==d', < == != = >;


# Print the strings.
# Print the strings.
say @chunks».Str.perl;
say @chunks».Str.raku;


# Print the positions of the separators.
# Print the positions of the separators.
for grep Match, @chunks -> $s {
for grep Match, @chunks -> $s {
say " $s from $s.from() to $s.to()";
say "{$s.fmt: '%2s'} from {$s.from.fmt: '%2d'} to {$s.to.fmt: '%2d'}";
}</lang>
}</syntaxhighlight>
{{out}}
{{out}}
<pre>("a", "!=", "", "==", "b", "=", "", "!=", "c", "==", "d")
<pre>("a", "!=", "", "==", "b", "=", "", "!=", "c", "==", "d")
!= from 1 to 3
!= from 1 to 3
== from 3 to 5
== from 3 to 5
= from 6 to 7
= from 6 to 7
!= from 7 to 9
!= from 7 to 9
== from 10 to 12</pre>
== from 10 to 12</pre>
Using the array <tt>@seps</tt> in a pattern automatically does alternation.
Using the array <tt>@seps</tt> in a pattern automatically does alternation.
By default this would do longest-term matching (that is, <tt>|</tt> semantics), but we can force it to do left-to-right matching by embedding the array in a short-circuit alternation (that is, <tt>||</tt> semantics).
By default this would do longest-term matching (that is, <tt>|</tt> semantics), but we can force it to do left-to-right matching by embedding the array in a short-circuit alternation (that is, <tt>||</tt> semantics).
Line 1,561: Line 1,937:


=={{header|REXX}}==
=={{header|REXX}}==
<lang rexx>/*REXX program splits a (character) string based on different separator delimiters.*/
<syntaxhighlight lang="rexx">/*REXX program splits a (character) string based on different separator delimiters.*/
parse arg $ /*obtain optional string from the C.L. */
parse arg $ /*obtain optional string from the C.L. */
if $='' then $= "a!===b=!=c" /*None specified? Then use the default*/
if $='' then $= "a!===b=!=c" /*None specified? Then use the default*/
Line 1,586: Line 1,962:
$=changestr(null, $, showNull) /* ··· showing of "null" chars. */
$=changestr(null, $, showNull) /* ··· showing of "null" chars. */
say 'new string:' $ /*now, display the new string to term. */
say 'new string:' $ /*now, display the new string to term. */
/*stick a fork in it, we're all done. */</lang>
/*stick a fork in it, we're all done. */</syntaxhighlight>
Some older REXXes don't have a &nbsp; '''changestr''' &nbsp; BIF, so one is included here &nbsp; ──► &nbsp; [[CHANGESTR.REX]].
Some older REXXes don't have a &nbsp; '''changestr''' &nbsp; BIF, so one is included here &nbsp; ──► &nbsp; [[CHANGESTR.REX]].
<br><br>'''output''' &nbsp; when using the default input:
<br><br>'''output''' &nbsp; when using the default input:
Line 1,595: Line 1,971:


=={{header|Ring}}==
=={{header|Ring}}==
<lang ring>
<syntaxhighlight lang="ring">
# Project : Multisplit
# Project : Multisplit


Line 1,605: Line 1,981:
see "" + n + ": " + substr(str, 1, pos-1) + " Sep By: " + sep[n] + nl
see "" + n + ": " + substr(str, 1, pos-1) + " Sep By: " + sep[n] + nl
next
next
</syntaxhighlight>
</lang>
Output:
Output:
<pre>
<pre>
Line 1,618: Line 1,994:
The simple method, using a regular expression to split the text.
The simple method, using a regular expression to split the text.


<lang ruby>text = 'a!===b=!=c'
<syntaxhighlight lang="ruby">text = 'a!===b=!=c'
separators = ['==', '!=', '=']
separators = ['==', '!=', '=']


Line 1,626: Line 2,002:


p multisplit_simple(text, separators) # => ["a", "", "b", "", "c"]
p multisplit_simple(text, separators) # => ["a", "", "b", "", "c"]
</syntaxhighlight>
</lang>


The version that also returns the information about the separations.
The version that also returns the information about the separations.


<lang ruby>def multisplit(text, separators)
<syntaxhighlight lang="ruby">def multisplit(text, separators)
sep_regex = Regexp.union(separators)
sep_regex = Regexp.union(separators)
separator_info = []
separator_info = []
Line 1,647: Line 2,023:


p multisplit(text, separators)
p multisplit(text, separators)
# => [["a", "", "b", "", "c"], [["!=", 1], ["==", 3], ["=", 6], ["!=", 7]]]</lang>
# => [["a", "", "b", "", "c"], [["!=", 1], ["==", 3], ["=", 6], ["!=", 7]]]</syntaxhighlight>


Also demonstrating a method to rejoin the string given the separator information.
Also demonstrating a method to rejoin the string given the separator information.


<lang ruby>def multisplit_rejoin(info)
<syntaxhighlight lang="ruby">def multisplit_rejoin(info)
str = info[0].zip(info[1])[0..-2].inject("") {|str, (piece, (sep, idx))| str << piece << sep}
str = info[0].zip(info[1])[0..-2].inject("") {|str, (piece, (sep, idx))| str << piece << sep}
str << info[0].last
str << info[0].last
Line 1,657: Line 2,033:


p multisplit_rejoin(multisplit(text, separators)) == text
p multisplit_rejoin(multisplit(text, separators)) == text
# => true</lang>
# => true</syntaxhighlight>


=={{header|Run BASIC}}==
=={{header|Run BASIC}}==
<lang runbasic>str$ = "a!===b=!=c"
<syntaxhighlight lang="runbasic">str$ = "a!===b=!=c"
sep$ = "=== != =! b =!="
sep$ = "=== != =! b =!="


Line 1,668: Line 2,044:
split$ = word$(str$,1,theSep$)
split$ = word$(str$,1,theSep$)
print i;" ";split$;" Sep By: ";theSep$
print i;" ";split$;" Sep By: ";theSep$
wend</lang>
wend</syntaxhighlight>
{{out}}
{{out}}
<pre>1 a! Sep By: ===
<pre>1 a! Sep By: ===
Line 1,677: Line 2,053:


=={{header|Scala}}==
=={{header|Scala}}==
<lang scala>import scala.annotation.tailrec
<syntaxhighlight lang="scala">import scala.annotation.tailrec
def multiSplit(str:String, sep:Seq[String])={
def multiSplit(str:String, sep:Seq[String])={
def findSep(index:Int)=sep find (str startsWith (_, index))
def findSep(index:Int)=sep find (str startsWith (_, index))
Line 1,692: Line 2,068:
}
}


println(multiSplit("a!===b=!=c", Seq("!=", "==", "=")))</lang>
println(multiSplit("a!===b=!=c", Seq("!=", "==", "=")))</syntaxhighlight>
{{out}}
{{out}}
<pre>List(a, , b, , c)</pre>
<pre>List(a, , b, , c)</pre>
Line 1,698: Line 2,074:
=={{header|Scheme}}==
=={{header|Scheme}}==
{{works with|Gauche Scheme}}
{{works with|Gauche Scheme}}
<lang Scheme>(use srfi-13)
<syntaxhighlight lang="scheme">(use srfi-13)
(use srfi-42)
(use srfi-42)


Line 1,712: Line 2,088:
(define (glean shards)
(define (glean shards)
(list-ec (: x (index i) shards)
(list-ec (: x (index i) shards)
(if (even? i)) x))</lang>
(if (even? i)) x))</syntaxhighlight>
<b>Testing:</b>
<b>Testing:</b>
<pre>
<pre>
Line 1,725: Line 2,101:
First approach, using line delimiters. Lines are delimited by an array of separator strings, normally [CRLF, LF, CR, lineSeparator(0x2028), paragraphSeparator(0x2029)]. Supplying an alternate set of delimiters lets us split a string by a different (ordered) set of strings:
First approach, using line delimiters. Lines are delimited by an array of separator strings, normally [CRLF, LF, CR, lineSeparator(0x2028), paragraphSeparator(0x2029)]. Supplying an alternate set of delimiters lets us split a string by a different (ordered) set of strings:


<lang sensetalk>set source to "a!===b=!=c"
<syntaxhighlight lang="sensetalk">set source to "a!===b=!=c"
set separators to ["==", "!=", "="]
set separators to ["==", "!=", "="]


put each line delimited by separators of source</lang>
put each line delimited by separators of source</syntaxhighlight>
Output:
Output:
<lang sensetalk>(a,,b,,c)</lang>
<syntaxhighlight lang="sensetalk">(a,,b,,c)</syntaxhighlight>


Second approach, using a pattern. SenseTalk's pattern language lets us define a pattern (a regex) which can then be used to split the string and also to display the actual separators that were found.
Second approach, using a pattern. SenseTalk's pattern language lets us define a pattern (a regex) which can then be used to split the string and also to display the actual separators that were found.
<lang sensetalk>set source to "a!===b=!=c"
<syntaxhighlight lang="sensetalk">set source to "a!===b=!=c"
set separatorPattern to <"==" or "!=" or "=">
set separatorPattern to <"==" or "!=" or "=">


Line 1,739: Line 2,115:


put each occurrence of separatorPattern in source
put each occurrence of separatorPattern in source
</syntaxhighlight>
</lang>
Output:
Output:
<lang sensetalk>(a,,b,,c)
<syntaxhighlight lang="sensetalk">(a,,b,,c)
(!=,==,=,!=)</lang>
(!=,==,=,!=)</syntaxhighlight>


=={{header|Sidef}}==
=={{header|Sidef}}==
<lang ruby>func multisplit(sep, str, keep_sep=false) {
<syntaxhighlight lang="ruby">func multisplit(sep, str, keep_sep=false) {
sep = sep.map{.escape}.join('|');
sep = sep.map{.escape}.join('|');
var re = Regex.new(keep_sep ? "(#{sep})" : sep);
var re = Regex.new(keep_sep ? "(#{sep})" : sep);
Line 1,753: Line 2,129:
[false, true].each { |bool|
[false, true].each { |bool|
say multisplit(%w(== != =), 'a!===b=!=c', keep_sep: bool);
say multisplit(%w(== != =), 'a!===b=!=c', keep_sep: bool);
}</lang>
}</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 1,759: Line 2,135:
["a", "!=", "", "==", "b", "=", "", "!=", "c"]
["a", "!=", "", "==", "b", "=", "", "!=", "c"]
</pre>
</pre>

=={{header|Swift}}==

Swift strings are purposefully not index by integers to avoid confusion and performance traps when dealing with unicode. As such the indexes returned by this method are not very helpful to a human reader, but can be used to manipulate the original string.

{{trans|Python}}

<syntaxhighlight lang="swift">extension String {
func multiSplit(on seps: [String]) -> ([Substring], [(String, (start: String.Index, end: String.Index))]) {
var matches = [Substring]()
var matched = [(String, (String.Index, String.Index))]()
var i = startIndex
var lastMatch = startIndex

main: while i != endIndex {
for sep in seps where self[i...].hasPrefix(sep) {
if i > lastMatch {
matches.append(self[lastMatch..<i])
} else {
matches.append("")
}

lastMatch = index(i, offsetBy: sep.count)
matched.append((sep, (i, lastMatch)))
i = lastMatch

continue main
}

i = index(i, offsetBy: 1)
}

if i > lastMatch {
matches.append(self[lastMatch..<i])
}

return (matches, matched)
}
}

let (matches, matchedSeps) = "a!===b=!=c".multiSplit(on: ["==", "!=", "="])

print(matches, matchedSeps.map({ $0.0 }))</syntaxhighlight>


{{out}}

<pre>["a", "", "b", "", "c"] ["!=", "==", "=", "!="]</pre>


=={{header|Tcl}}==
=={{header|Tcl}}==
This simple version does not retain information about what the separators were:
This simple version does not retain information about what the separators were:
<lang tcl>proc simplemultisplit {text sep} {
<syntaxhighlight lang="tcl">proc simplemultisplit {text sep} {
set map {}; foreach s $sep {lappend map $s "\uffff"}
set map {}; foreach s $sep {lappend map $s "\uffff"}
return [split [string map $map $text] "\uffff"]
return [split [string map $map $text] "\uffff"]
}
}
puts [simplemultisplit "a!===b=!=c" {"==" "!=" "="}]</lang>
puts [simplemultisplit "a!===b=!=c" {"==" "!=" "="}]</syntaxhighlight>
{{out}}
{{out}}
<pre>a {} b {} c</pre>
<pre>a {} b {} c</pre>
Line 1,773: Line 2,197:
to the match information (because the two collections of information
to the match information (because the two collections of information
are of different lengths).
are of different lengths).
<lang tcl>proc multisplit {text sep} {
<syntaxhighlight lang="tcl">proc multisplit {text sep} {
foreach s $sep {lappend sr [regsub -all {\W} $s {\\&}]}
foreach s $sep {lappend sr [regsub -all {\W} $s {\\&}]}
set sepRE [join $sr "|"]
set sepRE [join $sr "|"]
Line 1,786: Line 2,210:
}
}
return [list [lappend pieces [string range $text $start end]] $match]
return [list [lappend pieces [string range $text $start end]] $match]
}</lang>
}</syntaxhighlight>
Demonstration code:
Demonstration code:
<lang tcl>set input "a!===b=!=c"
<syntaxhighlight lang="tcl">set input "a!===b=!=c"
set matchers {"==" "!=" "="}
set matchers {"==" "!=" "="}
lassign [multisplit $input $matchers] substrings matchinfo
lassign [multisplit $input $matchers] substrings matchinfo
puts $substrings
puts $substrings
puts $matchinfo</lang>
puts $matchinfo</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 1,809: Line 2,233:
The <code>:gap 0</code> makes the horizontal collect repetitions strictly adjacent. This means that <code>coll</code> will quit when faced with a nonmatching suffix portion of the data rather than scan forward (no gap allowed!). This creates an opportunity for the <code>tail</code> variable to grab the suffix which remains, which may be an empty string.
The <code>:gap 0</code> makes the horizontal collect repetitions strictly adjacent. This means that <code>coll</code> will quit when faced with a nonmatching suffix portion of the data rather than scan forward (no gap allowed!). This creates an opportunity for the <code>tail</code> variable to grab the suffix which remains, which may be an empty string.


<lang txr>@(next :args)
<syntaxhighlight lang="txr">@(next :args)
@(coll :gap 0)@(choose :shortest tok)@\
@(coll :gap 0)@(choose :shortest tok)@\
@tok@{sep /==/}@\
@tok@{sep /==/}@\
Line 1,819: Line 2,243:
@(output)
@(output)
@(rep)"@tok" {@sep} @(end)"@tail"
@(rep)"@tok" {@sep} @(end)"@tail"
@(end)</lang>
@(end)</syntaxhighlight>


Runs:
Runs:
Line 1,844: Line 2,268:
{{trans|Racket}}
{{trans|Racket}}


<lang sh>$ txr -p '(tok-str "a!===b=!=c" #/==|!=|=/ t)'
<syntaxhighlight lang="sh">$ txr -p '(tok-str "a!===b=!=c" #/==|!=|=/ t)'
("a" "!=" "" "==" "b" "=" "" "!=" "c")</lang>
("a" "!=" "" "==" "b" "=" "" "!=" "c")</syntaxhighlight>


Here the third boolean argument means "keep the material between the tokens", which in the Racket version seems to be requested by the argument <code>#:gap-select? #:t</code>.
Here the third boolean argument means "keep the material between the tokens", which in the Racket version seems to be requested by the argument <code>#:gap-select? #:t</code>.
Line 1,851: Line 2,275:
=={{header|UNIX Shell}}==
=={{header|UNIX Shell}}==
{{works with|bash}}
{{works with|bash}}
<lang bash>multisplit() {
<syntaxhighlight lang="bash">multisplit() {
local str=$1
local str=$1
shift
shift
Line 1,879: Line 2,303:
if [[ $original == $recreated ]]; then
if [[ $original == $recreated ]]; then
echo "successfully able to recreate original string"
echo "successfully able to recreate original string"
fi</lang>
fi</syntaxhighlight>


{{out}}
{{out}}
Line 1,890: Line 2,314:


=={{header|VBScript}}==
=={{header|VBScript}}==
<syntaxhighlight lang="vb">
<lang vb>
Function multisplit(s,sep)
Function multisplit(s,sep)
arr_sep = Split(sep,"|")
arr_sep = Split(sep,"|")
Line 1,917: Line 2,341:
WScript.StdOut.WriteLine
WScript.StdOut.WriteLine
WScript.StdOut.Write "Extra Credit: " & multisplit_extra("a!===b=!=c","!=|==|=")
WScript.StdOut.Write "Extra Credit: " & multisplit_extra("a!===b=!=c","!=|==|=")
WScript.StdOut.WriteLine</lang>
WScript.StdOut.WriteLine</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 1,923: Line 2,347:
Extra Credit: a(!=)(==)b(=)(!=)c
Extra Credit: a(!=)(==)b(=)(!=)c
</pre>
</pre>

=={{header|V (Vlang)}}==
Without using additional libraries or regular expressions:
<syntaxhighlight lang="v (vlang)">fn main() {
str := "a!===b=!=c"
sep := ["==","!=","="]
println(ms(str, sep))
}

fn ms(txt string, sep []string) (map[int]string, []string, []string) {
mut ans, mut extra := []string{}, []string{}
mut place := map[int]string{}
mut temp :=''
mut vlen := 0

for slen in sep {if slen.len > vlen {vlen = slen.len}}
for cidx, cval in txt {
temp += cval.ascii_str()
for value in sep {
if temp.contains(value) && temp.len >= vlen {
place[cidx] = value
temp =''
}
}
}

for tidx, tval in txt {
for pkey, pval in place {
if tidx == pkey {
ans << ''
extra << '(' + pval + ')'
}
}
if sep.any(it.contains(tval.ascii_str())) == false {
ans << tval.ascii_str()
extra << tval.ascii_str()
}
}
println('Ending indices: $place')
println('Answer: $ans')
println('Extra: $extra')
return place, ans, extra
}</syntaxhighlight>
{{out}}
<pre>
Ending indices: {2: '!=', 4: '==', 6: '=', 8: '!='}
Answer: ['a', '', '', 'b', '', '', 'c']
Extra: ['a', '(!=)', '(==)', 'b', '(=)', '(!=)', 'c']
({2: '!=', 4: '==', 6: '=', 8: '!='}, ['a', '', '', 'b', '', '', 'c'], ['a', '(!=)', '(==)', 'b', '(=)', '(!=)', 'c'])
</pre>

=={{header|Wren}}==
{{libheader|Wren-pattern}}
{{libheader|Wren-fmt}}
<syntaxhighlight lang="wren">import "./pattern" for Pattern
import "./fmt" for Fmt

var input = "a!===b=!=c"
var p = Pattern.new("[/=/=|!/=|/=]")
var separators = p.findAll(input)
System.print("The separators matched and their starting/ending indices are:")
for (sep in separators) {
System.print(" %(Fmt.s(-4, Fmt.q(sep.text))) between %(sep.span)")
}
var parts = p.splitAll(input)
System.print("\nThe substrings between the separators are:")
System.print(parts.map { |p| (p != "") ? Fmt.q(p) : "empty string" }.toList)</syntaxhighlight>

{{out}}
<pre>
The separators matched and their starting/ending indices are:
"!=" between [1, 2]
"==" between [3, 4]
"=" between [6, 6]
"!=" between [7, 8]

The substrings between the separators are:
["a", empty string, "b", empty string, "c"]
</pre>

=={{header|XPL0}}==
<syntaxhighlight lang "XPL0">include xpllib; \for StrLen, StrNCmp, and Print

proc MultiSplit(Str, Seps, N);
char Str; int Seps, N;
int S, Ch, SepLen;
[while Str(0) # 0 do
[for S:= 0 to N-1 do
[SepLen:= StrLen(Seps(S));
if StrNCmp(Str, Seps(S), SepLen) = 0 then
[Print(" (%s) ", Seps(S));
Str:= Str + SepLen;
S:= 100;
];
];
if S < 100 then
[Ch:= Str(0); Str:= Str+1;
if Ch # 0 then ChOut(0, Ch);
];
];
];

MultiSplit("a!===b=!=c", ["==", "!=", "="], 3)</syntaxhighlight>
{{out}}
<pre>
a (!=) (==) b (=) (!=) c</pre>


=={{header|Yabasic}}==
=={{header|Yabasic}}==
<lang Yabasic>t$ = "a!===b=!=c"
<syntaxhighlight lang="yabasic">t$ = "a!===b=!=c"
s$ = "==,!=,="
s$ = "==,!=,="


Line 1,943: Line 2,474:
print left$(t$, l - 1), " with separator ", n$(j)
print left$(t$, l - 1), " with separator ", n$(j)
t$ = right$(t$, len(t$) - (l + len(n$(j))) + 1)
t$ = right$(t$, len(t$) - (l + len(n$(j))) + 1)
loop</lang>
loop</syntaxhighlight>


=={{header|zkl}}==
=={{header|zkl}}==
{{trans|Python}}
{{trans|Python}}
<lang zkl>fcn multisplit(text, sep){
<syntaxhighlight lang="zkl">fcn multisplit(text, sep){
lastmatch := i := 0; matches := List();
lastmatch := i := 0; matches := List();
while(i < text.len()){
while(i < text.len()){
Line 1,963: Line 2,494:
if(i > lastmatch) matches.append(text[lastmatch,i-lastmatch]);
if(i > lastmatch) matches.append(text[lastmatch,i-lastmatch]);
return(matches);
return(matches);
}</lang>
}</syntaxhighlight>
<lang zkl>multisplit("a!===b=!=c", T("==", "!=", "=")).println();
<syntaxhighlight lang="zkl">multisplit("a!===b=!=c", T("==", "!=", "=")).println();
multisplit("a!===b=!=c", T("!=", "==", "=")).println();</lang>
multisplit("a!===b=!=c", T("!=", "==", "=")).println();</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>

Revision as of 23:35, 7 January 2024

Task
Multisplit
You are encouraged to solve this task according to the task description, using any language you may know.

It is often necessary to split a string into pieces based on several different (potentially multi-character) separator strings, while still retaining the information about which separators were present in the input.

This is particularly useful when doing small parsing tasks.
The task is to write code to demonstrate this.

The function (or procedure or method, as appropriate) should take an input string and an ordered collection of separators.

The order of the separators is significant:
The delimiter order represents priority in matching, with the first defined delimiter having the highest priority. In cases where there would be an ambiguity as to which separator to use at a particular point (e.g., because one separator is a prefix of another) the separator with the highest priority should be used. Delimiters can be reused and the output from the function should be an ordered sequence of substrings.

Test your code using the input string “a!===b=!=c” and the separators “==”, “!=” and “=”.

For these inputs the string should be parsed as "a" (!=) "" (==) "b" (=) "" (!=) "c", where matched delimiters are shown in parentheses, and separated strings are quoted, so our resulting output is "a", empty string, "b", empty string, "c". Note that the quotation marks are shown for clarity and do not form part of the output.

Extra Credit: provide information that indicates which separator was matched at each separation point and where in the input string that separator was matched.

11l

Translation of: Python
F multisplit(text, sep)
   V lastmatch = 0
   V i = 0
   V matches = ‘’
   L i < text.len
      L(s) sep
         V j = L.index
         I text[i..].starts_with(s)
            I i > lastmatch
               matches ‘’= text[lastmatch .< i]
            matches ‘’= ‘{’s‘}’
            lastmatch = i + s.len
            i += s.len
            L.break
      L.was_no_break
         i++
   I i > lastmatch
      matches ‘’= text[lastmatch .< i]
   R matches

print(multisplit(‘a!===b=!=c’, [‘==’, ‘!=’, ‘=’]))
Output:
a{!=}{==}b{=}{!=}c

Ada

multisplit.adb:

with Ada.Containers.Indefinite_Doubly_Linked_Lists;
with Ada.Text_IO;

procedure Multisplit is
   package String_Lists is new Ada.Containers.Indefinite_Doubly_Linked_Lists
     (Element_Type => String);
   use type String_Lists.Cursor;

   function Split
     (Source     : String;
      Separators : String_Lists.List)
      return       String_Lists.List
   is
      Result             : String_Lists.List;
      Next_Position      : Natural := Source'First;
      Prev_Position      : Natural := Source'First;
      Separator_Position : String_Lists.Cursor;
      Separator_Length   : Natural;
      Changed            : Boolean;
   begin
      loop
         Changed            := False;
         Separator_Position := Separators.First;
         while Separator_Position /= String_Lists.No_Element loop
            Separator_Length :=
              String_Lists.Element (Separator_Position)'Length;
            if Next_Position + Separator_Length - 1 <= Source'Last
              and then Source
                (Next_Position .. Next_Position + Separator_Length - 1) =
                String_Lists.Element (Separator_Position)
            then
               if Next_Position > Prev_Position then
                  Result.Append
                    (Source (Prev_Position .. Next_Position - 1));
               end if;
               Result.Append (String_Lists.Element (Separator_Position));
               Next_Position := Next_Position + Separator_Length;
               Prev_Position := Next_Position;
               Changed       := True;
               exit;
            end if;
            Separator_Position := String_Lists.Next (Separator_Position);
         end loop;
         if not Changed then
            Next_Position := Next_Position + 1;
         end if;
         if Next_Position > Source'Last then
            Result.Append (Source (Prev_Position .. Source'Last));
            exit;
         end if;
      end loop;
      return Result;
   end Split;

   Test_Input      : constant String := "a!===b=!=c";
   Test_Separators : String_Lists.List;
   Test_Result     : String_Lists.List;
   Pos             : String_Lists.Cursor;
begin
   Test_Separators.Append ("==");
   Test_Separators.Append ("!=");
   Test_Separators.Append ("=");
   Test_Result := Split (Test_Input, Test_Separators);
   Pos         := Test_Result.First;
   while Pos /= String_Lists.No_Element loop
      Ada.Text_IO.Put (" " & String_Lists.Element (Pos));
      Pos := String_Lists.Next (Pos);
   end loop;
   Ada.Text_IO.New_Line;
   -- other order of separators
   Test_Separators.Clear;
   Test_Separators.Append ("=");
   Test_Separators.Append ("!=");
   Test_Separators.Append ("==");
   Test_Result := Split (Test_Input, Test_Separators);
   Pos         := Test_Result.First;
   while Pos /= String_Lists.No_Element loop
      Ada.Text_IO.Put (" " & String_Lists.Element (Pos));
      Pos := String_Lists.Next (Pos);
   end loop;
end Multisplit;
Output:
 a != == b = != c
 a != = = b = != c

ALGOL 68

# split a string based on a number of separators #

# MODE to hold the split results #
MODE SPLITINFO = STRUCT( STRING text      # delimited string, may be empty          #
                       , INT    position  # starting position of the token          #
                       , STRING delimiter # the delimiter that terminated the token #
                       );
# calculates the length of string s #
OP   LENGTH = ( STRING s )INT: ( UPB s + 1 ) - LWB s;
# returns TRUE if s starts with p, FALSE otherwise #
PRIO STARTSWITH = 5;
OP   STARTSWITH = ( STRING s, p )BOOL: IF LENGTH p > LENGTH s THEN FALSE ELSE s[ LWB s : ( LWB s + LENGTH p ) - 1 ] = p FI;
# returns an array of SPLITINFO describing the tokens in str based on the delimiters #
# zero-length delimiters are ignored #
PRIO SPLIT = 5;
OP   SPLIT = ( STRING str, []STRING delimiters )[]SPLITINFO:
     BEGIN
        # count the number of tokens #
        # allow there to be as many tokens as characters in the string + 2 #
        # that would cater for a string composed of delimiters only        #
        [ 1 : ( UPB str + 3 ) - LWB str ]SPLITINFO tokens;
        INT   token count   := 0;
        INT   str pos       := LWB str;
        INT   str max        = UPB str;
        BOOL  token pending := FALSE;
        # construct the tokens #
        str pos       := LWB str;
        INT prev pos  := LWB str;
        token count   := 0;
        token pending := FALSE;
        WHILE str pos <= str max
        DO
            BOOL found delimiter := FALSE;
            FOR d FROM LWB delimiters TO UPB delimiters WHILE NOT found delimiter DO
                IF LENGTH delimiters[ d ] > 0 THEN
                    IF found delimiter := str[ str pos : ] STARTSWITH delimiters[ d ] THEN
                        token count          +:= 1;
                        tokens[ token count ] := ( str[ prev pos : str pos - 1 ], prev pos, delimiters[ d ] );
                        str pos              +:= LENGTH delimiters[ d ];
                        prev pos              := str pos;
                        token pending         := FALSE
                    FI
                FI
            OD;
            IF NOT found delimiter THEN
                # the current character is part of s token #
                token pending := TRUE;
                str pos      +:= 1
            FI
        OD;
        IF token pending THEN
            # there is an additional token after the final delimiter #
            token count +:= 1;
            tokens[ token count ] := ( str[ prev pos : ], prev pos, "" )
        FI;
        # return an array of the actual tokens #
        tokens[ 1 : token count ]
     END # SPLIT # ;


# test the SPLIT operator #
[]SPLITINFO test tokens = "a!===b=!=c" SPLIT []STRING( "==", "!=", "=" );
FOR t FROM LWB test tokens TO UPB test tokens DO
    SPLITINFO token = test tokens[ t ];
    print( ( "token: [",  text OF token, "] at: ", whole( position OF token, 0 ), " delimiter: (", delimiter OF token, ")", newline ) )
OD
Output:
token: [a] at: 1 delimiter: (!=)
token: [] at: 4 delimiter: (==)
token: [b] at: 6 delimiter: (=)
token: [] at: 8 delimiter: (!=)
token: [c] at: 10 delimiter: ()

Arturo

print split.by:["==" "!=" "="] "a!===b=!=c"
Output:
a b c

AutoHotkey

Str := "a!===b=!=c"
Sep := ["==","!=", "="]
Res := StrSplit(Str, Sep)
for k, v in Res
	Out .= (Out?",":"")  v 
MsgBox % Out
for k, v in Sep
	N .= (N?"|":"") "\Q" v "\E"
MsgBox % RegExReplace(str, "(.*?)(" N ")", "$1 {$2}")
Output:
a,,b,,c
a {!=} {==}b {=} {!=}c

AWK

# syntax: GAWK -f MULTISPLIT.AWK
BEGIN {
    str = "a!===b=!=c"
    sep = "(==|!=|=)"
    printf("str: %s\n",str)
    printf("sep: %s\n\n",sep)
    n = split(str,str_arr,sep,sep_arr)
    printf("parsed: ")
    for (i=1; i<=n; i++) {
      printf("'%s'",str_arr[i])
      if (i<n) { printf(" '%s' ",sep_arr[i]) }
    }
    printf("\n\nstrings: ")
    for (i=1; i<=n; i++) {
      printf("'%s' ",str_arr[i])
    }
    printf("\n\nseparators: ")
    for (i=1; i<n; i++) {
      printf("'%s' ",sep_arr[i])
    }
    printf("\n")
    exit(0)
}
Output:
str: a!===b=!=c
sep: (==|!=|=)

parsed: 'a' '!=' '' '==' 'b' '=' '' '!=' 'c'

strings: 'a' '' 'b' '' 'c'

separators: '!=' '==' '=' '!='

BBC BASIC

      DIM sep$(2)
      sep$() = "==", "!=", "="
      PRINT "String splits into:"
      PRINT FNmultisplit("a!===b=!=c", sep$(), FALSE)
      PRINT "For extra credit:"
      PRINT FNmultisplit("a!===b=!=c", sep$(), TRUE)
      END
      
      DEF FNmultisplit(s$, d$(), info%)
      LOCAL d%, i%, j%, m%, p%, o$
      p% = 1
      REPEAT
        m% = LEN(s$)
        FOR i% = 0 TO DIM(d$(),1)
          d% = INSTR(s$, d$(i%), p%)
          IF d% IF d% < m% m% = d% : j% = i%
        NEXT
        IF m% < LEN(s$) THEN
          o$ += """" + MID$(s$, p%, m%-p%) + """"
          IF info% o$ += " (" + d$(j%) + ") " ELSE o$ += ", "
          p% = m% + LEN(d$(j%))
        ENDIF
      UNTIL m% = LEN(s$)
      = o$ + """" + MID$(s$, p%) + """"
Output:
String splits into:
"a", "", "b", "", "c"
For extra credit:
"a" (!=) "" (==) "b" (=) "" (!=) "c"

Bracmat

This is a surprisingly difficult task to solve in Bracmat, because in a naive solution using a alternating pattern ("=="|"!="|"=") the shorter pattern "=" would have precedence over "==". In the solution below the function oneOf iterates (by recursion) over the operators, trying to match the start of the current subject string sjt with one operator at a time, until success or reaching the end of the list with operators, whichever comes first. If no operator is found at the start of the current subject string, the variable nonOp is extended with one byte, thereby shifting the start of the current subject string one byte to the right. Then a new attempt is made to find an operator. This is repeated until either an operator is found, in which case the unparsed string is restricted to the part of the input after the found operator, or no operator is found, in which case the whl loop terminates.

( ( oneOf
  =   operator
    .   !arg:%?operator ?arg
      & ( @(!sjt:!operator ?arg)&(!operator.!arg)
        | oneOf$!arg
        )
  )
& "a!===b=!=c":?unparsed
& "==" "!=" "=":?operators
&   whl
  ' ( @( !unparsed
       : ?nonOp [%(oneOf$!operators:(?operator.?unparsed))
       )
    & put$(!nonOp str$("{" !operator "} "))
    )
& put$!unparsed
& put$\n
);
Output:
a {!=} {==} b {=} {!=} c

C

What kind of silly parsing is this?

#include <stdio.h>
#include <string.h>

void parse_sep(const char *str, const char *const *pat, int len)
{
	int i, slen;
	while (*str != '\0') {
		for (i = 0; i < len || !putchar(*(str++)); i++) {
			slen = strlen(pat[i]);
			if (strncmp(str, pat[i], slen)) continue;
			printf("{%.*s}", slen, str);
			str += slen;
			break;
		}
	}
}

int main()
{
	const char *seps[] = { "==", "!=", "=" };
	parse_sep("a!===b=!=c", seps, 3);

	return 0;
}
Output:
a{!=}{==}b{=}{!=}c

C#

Extra Credit Solution

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Multisplit
{
    internal static class Program
    {
        private static void Main(string[] args)
        {
            foreach (var s in "a!===b=!=c".Multisplit(true, "==", "!=", "=")) // Split the string and return the separators.
            {
                Console.Write(s); // Write the returned substrings and separators to the console.
            }
            Console.WriteLine();
        }

        private static IEnumerable<string> Multisplit(this string s, bool returnSeparators = false,
                                                      params string[] delimiters)
        {
            var currentString = new StringBuilder(); /* Initiate the StringBuilder. This will hold the current string to return
                                                      * once we find a separator. */

            int index = 0; // Initiate the index counter at 0. This tells us our current position in the string to read.

            while (index < s.Length) // Loop through the string.
            {
                // This will get the highest priority separator found at the current index, or null if there are none.
                string foundDelimiter =
                    (from delimiter in delimiters
                     where s.Length >= index + delimiter.Length &&
                           s.Substring(index, delimiter.Length) == delimiter
                     select delimiter).FirstOrDefault();

                if (foundDelimiter != null)
                {
                    yield return currentString.ToString(); // Return the current string.
                    if (returnSeparators) // Return the separator, if the user specified to do so.
                        yield return
                            string.Format("{{\"{0}\", ({1}, {2})}}",
                                          foundDelimiter,
                                          index, index + foundDelimiter.Length);
                    currentString.Clear(); // Clear the current string.
                    index += foundDelimiter.Length; // Move the index past the current separator.
                }
                else
                {
                    currentString.Append(s[index++]); // Add the character at this index to the current string.
                }
            }

            if (currentString.Length > 0)
                yield return currentString.ToString(); // If we have anything left over, return it.
        }
    }
}
Output:
a{"!=", (1, 3)}{"==", (3, 5)}b{"=", (6, 7)}{"!=", (7, 9)}c

C++

using the Boost library tokenizer!

#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>

int main( ) {
   std::string str( "a!===b=!=c" ) , output ;
   typedef boost::tokenizer<boost::char_separator<char> > tokenizer ;
   boost::char_separator<char> separator ( "==" , "!=" ) , sep ( "!" )  ;
   tokenizer mytok( str , separator ) ;
   tokenizer::iterator tok_iter = mytok.begin( ) ;
   for ( ; tok_iter != mytok.end( ) ; ++tok_iter )
      output.append( *tok_iter ) ;
   tokenizer nexttok ( output , sep ) ;
   for ( tok_iter = nexttok.begin( ) ; tok_iter != nexttok.end( ) ;
	 ++tok_iter ) 
      std::cout << *tok_iter << " " ;
   std::cout << '\n' ;
   return 0 ;
}
Output:
a b c

Without external libraries

#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

struct Split_data {
	std::string segment;
	int32_t index;
	std::string separator;
};

std::vector<Split_data> multi_split(const std::string& text, const std::vector<std::string>& separators) {
	std::vector<Split_data> result;
	uint64_t i = 0;
	std::string segment = "";
	while ( i < text.length() ) {
		bool found = false;
		for ( std::string separator : separators ) {
			if ( text.substr(i, separator.length()) == separator ) {
				found = true;
				result.emplace_back(segment, i, separator);
				i += separator.length();
				segment = "";
				break;
			}
		}

		if ( ! found ) {
		  segment += text[i];
		  i += 1;
		}
	}
	result.emplace_back(segment, i, "");
	return result;
}

int main() {
	for ( Split_data splits : multi_split("a!===b=!=c", { "==", "!=", "=" } ) ) {
		std::cout << std::left << std::setw(3) << "\"" + splits.segment + "\""
				  << std::setw(18) << " ( split with \"" + splits.separator + "\""
				  << " at index " << splits.index << " )" << std::endl;
	}
}
Output:
"a" ( split with "!=" at index 1 )
""  ( split with "==" at index 3 )
"b" ( split with "="  at index 6 )
""  ( split with "!=" at index 7 )
"c" ( split with ""   at index 10 )


C++23

/* multisplit.cpp */
#include <features.h>
#include <iostream>
#include <string>
#include <vector>
#include <format>

/* C++23 example for Multisplit  6 Jan 2024
 email:  
      spikeysnack@gmail.com 

 compile:
      g++-13 -std=c++23 -Wall -o multisplit multisplit.cpp
*/

// extra info
#define _EXTRA 

// aliases
using std::string;
using std::vector;
using str_vec = vector<string>;
using std::cout;


// constants
constexpr static const size_t npos = -1;

// function signatures
string replace_all(string& str, string& remove, string& insert );

str_vec split_on_delim(string& str, const string& delims);

str_vec Multisplit( string& input, const str_vec& seps);

// functions 

// replace all substrings in string
//     a = "dogs and cats and dogs and cats and birds"
//     replace(a, "cats" , "fish"); 
//        ==> "dogs and fish and dogs and fish and birds"

string replace_all(string& str,
		   const string& remove,
		   const string& insert ){
 string s{str};
 string::size_type pos = 0;

 #ifdef _EXTRA
   const string rightarrow{"\u2B62"}; //unicode arrow
   auto ex = std::format("match: {}\t{} ", remove, rightarrow);
   std::cerr << ex;
 #endif
    
 while ((pos = s.find(remove, pos)) != npos){
   s.replace(pos, remove.size(), insert);
   pos++;
 }

 return s;
}


// create a string vector from a string,
// split on a delimiter string
//    x = "ab:cde:fgh:ijk"
//   split_on_delim( x, ":");
//      ==> { "ab", "cde", "fgh", "ijk" } 

str_vec split_on_delim(string& str, const string& delims) {
  string::size_type beg, pos = 0;
  str_vec sv;
  string tmp;
  
  while ( (beg = str.find_first_not_of(delims, pos)) != npos ){

    pos = str.find_first_of(delims, beg + 1);

    tmp = { str.substr(beg, pos - beg) };

    sv.push_back(tmp);
    }  
  return sv;
}


str_vec Multisplit( string& input, const str_vec& seps) {

  string s1{input};
  str_vec sv;

  for( auto sep : seps){
      s1 = replace_all(s1, sep, "^"); // space sep

#ifdef _EXTRA
     std::cerr << s1 << "\n";
#endif
      sv = split_on_delim(s1, "^"); // split
    }
  return sv;
}


/* main program */

int main(){
  
  string sample{"a!===b=!=c"};

  const str_vec seps {"!=", "==", "="};

  auto s = std::format("sample: \t{}\n", sample);

  cout << s;

  auto sv = Multisplit(sample, seps);

  for( auto s : sv){
    auto out = std::format( "{}\t" , s);
    cout << out;
  }
  cout << "\n";     
      
  return 0;
}

// end
Output:
sample:         a!===b=!=c
match: !=       ⭢ a^==b=^c
match: ==       ⭢ a^^b=^c
match: =        ⭢ a^^b^^c
a       b       c

CoffeeScript

multi_split = (text, separators) ->
  # Split text up, using separators to break up text and discarding
  # separators.
  #
  # Returns an array of strings, which can include empty strings when
  # separators are found either adjacent to each other or at the
  # beginning/end of the text.
  #
  # Separators have precedence, according to their order in the array,
  # and each separator should be at least one character long.
  result = []
  i = 0
  s = ''
  while i < text.length
    found = false
    for separator in separators
      if text.substring(i, i + separator.length) == separator
        found = true
        i += separator.length
        result.push s
        s = ''
        break
    if !found
      s += text[i]
      i += 1
  result.push s
  result
  
console.log multi_split 'a!===b=!=c', ['==', '!=', '='] # [ 'a', '', 'b', '', 'c' ]
console.log multi_split '', ['whatever'] # [ '' ]

D

import std.stdio, std.array, std.algorithm;

string[] multiSplit(in string s, in string[] divisors) pure nothrow {
    string[] result;
    auto rest = s.idup;

    while (true) {
	    bool done = true;
        string delim;
        {
            string best;
            foreach (const div; divisors) {
                const maybe = rest.find(div);
                if (maybe.length > best.length) {
                    best = maybe;
                    delim = div;
                    done = false;
                }
            }
        }
	    result.length++;
	    if (done) {
            result.back = rest.idup;
		    return result;
	    } else {
            const t = rest.findSplit(delim);
		    result.back = t[0].idup;
		    rest = t[2];
	    }
    }
}

void main() {
    "a!===b=!=c"
    .multiSplit(["==", "!=", "="])
    .join(" {} ")
    .writeln;
}
Output:

(separator locations indicated by braces)

a {}  {} b {}  {} c

Delphi

program Multisplit;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

begin
  write('[');
  for var s in 'a!===b=!=c'.Split(['==', '!=', '=']) do
    write(s.QuotedString('"'), ' ');
  write(']');
  readln;
end.
Output:
["a" "" "b" "" "c" ]

Elixir

Translation of: Erlang
iex(1)> Regex.split(~r/==|!=|=/, "a!====b=!=c")
["a", "", "", "b", "", "c"]

Erlang

20> re:split("a!===b=!=c", "==|!=|=",[{return, list}]).
["a",[],"b",[],"c"]


F#

If we ignore the "Extra Credit" requirements and skip 'ordered separators' condition (i.e. solving absolute different task), this is exactly what one of the overloads of .NET's String.Split method does. Using F# Interactive:

> "a!===b=!=c".Split([|"=="; "!="; "="|], System.StringSplitOptions.None);;
val it : string [] = [|"a"; ""; "b"; ""; "c"|]

> "a!===b=!=c".Split([|"="; "!="; "=="|], System.StringSplitOptions.None);;
val it : string [] = [|"a"; ""; ""; "b"; ""; "c"|]

System.StringSplitOptions.None specifies that empty strings should be included in the result.

Factor

USING: arrays fry kernel make sequences ;

IN: rosetta-code.multisplit

: first-subseq ( seq separators -- n separator )
    tuck
    [ [ subseq-index ] dip 2array ] withd map-index sift-keys
    [ drop f f ] [ [ first ] infimum-by first2 rot nth ] if-empty ;

: multisplit ( string separators -- seq )
    '[
        [ dup _ first-subseq dup ] [
            length -rot cut-slice [ , ] dip swap tail-slice
        ] while 2drop ,
    ] { } make ;
Output:
> "a!===b=!=c" { "==" "!=" "=" } multisplit [ >string ] map .

{ "a" "" "b" "" "c" }

FreeBASIC

FreeBASIC does not have a built in 'split' function so we need to write one:

' FB 1.05.0 Win64

Sub Split(s As String, sepList() As String, result() As String, removeEmpty As Boolean = False, showSepInfo As Boolean = False)
  If s = "" OrElse UBound(sepList) = -1 Then
     Redim result(0)
     result(0) = s
     Return
  End If
  Dim As Integer i = 0, j, count = 0, empty = 0, length 
  Dim As Integer position(len(s) + 1)
  Dim As Integer sepIndex(1 To len(s))
  Dim As Integer sepLength(len(s))
  position(0) = 0 : sepLength(0) = 1
 
  While i  < Len(s)
    For j = lbound(sepList) To ubound(sepList)
      length = len(sepList(j))
      If length = 0 Then Continue For '' ignore blank separators
      If mid(s, i + 1, length) = sepList(j) Then
        count += 1
        position(count) = i + 1
        sepIndex(count) = j
        sepLength(count) = length
        i += length - 1
        Exit For
      End If
    Next j
    i += 1
  Wend

  Redim result(count)
  If count  = 0 Then
    If showSepInfo Then
      Print "No delimiters were found" : Print
    End If 
    result(0) = s
    Return
  End If
  position(count + 1) = len(s) + 1
  
  For i = 1 To count + 1  
    length = position(i) - position(i - 1) - sepLength(i - 1)
    result(i - 1 - empty) = Mid(s, position(i - 1) + sepLength(i - 1), length)
    If removeEmpty AndAlso cbool(length = 0) Then empty += 1
  Next

  If empty > 0 Then Redim Preserve result(count - empty)

  If showSepInfo Then
    Print "The 1-based indices of the delimiters found are : "
    Print
    For x As Integer = 1 To count
      Print "At index"; position(x), sepList(sepIndex(x)) 
    Next
    Print
  End If 
End Sub


Dim s As String = "a!===b=!=c"
Print "The string to be split is : "; s
Print
Dim a() As String '' to hold results
Dim b(1 To 3) As String = {"==", "!=", "="} '' separators to be used in order of priority (highest first)
split s, b(), a(), False, True  '' show separator info
Print "The sub-strings are : "
Print
For i As integer = 0 To ubound(a)
 Print Using "##"; i + 1; 
 Print " : "; a(i)
Next
Print
Print "Press any key to quit"
Sleep
Output:
The 1-based indices of the delimiters found are :

At index 2    !=
At index 4    ==
At index 7    =
At index 8    !=

The sub-strings are :

 1 : a
 2 :
 3 : b
 4 :
 5 : c

Go

package main

import (
    "fmt"
    "strings"
)

func ms(txt string, sep []string) (ans []string) {
    for txt > "" {
        sepMatch := ""
        posMatch := len(txt)
        for _, s := range sep {
            if p := strings.Index(txt, s); p >= 0 && p < posMatch {
                sepMatch = s
                posMatch = p
            }
        }
        ans = append(ans, txt[:posMatch])
        txt = txt[posMatch+len(sepMatch):]
    }
    return
}

func main() {
    fmt.Printf("%q\n", ms("a!===b=!=c", []string{"==", "!=", "="}))
}
Output:
["a" "" "b" "" "c"]

Haskell

import Data.List
  ( genericLength,
    intercalate,
    isPrefixOf,
    stripPrefix,
  )
  
------------------------ MULTISPLIT ----------------------

multisplit :: [String] -> String -> [(String, String, Int)]
multisplit delims = go [] 0
  where
    go acc pos [] = [(acc, [], pos)]
    go acc pos l@(s : sx) =
      case trysplit delims l of
        Nothing -> go (s : acc) (pos + 1) sx
        Just (d, sxx) ->
          (acc, d, pos) :
          go [] (pos + genericLength d) sxx

trysplit :: [String] -> String -> Maybe (String, String)
trysplit delims s =
  case filter (`isPrefixOf` s) delims of
    [] -> Nothing
    (d : _) -> Just (d, (\(Just x) -> x) $ stripPrefix d s)

--------------------------- TEST -------------------------
main :: IO ()
main = do
  let parsed = multisplit ["==", "!=", "="] "a!===b=!=c"
  mapM_
    putStrLn
    [ "split string:",
      intercalate "," $ map (\(a, _, _) -> a) parsed,
      "with [(string, delimiter, offset)]:",
      show parsed
    ]
Output:
split string:
a,,b,,c
with [(string, delimiter, offset)]:
[("a","!=",1),("","==",3),("b","=",6),("","!=",7),("c","",10)]

Or as a fold:

import Data.List (find, isPrefixOf, foldl') --'
import Data.Bool (bool)

multiSplit :: [String] -> String -> [(String, String, Int)]
multiSplit ds s =
  let (ts, ps, o) =
        foldl' --'
          (\(tokens, parts, offset) (c, i) ->
              let inDelim = offset > i
              in maybe
                   (bool (c : tokens) tokens inDelim, parts, offset)
                   (\x -> ([], (tokens, x, i) : parts, i + length x))
                   (bool (find (`isPrefixOf` drop i s) ds) Nothing inDelim))
          ([], [], 0)
          (zip s [0 ..])
  in reverse $ (ts, [], length s) : ps
main :: IO ()
main = print $ multiSplit ["==", "!=", "="] "a!===b=!=c"
Output:
[("a","!=",1),("","==",3),("b","=",6),("","!=",7),("c","",10)]

Icon and Unicon

procedure main()
   s := "a!===b=!=c"
   # just list the tokens
   every writes(multisplit(s,["==", "!=", "="])," ") | write()
   
   # list tokens and indices
   every ((p := "") ||:= t := multisplit(s,sep := ["==", "!=", "="])) | break write() do 
      if t == !sep then writes(t," (",*p+1-*t,") ") else writes(t," ")
      
end

procedure multisplit(s,L)
s ? while not pos(0) do {
   t := =!L | 1( arb(), match(!L)|pos(0) )
   suspend t
   }
end 

procedure arb()
suspend .&subject[.&pos:&pos <- &pos to *&subject + 1]
end
Output:
a != == b = != c
a != (2) == (4) b = (7) != (8) c

J

multisplit=: {{
  'begin sep'=. |:bs=. _,~/:~;(,.&.>i.@#) y I.@E.L:0 x NB. 
  len=. #@>y NB. 
  r=. i.3 0
  j=. k=. 0
  while.j<#x do.
    while. j>k{begin do. k=.k+1 end.
    'b s'=. k{bs NB. character index where separator appears, separator index
    if. _=b do. r,.(j}.x);'';'' return. end.    
    txt=. (j + i. b-j){x
    j=. b+s{len
    r=.r,.txt;(s{::y);b 
  end.
}}

Explanation:

First find all potentially relevant separator instances, and sort them in increasing order, by starting location and separator index. sep is separator index, and begin is starting location.

Then, loop through the possibilities, skipping over those separators which would overlap with previously used separators.

The result consists of three rows: The first row is the extracted substrings, the second and third rows are the "extra credit" part -- for each extracted substring: the following separator and the position in the original string where that separator started. Note that the very last substring does not have a separator following it, so the extra credit part is blank for that substring.

Example use:

   S multisplit '==';'!=';'='
┌──┬──┬─┬──┬─┐
a   b  c
├──┼──┼─┼──┼─┤
!====!= 
├──┼──┼─┼──┼─┤
1 3 67  
└──┴──┴─┴──┴─┘
   S multisplit '=';'!=';'=='
┌──┬─┬─┬─┬──┬─┐
a   b  c
├──┼─┼─┼─┼──┼─┤
!====!= 
├──┼─┼─┼─┼──┼─┤
1 3467  
└──┴─┴─┴─┴──┴─┘
   'X123Y' multisplit '1';'12';'123';'23';'3'
┌─┬──┬─┐
X  Y
├─┼──┼─┤
123 
├─┼──┼─┤
12  
└─┴──┴─┘

Java

import java.util.*;

public class MultiSplit {

    public static void main(String[] args) {
        System.out.println("Regex split:");
        System.out.println(Arrays.toString("a!===b=!=c".split("==|!=|=")));

        System.out.println("\nManual split:");
        for (String s : multiSplit("a!===b=!=c", new String[]{"==", "!=", "="}))
            System.out.printf("\"%s\" ", s);
    }

    static List<String> multiSplit(String txt, String[] separators) {
        List<String> result = new ArrayList<>();
        int txtLen = txt.length(), from = 0;

        for (int to = 0; to < txtLen; to++) {
            for (String sep : separators) {
                int sepLen = sep.length();
                if (txt.regionMatches(to, sep, 0, sepLen)) {
                    result.add(txt.substring(from, to));
                    from = to + sepLen;
                    to = from - 1; // compensate for the increment
                    break;
                }
            }
        }
        if (from < txtLen)
            result.add(txt.substring(from));
        return result;
    }
}
Regex split:
[a, , b, , c]

Manual split:
"a" "" "b" "" "c"

JavaScript

ES5

Based on Ruby example.

Library: Underscore.js
RegExp.escape = function(text) {
    return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}

multisplit = function(string, seps) {
    var sep_regex = RegExp(_.map(seps, function(sep) { return RegExp.escape(sep); }).join('|'));
    return string.split(sep_regex);
}

ES6

Without importing an external library, and generalizing to allow for any set of delimiters (avoiding the need for a hand-crafted regex):

Translation of: Haskell

(Multisplit by fold example)

(() => {

    /// Delimiter list -> String -> list of parts, delimiters, offsets

    // multiSplit :: [String] -> String ->
    //      [{part::String, delim::String, offset::Int}]
    const multiSplit = (ds, s) => {
        const
            dcs = map(chars, ds),
            xs = chars(s),
            dct = foldl(
                (a, c, i, s) => {
                    const
                        inDelim = a.offset > i,
                        mb = inDelim ? (
                            nothing('')
                        ) : find(d => isPrefixOf(d, drop(i, xs)), dcs);
                    return mb.nothing ? {
                        tokens: a.tokens.concat(inDelim ? (
                            []
                        ) : [c]),
                        parts: a.parts,
                        offset: a.offset
                    } : {
                        tokens: [],
                        parts: append(a.parts, [{
                            part: intercalate('', a.tokens),
                            delim: intercalate('', mb.just),
                            offset: i
                        }]),
                        offset: i + length(mb.just)
                    };
                }, {
                    tokens: [],
                    parts: [],
                    offset: 0
                }, xs
            );
        return append(dct.parts, [{
            part: intercalate('', dct.tokens),
            delim: "",
            offset: length(s)
        }]);
    };

    // GENERIC FUNCTIONS -----------------------------------------------------

    // append (++) :: [a] -> [a] -> [a]
    const append = (xs, ys) => xs.concat(ys);

    // chars :: String -> [Char]
    const chars = s => s.split('');

    // drop :: Int -> [a] -> [a]
    // drop :: Int -> String -> String
    const drop = (n, xs) => xs.slice(n);

    // find :: (a -> Bool) -> [a] -> Maybe a
    const find = (p, xs) => {
        for (var i = 0, lng = xs.length; i < lng; i++) {
            var x = xs[i];
            if (p(x)) return just(x);
        }
        return nothing('Not found');
    };

    // foldl :: (a -> b -> a) -> a -> [b] -> a
    const foldl = (f, a, xs) => xs.reduce(f, a);

    // intercalate :: String -> [String] -> String
    const intercalate = (s, xs) => xs.join(s);

    // isPrefixOf takes two lists or strings and returns
    // true iff the first is a prefix of the second.
    // isPrefixOf :: [a] -> [a] -> Bool
    // isPrefixOf :: String -> String -> Bool
    const isPrefixOf = (xs, ys) => {
        const pfx = (xs, ys) => xs.length ? (
            ys.length ? xs[0] === ys[0] && pfx(
                xs.slice(1), ys.slice(1)
            ) : false
        ) : true;
        return typeof xs !== 'string' ? pfx(xs, ys) : ys.startsWith(xs);
    };

    // just :: a -> Just a
    const just = x => ({
        nothing: false,
        just: x
    });

    // length :: [a] -> Int
    const length = xs => xs.length;

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // nothing :: () -> Nothing
    const nothing = (optionalMsg) => ({
        nothing: true,
        msg: optionalMsg
    });

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // TEST ------------------------------------------------------------------
    const
        strTest = 'a!===b=!=c',
        delims = ['==', '!=', '='];

    return show(2,
        multiSplit(delims, strTest)
    );
})();
Output:
[
  {
    "part": "a",
    "delim": "!=",
    "offset": 1
  },
  {
    "part": "",
    "delim": "==",
    "offset": 3
  },
  {
    "part": "b",
    "delim": "=",
    "offset": 6
  },
  {
    "part": "",
    "delim": "!=",
    "offset": 7
  },
  {
    "part": "c",
    "delim": "",
    "offset": 10
  }
]

jq

Works with: jq version 1.4

This version does not use regular expressions, which are only supported in later versions of jq.

multisplit(delims) produces the desired parse using an intermediate parse produced by multisplit_parse(delims).

Both helper functions could be made inner functions of the main function, but are kept separate here for clarity.

# peeloff(delims) either peels off a delimiter or 
# a single character from the input string.
# The input should be a nonempty string, and delims should be
# a non-empty array of delimiters;
# return [peeledoff, remainder]
# where "peeledoff" is either [delim] or the peeled off character:
def peeloff(delims):
  delims[0] as $delim
  | if startswith($delim) then [ [$delim], .[ ($delim|length):]]
    elif (delims|length)>1 then peeloff(delims[1:])
    else [ .[0:1], .[1:]]
    end ;

# multisplit_parse(delims) produces an intermediate parse.
# Input must be of the parse form: [ string, [ delim ], ... ]
# Output is of the same form.
def multisplit_parse(delims):
  if (delims|length) == 0 or length == 0 then .
  else
    .[length-1] as $last
    |  .[0:length-1] as $butlast
    | if ($last|type) == "array" then . # all done
      elif $last == "" then .
      else
        ($last | peeloff(delims)) as $p # [ peeledoff, next ]
        | $p[0] as $peeledoff
        | $p[1] as $next
        | if ($next|length) > 0
          then $butlast + [$peeledoff] + ([$next]|multisplit_parse(delims))
          else $butlast + $p
          end
      end
  end ;

def multisplit(delims):
  [.] | multisplit_parse(delims)
  # insert "" between delimiters, compress strings, remove trailing "" if any
  | reduce .[] as $x ([]; 
      if length == 0 then [ $x ] 
      elif ($x|type) == "array"
      then if (.[length-1]|type) == "array" then . + ["",  $x]
           else  . + [$x]
           end
      elif .[length-1]|type == "string"
      then .[0:length-1] + [ .[length-1] + $x ]
      else  . + [$x]
      end ) ;

Examples

("a!===b=!=c",
 "aaa!===bbb=!=ccc") | multisplit( ["==", "!=", "="] )
Output:
$ jq -n -M -c -f multisplit.jq
["a",["!="],"",["=="],"b",["="],"",["!="],"c"]
["aaa",["!="],"",["=="],"bbb",["="],"",["!="],"ccc"]

Julia

From REPL:

julia> split(s, r"==|!=|=")
 5-element Array{SubString{String},1}:
  "a"
  ""
  "b"
  ""
  "c"

Kotlin

// version 1.0.6

fun main(args: Array<String>) {
    val input = "a!===b=!=c"
    val delimiters = arrayOf("==", "!=", "=")
    val output = input.split(*delimiters).toMutableList()
    for (i in 0 until output.size) {
        if (output[i].isEmpty()) output[i] = "empty string"
        else output[i] = "\"" + output[i] + "\""
    }
    println("The splits are:")         
    println(output)

    // now find positions of matched delimiters
    val matches = mutableListOf<Pair<String, Int>>()
    var index = 0 
    while (index < input.length) {
        var matched = false
        for (d in delimiters) {
            if (input.drop(index).take(d.length) == d) {
                matches.add(d to index)
                index += d.length
                matched = true
                break
            }
        }
        if (!matched) index++
    }
    println("\nThe delimiters matched and the indices at which they occur are:")
    println(matches)
}
Output:
The splits are:
["a", empty string, "b", empty string, "c"]

The delimiters matched and the indices at which they occur are:
[(!=, 1), (==, 3), (=, 6), (!=, 7)]

Lua

The function I've written here is really excessive for this task but it has historically been hard to find example code for a good Lua split function on the Internet. This one behaves the same way as Julia's Base.split and I've included a comment describing its precise operation.

--[[
Returns a table of substrings by splitting the given string on 
occurrences of the given character delimiters, which may be specified 
as a single- or multi-character string or a table of such strings.
If chars is omitted, it defaults to the set of all space characters, 
and keep is taken to be false. The limit and keep arguments are 
optional: they are a maximum size for the result and a flag 
determining whether empty fields should be kept in the result.
]]
function split (str, chars, limit, keep)
    local limit, splitTable, entry, pos, match = limit or 0, {}, "", 1
    if keep == nil then keep = true end
    if not chars then
        for e in string.gmatch(str, "%S+") do
                table.insert(splitTable, e)
        end
        return splitTable
    end
    while pos <= str:len() do
        match = nil
        if type(chars) == "table" then
            for _, delim in pairs(chars) do
                if str:sub(pos, pos + delim:len() - 1) == delim then
                    match = string.len(delim) - 1
                    break
                end
            end
        elseif str:sub(pos, pos + chars:len() - 1) == chars then
            match = string.len(chars) - 1
        end
        if match then
            if not (keep == false and entry == "") then
                table.insert(splitTable, entry)
                if #splitTable == limit then return splitTable end
                entry = ""
            end
        else
            entry = entry .. str:sub(pos, pos)
        end
        pos = pos + 1 + (match or 0)
    end
    if entry ~= "" then table.insert(splitTable, entry) end
    return splitTable
end

local multisplit = split("a!===b=!=c", {"==", "!=", "="})

-- Returned result is a table (key/value pairs) - display all entries
print("Key\tValue")
print("---\t-----")
for k, v in pairs(multisplit) do
    print(k, v)
end
Output:
Key     Value
---     -----
1       a
2
3       b
4
5       c

M2000 Interpreter

Code from BBC BASIC with little changes to fit in M2000.

Module CheckIt {
            DIM sep$()
            sep$() = ("==", "!=", "=")
            PRINT "String splits into:"
            FNmultisplit("a!===b=!=c", sep$(), FALSE)
            PRINT "For extra credit:"
            FNmultisplit("a!===b=!=c", sep$(), TRUE)
            END
       
            SUB FNmultisplit(s$, d$(), info%)
            LOCAL d%, i%, j%, m%, p%, o$
            p% = 1
            REPEAT {
                    m% = LEN(s$)
                    FOR i% = 0 TO DIMENSION(d$(),1)-1
                      d% = INSTR(s$, d$(i%), p%)
                      IF d% THEN IF d% < m% THEN m% = d% : j% = i%
                    NEXT I%
                    IF m% < LEN(s$) THEN {
                            o$ += """" + MID$(s$, p%, m%-p%) + """"
                            IF info% THEN  {o$ += " (" + d$(j%) + ") "} ELSE o$ += ", "
                            p% = m% + LEN(d$(j%))
                  }
                   
            } UNTIL m% = LEN(s$)
            PRINT  o$ + """" + MID$(s$, p%) + """"
            END SUB
}
CheckIt

Mathematica/Wolfram Language

Just use the built-in function "StringSplit":

StringSplit["a!===b=!=c", {"==", "!=", "="}]
Output:
{a,,b,,c}

MiniScript

parseSep = function(s, pats)
    result = []
    startPos = 0
    pos = 0
    while pos < s.len
        for pat in pats
            if s[pos : pos+pat.len] != pat then continue
            result.push s[startPos : pos]
            result.push "{" + pat + "}"
            startPos = pos + pat.len
            pos = startPos - 1
            break
        end for
        pos = pos + 1
    end while
    return result
end function

print parseSep("a!===b=!=c", ["==", "!=", "="])
Output:
["a", "{!=}", "", "{==}", "b", "{=}", "", "{!=}"]

Nim

import strutils
 
iterator tokenize(text: string; sep: openArray[string]): tuple[token: string, isSep: bool] =
  var i, lastMatch = 0
  while i < text.len:
    for j, s in sep:
      if text[i..text.high].startsWith s:
        if i > lastMatch: yield (text[lastMatch ..< i], false)
        yield (s, true)
        lastMatch = i + s.len
        i += s.high
        break
    inc i
  if i > lastMatch: yield (text[lastMatch ..< i], false)
 
for token, isSep in "a!===b=!=c".tokenize(["==", "!=", "="]):
  if isSep: stdout.write '{',token,'}'
  else:     stdout.write     token
echo ""
Output:
a{!=}{==}b{=}{!=}c

Perl

sub multisplit {
   my ($sep, $string, %opt) = @_ ;
   $sep = join '|', map quotemeta($_), @$sep;
   $sep = "($sep)" if $opt{keep_separators};
   split /$sep/, $string, -1;
}

print "'$_' " for multisplit ['==','!=','='], "a!===b=!=c"; 
print "\n";
print "'$_' " for multisplit ['==','!=','='], "a!===b=!=c", keep_separators => 1;
print "\n";
Output:
'a' '' 'b' '' 'c' 
'a' '!=' '' '==' 'b' '=' '' '!=' 'c' 

Phix

with javascript_semantics
procedure multisplit(string text, sequence delims)
    integer k = 1, kdx
    while true do
        integer kmin = 0
        for i=1 to length(delims) do
            integer ki = match(delims[i],text,k)
            if ki!=0 then
                if kmin=0 or ki<kmin then
                    kmin = ki
                    kdx = i
                end if
            end if
        end for
        string token = text[k..kmin-1],
               delim = iff(kmin=0?"":sprintf(", delimiter (%s) at %d",{delims[kdx],kmin}))
        printf(1,"Token: [%s] at %d%s\n",{token,k,delim})
        if kmin=0 then exit end if
        k = kmin+length(delims[kdx])
    end while
end procedure
 
multisplit("a!===b=!=c",{"==","!=","="})
Output:
Token: [a] at 1, delimiter (!=) at 2
Token: [] at 4, delimiter (==) at 4
Token: [b] at 6, delimiter (=) at 7
Token: [] at 8, delimiter (!=) at 8
Token: [c] at 10

PicoLisp

(de multisplit (Str Sep)
   (setq Sep (mapcar chop Sep))
   (make
      (for (S (chop Str) S)
         (let L
            (make
               (loop
                  (T (find head Sep (circ S))
                     (link
                        (list
                           (- (length Str) (length S))
                           (pack (cut (length @) 'S)) ) ) )
                  (link (pop 'S))
                  (NIL S (link NIL)) ) )
            (link (pack (cdr (rot L))))
            (and (car L) (link @)) ) ) ) )

(println (multisplit "a!===b=!=c" '("==" "!=" "=")))
(println (multisplit "a!===b=!=c" '("=" "!=" "==")))
Output:
("a" (1 "!=") NIL (3 "==") "b" (6 "=") NIL (7 "!=") "c")
("a" (1 "!=") NIL (3 "=") NIL (4 "=") "b" (6 "=") NIL (7 "!=") "c")

Pike

string input = "a!===b=!=c";
array sep = ({"==", "!=", "=" });

array result = replace(input, sep, `+("\0", sep[*], "\0"))/"\0";
result;
Result: ({ "a", "!=", "", "==", "b", "=", "", "!=", "c" })

int pos = 0; 
foreach(result; int index; string data)
{ 
    if ((<"==", "!=", "=">)[data])
        result[index] = ({ data, pos });
    pos+=sizeof(data);
}

result;
Result: ({"a", ({"!=", 1}), "", ({"==", 3}), "b", ({"=", 6}), "", ({"!=", 7}), "c"})

PowerShell

$string = "a!===b=!=c"
$separators = [regex]"(==|!=|=)"

$matchInfo = $separators.Matches($string) |
    Select-Object -Property Index, Value |
    Group-Object  -Property Value |
    Select-Object -Property @{Name="Separator"; Expression={$_.Name}},
                            Count,
                            @{Name="Position" ; Expression={$_.Group.Index}}

$matchInfo
Output:
Separator Count Position
--------- ----- --------
!=            2 {1, 7}  
==            1 3       
=             1 6       

Prolog

Works with SWI-Prolog.

multisplit(_LSep, '') -->
	{!},
	[].

multisplit(LSep, T) -->
	{next_sep(LSep, T, [], Token, Sep, T1)},
	(   {Token \= '' },[Token], {!}; []),
	(   {Sep \= '' },[Sep], {!}; []),
	multisplit(LSep, T1).

next_sep([], T, Lst, Token, Sep, T1) :-
	% if we can't find any separator, the game is over
	(   Lst = [] ->
	Token = T, Sep = '', T1 = '';

	% we sort the list to get nearest longest separator 
	predsort(my_sort, Lst, [(_,_, Sep)|_]),
	atomic_list_concat([Token|_], Sep, T),
	atom_concat(Token, Sep, Tmp),
	atom_concat(Tmp, T1, T)).

next_sep([HSep|TSep], T, Lst, Token, Sep, T1) :-
	sub_atom(T, Before, Len, _, HSep),
	next_sep(TSep, T, [(Before, Len,HSep) | Lst], Token, Sep, T1).

next_sep([_HSep|TSep], T, Lst, Token, Sep, T1) :-
	next_sep(TSep, T, Lst, Token, Sep, T1).


my_sort(<, (N1, _, _), (N2, _, _)) :-
	N1 < N2.

my_sort(>, (N1, _, _), (N2, _, _)) :-
	N1 > N2.

my_sort(>, (N, N1, _), (N, N2, _)) :-
	N1 < N2.

my_sort(<, (N, N1, _), (N, N2, _)) :-
	N1 > N2.
Output:
?- multisplit(['==', '!=', '='], 'ax!===b=!=c', Lst, []).
Lst = [ax,'!=',==,b,=,'!=',c] .

Python

Procedural

Using regular expressions:

>>> import re
>>> def ms2(txt="a!===b=!=c", sep=["==", "!=", "="]):
	if not txt or not sep:
		return []
	ans = m = []
	for m in re.finditer('(.*?)(?:' + '|'.join('('+re.escape(s)+')' for s in sep) + ')', txt):
		ans += [m.group(1), (m.lastindex-2, m.start(m.lastindex))]
	if m and txt[m.end(m.lastindex):]:
		ans += [txt[m.end(m.lastindex):]]
	return ans

>>> ms2()
['a', (1, 1), '', (0, 3), 'b', (2, 6), '', (1, 7), 'c']
>>> ms2(txt="a!===b=!=c", sep=["=", "!=", "=="])
['a', (1, 1), '', (0, 3), '', (0, 4), 'b', (0, 6), '', (1, 7), 'c']

Not using regular expressions: Inspired by C-version

def multisplit(text, sep):
    lastmatch = i = 0
    matches = []
    while i < len(text):
        for j, s in enumerate(sep):
            if text[i:].startswith(s):
                if i > lastmatch:
                    matches.append(text[lastmatch:i])
                matches.append((j, i))  # Replace the string containing the matched separator with a tuple of which separator and where in the string the match occured
                lastmatch = i + len(s)
                i += len(s)
                break
        else:
            i += 1
    if i > lastmatch:
        matches.append(text[lastmatch:i])
    return matches

>>> multisplit('a!===b=!=c', ['==', '!=', '='])
['a', (1, 1), (0, 3), 'b', (2, 6), (1, 7), 'c']
>>> multisplit('a!===b=!=c', ['!=', '==', '='])
['a', (0, 1), (1, 3), 'b', (2, 6), (0, 7), 'c']

Alternative version

def min_pos(List):
	return List.index(min(List))

def find_all(S, Sub, Start = 0, End = -1, IsOverlapped = 0):
	Res = []
	if End == -1:
		End = len(S)
	if IsOverlapped:
		DeltaPos = 1
	else:
		DeltaPos = len(Sub)
	Pos = Start
	while True:
		Pos = S.find(Sub, Pos, End)
		if Pos == -1:
			break
		Res.append(Pos)
		Pos += DeltaPos
	return Res

def multisplit(S, SepList):
	SepPosListList = []
	SLen = len(S)
	SepNumList = []
	ListCount = 0
	for i, Sep in enumerate(SepList):
		SepPosList = find_all(S, Sep, 0, SLen, IsOverlapped = 1)
		if SepPosList != []:
			SepNumList.append(i)
			SepPosListList.append(SepPosList)
			ListCount += 1
	if ListCount == 0:
		return [S]
	MinPosList = []
	for i in range(ListCount):
		MinPosList.append(SepPosListList[i][0])
	SepEnd = 0
	MinPosPos = min_pos(MinPosList)
	Res = []
	while True:
		Res.append( S[SepEnd : MinPosList[MinPosPos]] )
		Res.append([SepNumList[MinPosPos], MinPosList[MinPosPos]])
		SepEnd = MinPosList[MinPosPos] + len(SepList[SepNumList[MinPosPos]])
		while True:
			MinPosPos = min_pos(MinPosList)
			if MinPosList[MinPosPos] < SepEnd:
				del SepPosListList[MinPosPos][0]
				if len(SepPosListList[MinPosPos]) == 0:
					del SepPosListList[MinPosPos]
					del MinPosList[MinPosPos]
					del SepNumList[MinPosPos]
					ListCount -= 1
					if ListCount == 0:
						break
				else:
					MinPosList[MinPosPos] = SepPosListList[MinPosPos][0]
			else:
				break
		if ListCount == 0:
			break
	Res.append(S[SepEnd:])
	return Res


S = "a!===b=!=c"
multisplit(S, ["==", "!=", "="]) # output: ['a', [1, 1], '', [0, 3], 'b', [2, 6], '', [1, 7], 'c']
multisplit(S, ["=", "!=", "=="]) # output: ['a', [1, 1], '', [0, 3], '', [0, 4], 'b', [0, 6], '', [1, 7], 'c']

Functional

In terms of a fold (reduce), without use of regular expressions:

Works with: Python version 3.7
'''Multisplit'''


from functools import reduce


# multiSplit :: [String] -> String -> [(String, String, Int)]
def multiSplit(separators):
    '''List of triples:
       [(token, separator, start index of separator].
    '''
    def go(s):
        def f(tokensPartsOffset, ic):
            tokens, parts, offset = tokensPartsOffset
            i, c = ic
            inDelim = offset > i
            return maybe(
                (
                    tokens if inDelim
                    else c + tokens, parts, offset
                )
            )(
                lambda x: (
                    '',
                    [(tokens, x, i)] + parts,
                    i + len(x)
                )
            )(
                None if inDelim else find(
                    s[i:].startswith
                )(separators)
            )
        ts, ps, _ = reduce(f, enumerate(s), ('', [], 0))
        return list(reversed(ps)) + [(ts, '', len(s))]
    return go


# ------------------------- TEST -------------------------
# main :: IO ()
def main():
    '''String split on three successive separators.'''
    print(
        multiSplit(['==', '!=', '='])(
            'a!===b=!=c'
        )
    )


# ------------------ GENERIC FUNCTIONS -------------------

# find :: (a -> Bool) -> [a] -> (a | None)
def find(p):
    '''Just the first element in the list that matches p,
       or None if no elements match.
    '''
    def go(xs):
        try:
            return next(x for x in xs if p(x))
        except StopIteration:
            return None
    return go


# maybe :: b -> (a -> b) -> (a | None) -> b
def maybe(v):
    '''Either the default value v, if m is None,
       or the application of f to x.
    '''
    return lambda f: lambda m: v if (
        None is m
    ) else f(m)


# MAIN ---
if __name__ == '__main__':
    main()
Output:
[('a', '!=', 1), ('', '==', 3), ('b', '=', 6), ('', '!=', 7), ('c', '', 10)]

Racket

#lang racket
(regexp-match* #rx"==|!=|=" "a!===b=!=c" #:gap-select? #t #:match-select values)
;; => '("a" ("!=") "" ("==") "b" ("=") "" ("!=") "c")

Raku

(formerly Perl 6)

sub multisplit($str, @seps) { $str.split: / ||@seps /, :v }

my @chunks = multisplit 'a!===b=!=c==d', < == != = >;

# Print the strings.
say @chunks».Str.raku;

# Print the positions of the separators.
for grep Match, @chunks -> $s {
    say "{$s.fmt: '%2s'} from {$s.from.fmt: '%2d'} to {$s.to.fmt: '%2d'}";
}
Output:
("a", "!=", "", "==", "b", "=", "", "!=", "c", "==", "d")
!= from  1 to  3
== from  3 to  5
 = from  6 to  7
!= from  7 to  9
== from 10 to 12

Using the array @seps in a pattern automatically does alternation. By default this would do longest-term matching (that is, | semantics), but we can force it to do left-to-right matching by embedding the array in a short-circuit alternation (that is, || semantics). As it happens, with the task's specified list of separators, it doesn't make any difference.

Raku automatically returns Match objects that will stringify to the matched pattern, but can also be interrogated for their match positions, as illustrated above by post-processing the results two different ways.

REXX

/*REXX program  splits  a (character) string  based on different  separator  delimiters.*/
parse arg $                                      /*obtain optional string from the C.L. */
if $=''   then $= "a!===b=!=c"                   /*None specified?  Then use the default*/
say 'old string:' $                              /*display the old string to the screen.*/
null= '0'x                                       /*null char.   It can be most anything.*/
seps= '== != ='                                  /*list of separator strings to be used.*/
                                                 /* [↓]   process the tokens in  SEPS.  */
  do j=1  for words(seps)                        /*parse the string with all the seps.  */
  sep=word(seps,j)                               /*pick a separator to use in below code*/
                                                 /* [↓]   process characters in the sep.*/
        do k=1  for length(sep)                  /*parse for various separator versions.*/
        sep=strip(insert(null, sep, k), , null)  /*allow imbedded "nulls" in separator, */
        $=changestr(sep, $, null)                /*       ··· but not trailing "nulls". */
                                                 /* [↓]   process strings in the input. */
             do  until $==old;      old=$        /*keep changing until no more changes. */
             $=changestr(null || null, $, null)  /*reduce replicated "nulls" in string. */
             end   /*until*/
                                                 /* [↓]  use  BIF  or  external program.*/
        sep=changestr(null, sep, '')             /*remove true nulls from the separator.*/
        end        /*k*/
  end              /*j*/

showNull= ' {} '                                 /*just one more thing, display the ··· */
$=changestr(null, $, showNull)                   /*        ··· showing of "null" chars. */
say 'new string:' $                              /*now, display the new string to term. */
                                                 /*stick a fork in it,  we're all done. */

Some older REXXes don't have a   changestr   BIF, so one is included here   ──►   CHANGESTR.REX.

output   when using the default input:

old string=a!===b=!=c
new string=a {} b {} c

Ring

# Project : Multisplit

str = "a!===b=!=c" 
sep = "=== != =! b =!="
sep = str2list(substr(sep, " ", nl))
for n = 1 to len(sep)
      pos = substr(str, sep[n])
      see "" + n + ": " + substr(str, 1, pos-1) + " Sep By: " + sep[n] + nl
next

Output:

1: a! Sep By: ===
2: a Sep By: !=
3: a!===b Sep By: =!
4: a!=== Sep By: b
5: a!===b Sep By: =!=

Ruby

The simple method, using a regular expression to split the text.

text = 'a!===b=!=c'
separators = ['==', '!=', '=']

def multisplit_simple(text, separators)
  text.split(Regexp.union(separators))
end

p multisplit_simple(text, separators) # => ["a", "", "b", "", "c"]

The version that also returns the information about the separations.

def multisplit(text, separators)
  sep_regex = Regexp.union(separators)
  separator_info = []
  pieces = []
  i = prev = 0
  while i = text.index(sep_regex, i)
    separator = Regexp.last_match(0)
    pieces << text[prev .. i-1]
    separator_info << [separator, i]
    i = i + separator.length
    prev = i
  end
  pieces << text[prev .. -1]
  [pieces, separator_info]
end

p multisplit(text, separators)
# => [["a", "", "b", "", "c"], [["!=", 1], ["==", 3], ["=", 6], ["!=", 7]]]

Also demonstrating a method to rejoin the string given the separator information.

def multisplit_rejoin(info)
  str = info[0].zip(info[1])[0..-2].inject("") {|str, (piece, (sep, idx))| str << piece << sep} 
  str << info[0].last
end

p multisplit_rejoin(multisplit(text, separators)) == text
# => true

Run BASIC

str$ = "a!===b=!=c" 
sep$ = "=== != =! b =!="

while word$(sep$,i+1," ") <> ""
 i = i + 1
 theSep$ = word$(sep$,i," ")
 split$  = word$(str$,1,theSep$)
 print i;" ";split$;" Sep By: ";theSep$
wend
Output:
1 a!     Sep By: ===
2 a      Sep By: !=
3 a!===b Sep By: =!
4 a!===  Sep By: b
5 a!===b Sep By: =!=

Scala

import scala.annotation.tailrec
def multiSplit(str:String, sep:Seq[String])={
   def findSep(index:Int)=sep find (str startsWith (_, index))
   @tailrec def nextSep(index:Int):(Int,Int)=
      if(index>str.size) (index, 0) else findSep(index) match {
         case Some(sep) => (index, sep.size)
         case _ => nextSep(index + 1)
      }
   def getParts(start:Int, pos:(Int,Int)):List[String]={
      val part=str slice (start, pos._1)
      if(pos._2==0) List(part) else part :: getParts(pos._1+pos._2, nextSep(pos._1+pos._2))
   }
   getParts(0, nextSep(0))
}

println(multiSplit("a!===b=!=c", Seq("!=", "==", "=")))
Output:
List(a, , b, , c)

Scheme

Works with: Gauche Scheme
(use srfi-13)
(use srfi-42)

(define (shatter separators the-string)
  (let loop ((str the-string) (tmp ""))
    (if (string=? "" str)
      (list tmp)
      (if-let1 sep (find (^s (string-prefix? s str)) separators)
        (cons* tmp sep
          (loop (string-drop str (string-length sep)) ""))
        (loop (string-drop str 1) (string-append tmp (string-take str 1)))))))

(define (glean shards)
  (list-ec (: x (index i) shards)
    (if (even? i)) x))

Testing:

(glean (shatter '("==" "!=" "=") "a!===b=!=c"))
("a" "" "b" "" "c")

(shatter '("==" "!=" "=") "a!===b=!=c")
("a" "!=" "" "==" "b" "=" "" "!=" "c")

SenseTalk

First approach, using line delimiters. Lines are delimited by an array of separator strings, normally [CRLF, LF, CR, lineSeparator(0x2028), paragraphSeparator(0x2029)]. Supplying an alternate set of delimiters lets us split a string by a different (ordered) set of strings:

set source to "a!===b=!=c"
set separators to ["==", "!=", "="]

put each line delimited by separators of source

Output:

(a,,b,,c)

Second approach, using a pattern. SenseTalk's pattern language lets us define a pattern (a regex) which can then be used to split the string and also to display the actual separators that were found.

set source to "a!===b=!=c"
set separatorPattern to <"==" or "!=" or "=">

put source split by separatorPattern

put each occurrence of separatorPattern in source

Output:

(a,,b,,c)
(!=,==,=,!=)

Sidef

func multisplit(sep, str, keep_sep=false) {
    sep = sep.map{.escape}.join('|');
    var re = Regex.new(keep_sep ? "(#{sep})" : sep);
    str.split(re, -1);
}

[false, true].each { |bool|
    say multisplit(%w(== != =), 'a!===b=!=c', keep_sep: bool);
}
Output:
["a", "", "b", "", "c"]
["a", "!=", "", "==", "b", "=", "", "!=", "c"]

Swift

Swift strings are purposefully not index by integers to avoid confusion and performance traps when dealing with unicode. As such the indexes returned by this method are not very helpful to a human reader, but can be used to manipulate the original string.

Translation of: Python
extension String {
  func multiSplit(on seps: [String]) -> ([Substring], [(String, (start: String.Index, end: String.Index))]) {
    var matches = [Substring]()
    var matched = [(String, (String.Index, String.Index))]()
    var i = startIndex
    var lastMatch = startIndex

    main: while i != endIndex {
      for sep in seps where self[i...].hasPrefix(sep) {
        if i > lastMatch {
          matches.append(self[lastMatch..<i])
        } else {
          matches.append("")
        }

        lastMatch = index(i, offsetBy: sep.count)
        matched.append((sep, (i, lastMatch)))
        i = lastMatch

        continue main
      }

      i = index(i, offsetBy: 1)
    }

    if i > lastMatch {
      matches.append(self[lastMatch..<i])
    }

    return (matches, matched)
  }
}

let (matches, matchedSeps) = "a!===b=!=c".multiSplit(on: ["==", "!=", "="])

print(matches, matchedSeps.map({ $0.0 }))


Output:
["a", "", "b", "", "c"] ["!=", "==", "=", "!="]

Tcl

This simple version does not retain information about what the separators were:

proc simplemultisplit {text sep} {
    set map {}; foreach s $sep {lappend map $s "\uffff"}
    return [split [string map $map $text] "\uffff"]
}
puts [simplemultisplit "a!===b=!=c" {"==" "!=" "="}]
Output:
a {} b {} c

However, to keep the match information a more sophisticated technique is best. Note that the most natural model of result here is to return the split substrings as a separate list to the match information (because the two collections of information are of different lengths).

proc multisplit {text sep} {
    foreach s $sep {lappend sr [regsub -all {\W} $s {\\&}]}
    set sepRE [join $sr "|"]
    set pieces {}
    set match {}
    set start 0
    while {[regexp -indices -start $start -- $sepRE $text found]} {
	lassign $found x y
	lappend pieces [string range $text $start [expr {$x-1}]]
	lappend match [lsearch -exact $sep [string range $text {*}$found]] $x
	set start [expr {$y + 1}]
    }
    return [list [lappend pieces [string range $text $start end]] $match]
}

Demonstration code:

set input "a!===b=!=c"
set matchers {"==" "!=" "="}
lassign [multisplit $input $matchers] substrings matchinfo
puts $substrings
puts $matchinfo
Output:
a {} b {} c
1 1 0 3 2 6 1 7

TXR

Using text-extraction pattern language

Here, the separators are embedded into the syntax rather than appearing as a datum. Nevertheless, this illustrates how to do that small tokenizing task with various separators.

The clauses of choose are applied in parallel, and all potentially match at the current position in the text. However :shortest tok means that only that clause survives (gets to propagate its bindings and position advancement) which minimizes the length of the string which is bound to the tok variable. The :gap 0 makes the horizontal collect repetitions strictly adjacent. This means that coll will quit when faced with a nonmatching suffix portion of the data rather than scan forward (no gap allowed!). This creates an opportunity for the tail variable to grab the suffix which remains, which may be an empty string.

@(next :args)
@(coll :gap 0)@(choose :shortest tok)@\
                @tok@{sep /==/}@\
              @(or)@\
                @tok@{sep /!=/}@\
              @(or)@\
                @tok@{sep /=/}@\
              @(end)@(end)@tail
@(output)
@(rep)"@tok" {@sep} @(end)"@tail"
@(end)

Runs:

$ ./txr multisplit.txr 'a!===b=!=c'
"a" {!=} "" {==} "b" {=} "" {!=} "c"
$ ./txr  multisplit.txr 'a!===!==!=!==b'
"a" {!=} "" {==} "" {!=} "" {=} "" {!=} "" {!=} "" {=} "b"
$ ./txr  multisplit.txr ''
""
$ ./txr  multisplit.txr 'a'
"a"
$ ./txr  multisplit.txr 'a='
"a" {=} ""
$ ./txr  multisplit.txr '='
"" {=} ""
$ ./txr  multisplit.txr '=='
"" {==} ""
$ ./txr  multisplit.txr '==='
"" {==} "" {=} ""

Using the tok-str function

Translation of: Racket
$ txr -p '(tok-str "a!===b=!=c" #/==|!=|=/ t)'
("a" "!=" "" "==" "b" "=" "" "!=" "c")

Here the third boolean argument means "keep the material between the tokens", which in the Racket version seems to be requested by the argument #:gap-select? #:t.

UNIX Shell

Works with: bash
multisplit() {
    local str=$1
    shift
    local regex=$( IFS='|'; echo "$*" )
    local sep 
    while [[ $str =~ $regex ]]; do 
        sep=${BASH_REMATCH[0]}
        words+=( "${str%%${sep}*}" )
        seps+=( "$sep" )
        str=${str#*$sep}
    done
    words+=( "$str" )
}

words=() seps=()

original="a!===b=!=c"
recreated=""

multisplit "$original" "==" "!=" "="

for ((i=0; i<${#words[@]}; i++)); do
    printf 'w:"%s"\ts:"%s"\n' "${words[i]}" "${seps[i]}"
    recreated+="${words[i]}${seps[i]}"
done

if [[ $original == $recreated ]]; then
    echo "successfully able to recreate original string"
fi
Output:
w:"a"	s:"!="
w:""	s:"=="
w:"b"	s:"="
w:""	s:"!="
w:"c"	s:""
successfully able to recreate original string

VBScript

Function multisplit(s,sep)
	arr_sep = Split(sep,"|")
	For i = 0 To UBound(arr_sep)
		arr_s = Split(s,arr_sep(i))
		s = Join(arr_s,",")
	Next
	multisplit = s
End Function
 
Function multisplit_extra(s,sep)
	Set dict_sep = CreateObject("Scripting.Dictionary")
	arr_sep = Split(sep,"|")
	For i = 0 To UBound(arr_sep)
		dict_sep.Add i,"(" & arr_sep(i) & ")"
		arr_s = Split(s,arr_sep(i))
		s = Join(arr_s,i)
	Next
	For Each key In dict_sep.Keys
		s = Replace(s,key,dict_sep.Item(key))
	Next
	multisplit_extra = s
End Function
 
WScript.StdOut.Write "Standard: " & multisplit("a!===b=!=c","!=|==|=")
WScript.StdOut.WriteLine
WScript.StdOut.Write "Extra Credit: " & multisplit_extra("a!===b=!=c","!=|==|=")
WScript.StdOut.WriteLine
Output:
Standard: a,,b,,c
Extra Credit: a(!=)(==)b(=)(!=)c

V (Vlang)

Without using additional libraries or regular expressions:

fn main() {
    str := "a!===b=!=c"
    sep := ["==","!=","="]
    println(ms(str, sep))
}

fn ms(txt string, sep []string) (map[int]string, []string, []string) {
    mut ans, mut extra  := []string{}, []string{}
    mut place := map[int]string{}
    mut temp :=''
    mut vlen := 0

    for slen in sep {if slen.len > vlen {vlen = slen.len}}
	
    for cidx, cval in txt {	
        temp += cval.ascii_str()
        for value in sep {
            if temp.contains(value) && temp.len >= vlen {
                place[cidx] = value
                temp =''
            }
        }
    }

    for tidx, tval in txt {
        for pkey, pval in place {
	    if tidx == pkey {
                ans << ''
                extra << '(' + pval + ')'
	    }
	}
	if sep.any(it.contains(tval.ascii_str())) == false {
	    ans << tval.ascii_str()
	    extra << tval.ascii_str()
        }
    }
    println('Ending indices: $place')
    println('Answer: $ans')
    println('Extra: $extra')
    return place, ans, extra
}
Output:
Ending indices: {2: '!=', 4: '==', 6: '=', 8: '!='}
Answer: ['a', '', '', 'b', '', '', 'c']
Extra: ['a', '(!=)', '(==)', 'b', '(=)', '(!=)', 'c']
({2: '!=', 4: '==', 6: '=', 8: '!='}, ['a', '', '', 'b', '', '', 'c'], ['a', '(!=)', '(==)', 'b', '(=)', '(!=)', 'c'])

Wren

Library: Wren-pattern
Library: Wren-fmt
import "./pattern" for Pattern
import "./fmt" for Fmt

var input = "a!===b=!=c"
var p = Pattern.new("[/=/=|!/=|/=]")
var separators = p.findAll(input)
System.print("The separators matched and their starting/ending indices are:")
for (sep in separators) {
    System.print("  %(Fmt.s(-4, Fmt.q(sep.text))) between %(sep.span)")
}
var parts = p.splitAll(input)
System.print("\nThe substrings between the separators are:")
System.print(parts.map { |p| (p != "") ? Fmt.q(p) : "empty string" }.toList)
Output:
The separators matched and their starting/ending indices are:
  "!=" between [1, 2]
  "==" between [3, 4]
  "="  between [6, 6]
  "!=" between [7, 8]

The substrings between the separators are:
["a", empty string, "b", empty string, "c"]

XPL0

include xpllib; \for StrLen, StrNCmp, and Print

proc MultiSplit(Str, Seps, N);
char Str;  int Seps, N;
int S, Ch, SepLen;
[while Str(0) # 0 do
    [for S:= 0 to N-1 do
        [SepLen:= StrLen(Seps(S));
        if StrNCmp(Str, Seps(S), SepLen) = 0 then
            [Print(" (%s) ", Seps(S));
            Str:= Str + SepLen;
            S:= 100;
            ];
        ];
    if S < 100 then
        [Ch:= Str(0);  Str:= Str+1;
        if Ch # 0 then ChOut(0, Ch);
        ];
    ];
];

MultiSplit("a!===b=!=c", ["==", "!=", "="], 3)
Output:
a (!=)  (==) b (=)  (!=) c

Yabasic

t$ = "a!===b=!=c"
s$ = "==,!=,="

dim n$(1)

n = token(s$, n$(), ",")
dim p(n)

do
    l = len(t$)
    j = 0
    for i = 1 to n
        p(i) = instr(t$, n$(i))
        if p(i) and p(i) < l then l = p(i) : j = i end if
    next
    if not j print t$ : break
    print left$(t$, l - 1), " with separator ", n$(j)
    t$ = right$(t$, len(t$) - (l + len(n$(j))) + 1)
loop

zkl

Translation of: Python
fcn multisplit(text, sep){
   lastmatch := i := 0; matches := List();
   while(i < text.len()){
      foreach j,s in ([0..].zip(sep)){
         if(i == text.find(s,i)){
	    if(i > lastmatch) matches.append(text[lastmatch,i-lastmatch]);
	    matches.append(T(j,i));  # Replace the string containing the matched separator with a tuple of which separator and where in the string the match occured
	    lastmatch = i + s.len();
	    i += s.len()-1;
	    break;
	 }
      }
      i += 1;
   }
   if(i > lastmatch) matches.append(text[lastmatch,i-lastmatch]);
   return(matches);
}
multisplit("a!===b=!=c", T("==", "!=", "=")).println();
multisplit("a!===b=!=c", T("!=", "==", "=")).println();
Output:
L("a",L(1,1),L(0,3),"b",L(2,6),L(1,7),"c")
L("a",L(0,1),L(1,3),"b",L(2,6),L(0,7),"c")