32
$\begingroup$

This seems like it should be trivial, but how do I partition a string into length n substrings? I can of course write something like

chunk[s_, n_] := StringJoin[#] & /@ Partition[Characters[s], n]

so that chunk["ABCDEF",2] -> {"AB","CD","EF"} but this appears unnecessarily cumbersome.

$\endgroup$
3
  • $\begingroup$ Welcome to Mathematica.SE! This is a good question, as there doesn't seem to be a direct built-in way (I bumped into this before). Please consider filling out the name field in your profile, so it will show as something easier to remember than 'user1268' $\endgroup$
    – Szabolcs
    Commented May 17, 2012 at 9:19
  • $\begingroup$ Link to MathGroup version. $\endgroup$
    – Szabolcs
    Commented May 17, 2012 at 10:22
  • $\begingroup$ Note StringJoin[#] & is the same as just StringJoin. $\endgroup$
    – amr
    Commented Oct 3, 2012 at 19:59

9 Answers 9

25
$\begingroup$

Try this:

StringCases["ABCDEFGHIJK", LetterCharacter ~~ LetterCharacter]

{"AB", "CD", "EF", "GH", "IJ"}

or for more general cases (i.e. not just for letters, but any characters, and for any partition size):

stringPartition1[s_String, n_Integer] := StringCases[s, StringExpression @@ Table[_, {n}]];

It is more elegant though to use Repeated (thanks rcollyer):

stringPartition2[s_String, n_Integer] := StringCases[s, Repeated[_, {n}]];

stringPartition2["longteststring", 4]

{"long", "test", "stri"}

$\endgroup$
2
  • 7
    $\begingroup$ Instead of Table[_,{n}], I'd consider using Repeated[_, {n}], instead. $\endgroup$
    – rcollyer
    Commented May 17, 2012 at 1:34
  • $\begingroup$ Thanks @rcollyer, I felt I missed something, but it was too late. Incorporated now. $\endgroup$ Commented May 17, 2012 at 9:50
20
$\begingroup$

Here is the regular-expression way:

chunk[s_, n_] := 
 StringCases[s, RegularExpression[".{1," <> ToString[n] <> "}"]]

chunk["Hello this is a test string", 2]

{"He", "ll", "o ", "th", "is", " i", "s ", "a ", "te", "st", " s", "tr", "in", "g"}

chunk["Hello this is a test string", 4]

{"Hell", "o th", "is i", "s a ", "test", " str", "ing"}

Note that the last substrings didn't fit the chunk size but were still included.

If you don't want to include them, change the regular expression from ".{1," <> ToString[n] <> "}" to ".{" <> ToString[n] <> "}".

$\endgroup$
15
$\begingroup$

Another possibility:

StringTake[#, 
   Partition[Range@StringLength@#, 2, 2, 1, {}]] &@"abcdefghi"

giving

(*  {"ab", "cd", "ef", "gh", "i"} *)
$\endgroup$
12
$\begingroup$

Not completely original, but very compact.

chunk[s_, n_] := StringJoin@@@Partition[Characters[s], n, n, 1, {}]

Update for V10.1

This new function is exactly for that:

StringPartition["ABCDEF",2]

{"AB", "CD", "EF"}

$\endgroup$
8
$\begingroup$

This will give better performance (3 times faster in my test, partitioning into length-two strings) than your original code:

chunk[s_, n_] := FromCharacterCode@Partition[ToCharacterCode[s], n]

The reason is that the first few steps of the computation are done with packed arrays.

It will still be slower than the regex-based approaches (István's and Jens's), on my machine by a factor of 2.

The StringTake approach is much slower than all the others in my machine.

Benchmarks

Function definitions:

(* original *)
chunk1[s_, n_] := StringJoin[#] & /@ Partition[Characters[s], n]

(* István *)
chunk2[s_, n_] := StringCases[s, Repeated[_, {n}]]

(* Jens *)
chunk3[s_, n_] := StringCases[s, RegularExpression[".{1," <> ToString[n] <> "}"]]

(* TomD *)
chunk4 = StringTake[#, Partition[Range@StringLength@#, #2, #2, 1, {}]] &;

(* mine *)
chunk5[s_, n_] := FromCharacterCode@Partition[ToCharacterCode[s], n]

text = ExampleData[{"Text", "Hamlet"}];
testString = StringJoin[ConstantArray[text, 20]];

StringLength[testString] (* 3438740 *)

Timings:

(* original *)
In[10]:= Timing[chunk1[testString, 2];]
         Timing[chunk1[testString, 100];]

Out[10]= {5.968, Null}
Out[11]= {1.703, Null}

(* István - fastest *)
In[12]:= Timing[chunk2[testString, 2];]
         Timing[chunk2[testString, 100];]

Out[12]={1.25, Null}
Out[13]={0.11, Null}

(* Jens - fastest *)
In[14]:= Timing[chunk3[testString, 2];]
         Timing[chunk3[testString, 100];]

Out[14]= {1.313, Null}
Out[15]= {0.125, Null}

(* TomD *)
In[16]:= Timing[chunk4[testString, 2];]
         Timing[chunk4[testString, 100];]

(* More than a few minutes. Didn't wait for it to finish ... *)

(* mine *)
In[18]:= Timing[chunk5[testString, 2];]
         Timing[chunk5[testString, 100];]

Out[18]= {2.25, Null}
Out[19]= {0.266, Null}

Conclusion: use regex-based methods. The built-in string patterns also use a regex library internally, I believe, but they are easier to construct programmatically because they are represented as expressions.

$\endgroup$
13
  • $\begingroup$ Scrollbar ate your timing results... First I thought it was so lightning fast you did not bother to write it out in numbers :) $\endgroup$ Commented May 17, 2012 at 9:54
  • 2
    $\begingroup$ Thanks, Szabolcs, and everyone who answered. As ever, more than one way to skin a cat with Mathematica. I'm a bit surprised that there isn't a StringPartition function taking the same arguments as Partition as a built-in, analogous to StringTake vs. Take. $\endgroup$
    – David G
    Commented May 17, 2012 at 13:23
  • 3
    $\begingroup$ Your code can be further sped up by using Developer`PartitionMap to apply FromCharacterCode to each term in the list. This does not unpack the list. The speed up is marginal, though, on my machine 1.25793 -> 1.12617 and 0.135588 -> 0.095252. So, still not a contender for the fastest method. $\endgroup$
    – rcollyer
    Commented May 17, 2012 at 16:38
  • 1
    $\begingroup$ @rcollyer Good point! I never used Developer`PartitionMap before. (I've seen it in the docs, but I didn't realize its significance: not unpacking.) $\endgroup$
    – Szabolcs
    Commented May 17, 2012 at 16:40
  • 1
    $\begingroup$ @rcollyer I solved the 'mystery': it was auto-compilation. It avoids unpacking only if the list is above the auto-compilation length. $\endgroup$
    – Szabolcs
    Commented May 17, 2012 at 17:08
6
$\begingroup$

I wanted a solution using StringSplit[]

chunk[s_, n_] := StringSplit[s, RegularExpression["(.{" <> ToString@n <> "})"] -> "$1"]
                                                                     ~ DeleteCases ~ ""
$\endgroup$
2
  • $\begingroup$ Cleaner, IMO: StringSplit[s, x : Repeated[_, {n}] :> x][[;; ;; 2]]. I don't see an advantage over StringCases however. $\endgroup$
    – Mr.Wizard
    Commented May 4, 2014 at 11:05
  • $\begingroup$ @Mr.Wizard No advantage. I just wanted a way to use StringSplit[] as said. But I'm too old to remember why I wanted that. $\endgroup$ Commented May 5, 2014 at 3:49
4
$\begingroup$

Historical note:

WolframLanguageData["StringPartition"
 , {"VersionIntroduced", "DateIntroduced"}]

{10.1, DateObject[{2015, 3, 30}, "Day", "Gregorian", 5.]}


The command supports UpTo as well as offset specs, similar to Partition.

str = "ABCDEFGHIJK";

StringPartition[str, UpTo[2]]

{"AB", "CD", "EF", "GH", "IJ", "K"}

StringPartition[str, 2, 1]

{"AB", "BC", "CD", "DE", "EF", "FG", "GH", "HI", "IJ", "JK"}

StringPartition[str, UpTo[7]]

{"ABCDEFG", "HIJK"}

$\endgroup$
4
$\begingroup$

Using SequenceCases (new in 10.1)

str = "ABCDEFGHIJK";

SequenceCases[Characters[str], x : {Repeated[_, {2}]} :> StringJoin[x]]

{"AB", "CD", "EF", "GH", "IJ"}

SequenceCases[Characters[str], x : {Repeated[_, {1, 2}]} :> StringJoin[x]]

{"AB", "CD", "EF", "GH", "IJ", "K"}

SequenceCases[Characters[str], x : {Repeated[_, {1, 7}]} :> StringJoin[x]]

{"ABCDEFG", "HIJK"}

SequenceCases[
 Characters[str], 
 x : {Repeated[_, {2}]} :> StringJoin[x], 
 Overlaps -> True]

{"AB", "BC", "CD", "DE", "EF", "FG", "GH", "HI", "IJ", "JK"}

SequenceCases[
 Characters[str], 
 x : {Repeated[_, {1, 2}]} :> StringJoin[x], 
 Overlaps -> True]

{"AB", "BC", "CD", "DE", "EF", "FG", "GH", "HI", "IJ", "JK", "K"}

$\endgroup$
1
$\begingroup$
str = "ABCDEFGHIJK";

Using MovingMap:

MovingMap["" <> # &, Characters[str], 1][[1 ;; -1 ;; 2]]

{"AB", "CD", "EF", "GH", "IJ"}

$\endgroup$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.