views:

471

answers:

5

My input consists of user-posted strings.

What I want to do is create a dictionary with words, and how often they've been used. This means I want to parse a string, remove all garbage, and get a list of words as output.

For example, say the input is : "#@!@LOLOLOL YOU'VE BEEN *PWN3D* ! :') !!!1einszwei drei !"

The output I need is the list : "LOLOLOL", "YOU'VE", "BEEN", "PWN3D", "einszwei", "drei"

I'm no hero at regular expressions and I've been Googling but my Google-kungfu seams to be weak...

How would I go from input to the wanted output ?

+2  A: 

You should look into Natural Language Processing (NLP), not regular expressions, and if you are targeting more than one spoken language, you need to factor that in as well. Since you're using C#, check out the SharpNLP project.

Edit: This approach is only necessary if you care about the semantic content of the words you're trying to split up.

Mike Atlas
Thanks very much for the response ! :)But lets keep it simple and say I don't care about the language - - I'll only consider words with optionally a ''' and / or a '-' character ?
Led
@Mike Atlas, nice link. +1
gmcalab
If you don't care about the language, then why not just string.Replace() all the characters you don't want and then string.Split() it on the space character? No need for regular expressions.
Mike Atlas
@Mike - ha that's what i just suggested
Jason
Because it seems more natural to specify what I *DO* allow : words with an optional ' or - character and nothing else.If I'm going about this the wrong way please tell me cause this stuff is kinda new to me ;)
Led
"There's more than one way to skin a cat."
Mike Atlas
+2  A: 

You don't necessarily need a regex for this, if tokenizing is all you're doing. First you could sanitize the string by removing all non-letter characters except for spaces and then do a Split() on the space character. That will work for most everything, although contractions may be tough. That should get you started at least.

Jason
Okay, so what I want to do is remove all invalid characters, but the ' and - characters are also invalid IF they are not inbetween alphabetical characters. (in "word-up" the - is valid, in "word ----- up" the - characters should be removed...)
Led
you can put a check in there that will look to see if the `'` or `-` is surrounded by letter characters and if they are, don't delete.
Jason
A: 

My gut feeling would not be to use regular expressions, but just do a loop or two.

Iterate over each char in the string, if not a valid char, replace it with a space Then use String.Split() and split over spaces.

Appostrophes and hyphens may be a little more tricky to determine if they are junk characters or legite ones. But if you are using a for loop to iterate over the string then looking backwards and forwards from the current character should help you.

Then you will have a list of words - for each of these words check if they are valid in your dictionary. If you want this to be fast, performing somekind of binary search would be best. But just to get it working a linear search would be easier to start with.

EDIT: I only mentioned the dictionary thing because I thought you might be interested only in legitimate words, ie not "asdfasdf" but ignore that last statement if that's not what you need.

JSmyth
you don't want to replace invalid chars w/spaces.
Jason
+4  A: 

Simple Regex:

\w+

This matches a string of "word" characters. That is almost what you want.

This is slightly more accurate:

\w(?<!\d)[\w'-]*

It matches any number of word characters, ensuring that the first character was not a digit.

Here are my matches:

1 LOLOLOL
2 YOU'VE
3 BEEN
4 PWN3D
5 einszwei
6 drei

Now, that's more like it.

EDIT:
The reason for the negative look-behind, is that some regex flavors support Unicode characters. Using [a-zA-Z] would miss quite a few "word" characters that are desirable. Allowing \w and disallowing \d includes all Unicode characters that would conceivably start a word in any block of text.

EDIT 2:
I have found a more concise way to get the effect of the negative lookbehind: Double negative character class with a single negative exclusion.

[^\W\d][\w'-]*(?<=\w)

This is the same as the above with the exception that it also ensures that the word ends with a word character. And, finally, there is:

[^\W\d](\w|[-']{1,2}(?=\w))*

Ensuring that there are no more than two non-word-characters in a row. Aka, It matches "word-up" but not "word--up", which makes sense. If you want it to match "word--up", but not "word---up", you can change the 2 to a 3.

John Gietzen
Thank you very much, works like a charm ! :)
Led
@Led: You may want to check out the Regex at the end of edit #2. It may be a little closer to what you are looking for.
John Gietzen
A: 

Using the following

var pattern = new Regex(
  @"( [^\W_\d]              # starting with a letter
                            # followed by a run of either...
      ( [^\W_\d] |          #   more letters or
        [-'\d](?=[^\W_\d])  #   ', -, or digit followed by a letter
      )*
      [^\W_\d]              # and finishing with a letter
    )",
  RegexOptions.IgnorePatternWhitespace);

var input = "#@!@LOLOLOL YOU'VE BEEN *PWN3D* ! :') !!!1einszwei drei foo--bar!";

foreach (Match m in pattern.Matches(input))
  Console.WriteLine("[{0}]", m.Groups[1].Value);

produces output of

[LOLOLOL]
[YOU'VE]
[BEEN]
[PWN3D]
[einszwei]
[drei]
[foo]
[bar]
Greg Bacon