5
$\begingroup$

Say I have a string that contains numbers and words, such as this one:

string = "there are 1234 words and numbers 5678 in here $999";

How would I separate the string into an ordered list containing sublists populated with words and numbers? The ideal list would look like this:

idealList = {{there are}, {1234}, {words and numbers}, {5678}, {in here}, {$999}}

I know how to extract all words and all numbers, but I can't create a list like the previous one.


Here's an example of what I tried to extract words and its output:

StringCases[string, RegularExpression["\\w(?<!\\d)[\\w'-]*"]]
{there, are, words, and, numbers, in, here}

I can also do this with pattern-matching instead of RegEx, but it doesn't get me closer to my goal.

Is my regex simply wrong, or does this problem require a tiny bit more involved solution?

$\endgroup$
  • $\begingroup$ In my view using regular expression is not good, in the sense that use of regular expression degrades the performance, it take more time. $\endgroup$ – user1674 Jul 5 '12 at 4:14
  • $\begingroup$ @gaurab: in the absence of concrete evidence (e.g. timing results) to back up your claim, your words are far from being an answer. I have thus turned your "answer" into a comment. $\endgroup$ – J. M. is away Jul 5 '12 at 4:23
12
$\begingroup$

What about this?

StringSplit[string, i : NumberString :> i]

Ok, everyone's giving answers that actually work with the $, so here's an edit, as @kguler and @MrWizard suggested

StringSplit[string, i : ("" | "$" ~~ NumberString) :> i] // StringTrim
$\endgroup$
  • $\begingroup$ This works, but can you explain how it works please? $\endgroup$ – CHM Jul 5 '12 at 0:51
  • $\begingroup$ @CHM, it splits the strings where it finds a NumberString, keeping the NumberString $\endgroup$ – Rojo Jul 5 '12 at 0:52
  • $\begingroup$ You can also modify the pattern to use Alternatives as in StringSplit[string, i : NumberString | StringExpression["$" ~~ NumberString] :> i] or, StringSplit[string, i : NumberString | ("$" ~~ NumberString) :> i] (+1). $\endgroup$ – kglr Jul 5 '12 at 4:22
  • $\begingroup$ +1 -- however, I think you shouldn't need StringTrim after using StringSplit[string]. $\endgroup$ – Mr.Wizard Jul 5 '12 at 9:02
  • $\begingroup$ @kguler, I edited. I had first tried that but fast and was bitten by ~~ vs |'s precedence and didn't spend a second realising that was the issue $\endgroup$ – Rojo Jul 5 '12 at 12:25
6
$\begingroup$

Note that Rojo's solution splits the expression containing the dollar sign as well:

StringSplit["there are 1234 words and numbers 5678 in here $999", i : NumberString :> i]
{"there are ", "1234", " words and numbers ", "5678", " in here $", "999"}

If you don't want that splitting to happen, here's one way, using a regex:

StringSplit["there are 1234 words and numbers 5678 in here $999",
            s : RegularExpression[".(\\d+)."] :> s]
{"there are", " 1234 ", "words and numbers", " 5678 ", "in here ", "$999"}

If the spaces in the ends of the strings are bothersome, you can use StringTrim[] to get rid of them:

StringSplit["there are 1234 words and numbers 5678 in here $999",
            s : RegularExpression[".(\\d+)."] :> s] // StringTrim
{"there are", "1234", "words and numbers", "5678", "in here", "$999"}

As another example:

str1 = "At 50x magnification, they'd better be paying me $1080 in 9 installments!";

StringSplit[str1, s : RegularExpression[".(\\d+)."] :> s] // StringTrim
{"At", "50x", "magnification, they'd better be paying me", "$1080", "in",
 "9", "installments!"}

The other methods presented would perform a splitting like

{"At", "50", "x magnification, they'd better be paying me", "$1080", "in",
 "9", "installments!"}

which may or may not be the desired behavior...

$\endgroup$
  • $\begingroup$ One could of course use a StringExpression[] instead of a RegularExpression[]: s : (_ ~~ DigitCharacter .. ~~ _) :> s. $\endgroup$ – J. M. is away Jul 5 '12 at 4:06
  • $\begingroup$ StringSplit["there are 1234 words and numbers 5678 in here $999", RegularExpression["\\$?(\\d)+"] -> "$0"] // StringTrima little bit more compact $\endgroup$ – Murta Jan 22 '13 at 0:22
3
$\begingroup$

I prefer the solutions given by Rojo and J.M. to the following one. But if you want to see a working version of your original approach with StringCases and RegularExpression, here is one possibility

StringCases[string, RegularExpression["([A-Za-z]|\\s)+|(\\$|\\d)+"]]

It returns

{"there are ", "1234", " words and numbers ", "5678", " in here ","$999"}

and as J.M. suggests above, apply StringTrim if desired. Handling decimals could also easily be added.

$\endgroup$
  • $\begingroup$ Instead of the pattern [A-Za-z] in the regex, you could use [[:alpha:]]. $\endgroup$ – J. M. is away Jul 5 '12 at 3:57
  • $\begingroup$ Ah nice, I couldn't think of a more proper RegEx. Only ick is that there are spaces before and after " words and numbers ". $\endgroup$ – CHM Jul 8 '12 at 16:32
3
$\begingroup$

Further variations:

using StringReplace:

List @@ StringTrim /@ StringReplace[string, 
    a : Except[{"$", DigitCharacter}] .. | NumberString | ("$" ~~ NumberString) :> {a}]

or, using the same replacement rule in StringCases:

 StringTrim /@ StringCases[string, 
   a : Except[{"$", DigitCharacter}] .. | NumberString | ("$" ~~ NumberString) :> {a}]

both yield:

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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