2017-01-02 2 views
1

Я хочу получить строку, содержащую первые N слов из заданной строки. Например.Получите подстроку, содержащую первые N слов в C# .NET с использованием Regex

Получить первые 5 слов из The quick_brown, fox1 перепрыгивает через ленивую собаку должен вернуть quick_brown, fox1 перепрыгивает

Обратите внимание, что слово из букв, цифр и _ (в основном \ W +) и все исходные разделители (например,) сохраняются.

мне удалось сделать это, используя классический C# такой код:

public static bool IsWordChar(this char c) 
{ 
    return char.IsLetterOrDigit(c) || c == '_'; 
} 

public static string GetFirstWords(string s, int wordCount, string truncateSuffix = " [...]") 
{ 
    var sb = new StringBuilder(); 
    int currWordCount = 0; 
    char prevC = '\0'; 
    foreach (var c in s) 
    { 
     sb.Append(c); 
     if (!c.IsWordChar() && prevC.IsWordChar()) 
      currWordCount++; 

     if (currWordCount >= wordCount) 
     { 
      if (sb.Length < s.Length) 
       sb.Append(truncateSuffix); 

      return sb.ToString(); 
     } 

     prevC = c; 
    } 

    // adding last word, if necessary 
    if (prevC.IsWordChar()) 
     sb.Append(prevC); 

    return sb.ToString(); 
} 

Он работает достаточно быстро для моих потребностей (O (п)), но мне интересно, если это может быть достигнуто с помощью регулярных выражений.

Я попытался использовать \W+ и выполнить первые N совпадений, но я потерял фактические разделители без слова из исходного текста.

Вопрос: Существует ли эквивалент C#, эквивалентный вышеуказанному коду?

Спасибо.

+2

Я думаю, вы могли бы попробовать 'Regex.Replace (s, @" (? S)^(\ W * \ w + (?: \ W + \ w +) {4}). * "," $ 1 [... ] ")', см. [this regex demo] (http://regexstorm.net/tester?p=%28%3fs%29%5e%28%5cW*%5cw%2b%28%3f%3a%5cW% 2б% 5cw% 2b% 29% 7b4% 7d% 29. * & я = + quick_brown% 2c + fox1 + прыжки + над + кнопки + ленивым + собака + & г =% 241 +% 5б ...% 5d). Если ожидается, что строка содержит менее 5 слов, и результат должен содержать весь ввод, замените '{4}' на '{0,4}'. –

+0

Да, он корректно работает с Match (нет необходимости заменять). Пожалуйста, разместите его как ответ, чтобы я мог его принять. Спасибо. – Alexei

+1

Regex будет медленнее, чем ваш метод (по крайней мере, если строка большая, так что это важно), поэтому не заменяйте ее по соображениям производительности. –

ответ

1

Регулярное выражение для извлечения siubstring, содержащий первые пять слов из длинной строки является

@"^\W*\w+(?:\W+\w+){4}" 

Смотрите regex demo

Детали:

  • ^ - начало строки
  • \W* - ноль или более символов без слов
  • \w+ - 1+ символы слово
  • (?:\W+\w+){4} - 4 последовательности (заменить {0,4}, если число слов может быть меньше, чем 5 во входной строке и ожидаемый выход вся струнное то) из:
    • \W+ - 1+ несловообразующих символы
    • \w+ - 1+ слово символы.

ли регулярное выражение является более эффективным или нет, вам необходимо проверить решение в C#. Чтобы эффективно использовать регулярное выражение, объявите его как поле readonly, используя RegexOptions.Compiled, а затем позвоните с помощью Regex.Match. Смотрите C# demo:

private static readonly Regex rxFirst5Words = new Regex(@"^\W*\w+(?:\W+\w+){4}", RegexOptions.Compiled); 
// ... 
var s = "The quick_brown, fox1 jumps over the lazy dog"; 
var result = rxFirst5Words.Match(s); 
if (result.Success) 
    Console.WriteLine(result.Value); 
1

Я использовал бы границы слов (\b), чтобы искать слова вместо \w и \W.

Если я немного изменить свой вопрос для поиска первых N слов и N-1 «вещей между словами» вы можете быть в состоянии использовать

Regex.Match("The quick_brown, fox1 jumps over the lazy dog", @"^(\b.+?\b){9}") 

, чтобы получить ожидаемый результат для N = 5.

Обратите внимание, что это предполагает, что ввод начинается со слова.

0

Для тех, кто хочет быстро работающий код, я отправляю код я использую на основе принятого ответа:

public static string GetFirstWordsRegEx(string s, int wordCount, string truncateSuffix = " [...]") 
{ 
    // replace with string.Format for C# less than 6.0 
    string pattern = [email protected]"^\W*\w+(?:\W+\w+){{{wordCount - 1}}}"; 
    var regex = new Regex(pattern); 
    var match = regex.Match(s); 
    if (!match.Success) 
     return s; 

    var ret = match.Value; 
    return ret.Length < s.Length ? ret + truncateSuffix : ret; 
} 

Он получает первые wordCount слова вместе с их разделители или весь текст, если он имеет менее wordCount. Кроме того, позволяет добавлять суффикс при усечении.

[EDIT]

Wiktor Stribiżew «s сомнения, о которых решение более высокую производительность, сделали тест его. Я буду называть первое решение «классическим», второе «регулярное выражение» и третье «скомпилированное Regex».

Первый в вопросе, второй в начале этого ответа и третий один ниже:

// generated all possible text truncation patterns 
    private static readonly List<Regex> FirstWordRegexes = new List<Regex> 
    { 
     new Regex(@"^\W*\w+(?:\W+\w+){0}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){1}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){2}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){3}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){4}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){5}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){6}", RegexOptions.Compiled), 
     // ... 
     // removed for brevity 
     // ... 
     new Regex(@"^\W*\w+(?:\W+\w+){147}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){148}", RegexOptions.Compiled), 
     new Regex(@"^\W*\w+(?:\W+\w+){149}", RegexOptions.Compiled), 
    }; 

    public static string GetFirstWordsRegExOptimized(string s, int wordCount, string truncateSuffix = " [...]") 
    { 
     var regex = FirstWordRegexes[wordCount-1]; 
     var match = regex.Match(s); 
     if (!match.Success) 
      return s; 

     var ret = match.Value; 
     return ret.Length < s.Length ? ret + truncateSuffix : ret; 
    } 

код Test

var sw = new Stopwatch(); 
var rand = new Random(); 
const int minValue = 20; 
const int maxValue = 150; 

#region warm up 
sw.Start(); 
Console.WriteLine($"Warming up..."); 

// _testStrings contains 100K random real texts which may be longer or not than first words truncation value 
foreach (var str in _testStrings) 
{ 
    var dummy = str; 
} 
Console.WriteLine($"Warm up took {sw.ElapsedMilliseconds} ms"); 
#endregion 

#region Classic C# approach 
foreach (var str in _testStrings) 
{ 
    int wordCount = rand.Next(minValue, maxValue); 
    var firstWords = Utils.GetFirstWords(str, wordCount); 
} 
Console.WriteLine($"Classic code took {sw.ElapsedMilliseconds} ms"); 
sw.Restart(); 
#endregion 

#region Uncompiled regex 
sw.Start(); 
foreach (var str in _testStrings) 
{ 
    int wordCount = rand.Next(minValue, maxValue); 
    var firstWords = Utils.GetFirstWordsRegEx(str, wordCount); 
} 
Console.WriteLine($"Uncompiled regex code took {sw.ElapsedMilliseconds} ms"); 
sw.Restart(); 
#endregion 

#region Compiled regex 
sw.Start(); 
foreach (var str in _testStrings) 
{ 

    int wordCount = rand.Next(minValue, maxValue); 
    var firstWords = Utils.GetFirstWordsRegExOptimized(str, wordCount); 
} 
Console.WriteLine($"Compiled regex code took {sw.ElapsedMilliseconds} ms"); 
sw.Restart(); 
#endregion 

Результаты

Классический код занял 953 мс

Неоткомпилированный код регулярного выражения взял 5559 мса

Составителя код регулярного выражения взял 4194 мс

Как и ожидались, составленное регулярное выражение было быстрее, чем неоткомпилированные один. Однако классическая версия была намного быстрее.

+0

Обратите внимание, что вы, вероятно, не хотите использовать RegexOptions.Compiled для регулярного выражения, которое вы создаете при каждом вызове. Ответ Wiktor явно переместил это в (статическое) поле по этой причине, чтобы создать/скомпилировать его один раз. Учитывая, что ваше выражение изменяется с помощью ввода («количество слов»), я бы, вероятно, сохранил ваш путь (т. Е. В методе), но полностью удалял эту опцию. См. Https://msdn.microsoft.com/en-us/library/yd1hzczs(v=vs.110).aspx#Anchor_7 и измерить/решить для себя –

+0

Да, это правильно. Я удалил его. Спасибо. – Alexei

Смежные вопросы