Рейтинг:
2
DarrenWheatley
Не уверен, что мое решение Excel VBA опубликовано, поэтому я пытаюсь еще раз
Public Function noBad(ByVal badstr As String) As String
Dim badWords As Variant, goodWords As Variant
Dim i As Integer, bw As String
badWords = Array("poop*", "PHB", "gotten", "POOP*")
goodWords = Array("p**p", "boss", "become", "P**P")
For i = 0 To UBound(badWords)
bw = badWords(i)
With CreateObject("VBScript.RegExp")
.Pattern = "\b" & bw & IIf(InStr(bw, "*"), "", "\b")
.Global = True
badstr = .Replace(badstr, goodWords(i))
End With
Next i
noBad = badstr
End Function
Стоит попробовать, подумал я. Ладно, дело обстоит не так уж хорошо, но я получаю только короткий обед
Kornfeld Eliyahu Peter
Там должно быть очень скучно идти на VBA :-)
DarrenWheatley
Почему именно Даунер на VBA? Честно говоря, это именно то, что я ожидал, но это не похоже на то, что мы кодовый магазин или что-то в этом роде.
Kornfeld Eliyahu Peter
:-) <-- вы это видели?
Я пошутил... Я верю в "правильный инструмент для..."
DarrenWheatley
Ну, я думаю, это был тихий обед...
Рейтинг:
2
PIEBALDconsult
После еще немного рефакторинга и тому подобного:
PIEBALD.Type.ReplaceOmatic replacer =
new PIEBALD.Type.ReplaceOmatic
(
new System.Tuple<string,string> ( "PHB!" , "boss!" )
,
new System.Tuple<string,string> ( "gotten" , "become" )
,
new System.Tuple<string,string> ( "*p[o0]{2}p*" , "p**p" )
) ;
string intext = "My PHB is such a poophead. It's gotten worse since his promotion. My PHB has started his new blog phblog.com. He's SUCH A MISBEGOTTEN POOPHEAD!" ;
string outtext = intext ;
int total = replacer.Replace ( ref outtext ) ;
System.Console.WriteLine ( "{0}\n{1}\n{2}" , intext , total , outtext ) ;
Производит:
My PHB is such a poophead. It's gotten worse since his promotion. My PHB has started his new blog phblog.com. He's SUCH A MISBEGOTTEN POOPHEAD!
5
My boss is such a p**phead. It's become worse since his promotion. My boss has started his new blog phblog.com. He's SUCH A MISBEGOTTEN P**PHEAD!
Это как раз в (2016-11-27): ReplaceOmatic[
^]
Последние новости (2016-11-28):
Теперь он отслеживает, сколько символов было заменено в каждом разделе, поэтому он перестанет применять замены, как только будет заменен весь раздел.
Это является причиной для сортировки наиболее часто встречающихся замен в начале списка.
И я ужесточил код для копирования символов в StringBuilder.
Часть изменений:
int r = 0 ;
/* Apply ReplacementSpecs to the section until we run out of them or we've replaced the entire section. */
for ( int i = 0 ; ( i < this.rep.Count ) && ( r < Candidate.Length ) ; i++ )
{
/* Track how many characters have been replaced so far. */
r += this.rep [ i ].Replace ( ref temp ) ;
}
Новость! (2016-11-30):
Добавлена сортировка, обновлена статья и загружен последний код (я надеюсь).
Рейтинг:
2
Graeme_Grant
Я заметил это только 20 минут назад, так что, надеюсь, не слишком поздно для чаепития!
Код C# допускает индивидуальную чувствительность к регистру слов + переопределение ярлыка (в Бонусном требовании). Во всех случаях используются одни и те же методы расширения замены плохих слов. ReplaceWords
расширение строки для набора слов или ReplaceText
расширение строки для детализации одного слова.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BadWordFilter
{
class Program
{
static void Main(string[] args)
{
FirstTest();
BonusTest();
Console.WriteLine("-- Press any key to exit --");
Console.ReadKey();
}
private static void FirstTest()
{
var badWords = new Dictionary<string, tuple<string, bool>>()
{
{"poophead", new Tuple<string, bool>("p**phead", false)},
{"PHB", new Tuple<string, bool>("boss", false)},
{"gotten",new Tuple<string, bool>("become", false) }
};
string badMessage = "My PHB is such a poophead. It's gotten worse since his promotion";
string goodMessage = badMessage.ReplaceWords(badWords, filtered: false);
Console.WriteLine("First Test");
Console.WriteLine("==========");
Console.WriteLine($"Before: {badMessage}");
Console.WriteLine($"After: {goodMessage}");
Console.WriteLine();
badWords = new Dictionary<string, tuple<string, bool>>()
{
{"POOPHEAD", new Tuple<string, bool>("P**PHEAD", true)},
{"poophead", new Tuple<string, bool>("p**phead", false)},
{"PHB", new Tuple<string, bool>("boss", true)}
};
badMessage = "My PHB is such a POOPHEAD!";
goodMessage = badMessage.ReplaceWords(badWords, filtered: false);
Console.WriteLine($"Before: {badMessage}");
Console.WriteLine($"After: {goodMessage}");
Console.WriteLine();
}
private static void BonusTest()
{
var badWords = new Dictionary<string, tuple<string, bool>>()
{
{"POOP*", new Tuple<string, bool>("P**P", true) },
{"poop*", new Tuple<string, bool>("p**p", false) },
{"PHB!", new Tuple<string, bool>("boss", true) },
{"gotten",new Tuple<string, bool>("become", true) }
};
string badMessage = "My PHB has started his new blog phblog.com. He's SUCH A MISBEGOTTEN POOPHEAD!";
string goodMessage = badMessage.ReplaceWords(badWords, filtered: true);
Console.WriteLine("Bonus Test");
Console.WriteLine("==========");
Console.WriteLine($"Before: {badMessage}");
Console.WriteLine($"After: {goodMessage}");
Console.WriteLine();
}
}
public static class StringExtension
{
private static readonly string[] spaceSplitChar = { " " };
private static readonly char[] wordSplitChars = @"?<=[\.!\?])".ToCharArray();
public static string ReplaceWords(this string input, Dictionary<string, tuple<string, bool>> badWords, bool filtered = false, bool trimWhitespaces = true)
{
if (string.IsNullOrEmpty(input))
return input;
foreach (var word in badWords)
input = input.ReplaceText(word.Value.Item1, word.Key, filtered: filtered, isCaseSensitive: word.Value.Item2, trimWhitespaces: trimWhitespaces);
return input;
}
public static string ReplaceText(this string input, string replaceText, string findText = "", bool filtered = false, bool isCaseSensitive = false, bool trimWhitespaces = true)
{
if (string.IsNullOrEmpty(input))
return input;
var sb = new StringBuilder();
bool isMatchStart = false,
isMatchEnd = false;
StringComparison compareMode = isCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
if (filtered)
{
isMatchStart = findText.EndsWith("*");
isMatchEnd = findText.StartsWith("*");
compareMode = findText.EndsWith("!") ? StringComparison.InvariantCulture : compareMode;
findText = findText.Trim(new[] { '*', '!' });
}
int lenOldValue = findText.Length,
curPosition = 0,
idxNext = input.IndexOf(findText, compareMode);
while (idxNext >= 0)
{
if (isMatchStart || !isMatchEnd)
{
if (input.Substring(idxNext - 1, 1).Equals(" "))
sb.Append(input, curPosition, idxNext - curPosition)
.Append(replaceText);
}
else if (isMatchEnd)
{
sb.Append(input, curPosition, idxNext - curPosition)
.Append(curPosition < input.Length && input.Substring(curPosition, curPosition + 1)[0].IsEndChar() ? findText : replaceText);
}
curPosition = idxNext + lenOldValue;
idxNext = input.IndexOf(findText, curPosition, compareMode);
}
sb.Append(input, curPosition, input.Length - curPosition);
input = sb.ToString();
if (trimWhitespaces)
input= input.TrimCharacter(' ');
return input;
}
public static bool IsEndChar(this char c)
=> wordSplitChars.Contains(c);
public static string TrimCharacter(this string input, char c)
=> string.Join(c.ToString(), input.Split(c).Where(str => str != string.Empty).ToArray());
}
}
Вывод из выполнения:
First Test
==========
Before: My PHB is such a poophead. It's gotten worse since his promotion
After: My boss is such a p**phead. It's become worse since his promotion
Before: My PHB is such a POOPHEAD!
After: My boss is such a P**PHEAD!
Bonus Test
==========
Before: My PHB has started his new blog phblog.com. He's SUCH A MISBEGOTTEN POOPHEAD!
After: My boss has started his new blog phblog.com. He's SUCH A MISBEGOTTEN P**PHEAD!
-- Press any key to exit --
Этот код построен для повышения эффективности, поэтому никаких регулярных выражений не используется! ;)
[edit: надеюсь, я исправил все странные вещи, когда вставлял код...]
Рейтинг:
2
Graeme_Grant
Примечание: это сводное сравнение производительности только вышеуказанных решений C#
** Обновлено 2/12/16: Стюарт Дутсон предоставил тестовый проект, поэтому тайминги обновлены.
Мне было любопытно посмотреть, как другие решения C# сравниваются по производительности с моими собственными, поэтому я построил тестовый стенд, который вы можете скачать[^] и попробовать себя. Я сбалансировал все решения так, чтобы они одинаково работали против одних и тех же тестовых строк.
Отказ от ответственности: я не проверял каждое решение, чтобы увидеть, дают ли они все один и тот же результат или нет. Теперь я запустил тесты на каждом из решений, и 3 из них не справляются с тестовым набором. (Я предлагаю авторам решений 3, 5 и 12 скачать ссылку выше и проверить на наличие ошибок.)
Вот результаты (от лучших до худших, ранжированные в среднем) на моей машине dev, скомпилированной в режиме выпуска и запущенной из командной строки.
(Примечание: результаты будут отличаться для вас в зависимости от конфигурации вашей системы)
После первоначального позиционирования результатов и настройки всех тестов (v2) я почувствовал, что могу немного улучшить свои результаты, поэтому я внес некоторые незначительные изменения (их можно найти по ссылке для скачивания выше), и теперь пересмотренные результаты:
-----------------------------------------------------------------------------------
Tests run: 10
Iterations/test: 100
Total executions: 1,000
Basic Test
Solution 10 : MIN: 0.72670 ms | MAX: 0.96770 ms | AVG: 0.75483 ms
Solution 1 : MIN: 1.17980 ms | MAX: 2.08300 ms | AVG: 1.30762 ms
Solution 3 : MIN: 2.92230 ms | MAX: 3.71300 ms | AVG: 3.03632 ms
Solution 8 : MIN: 3.97410 ms | MAX: 4.34740 ms | AVG: 4.06542 ms
Solution 12 : MIN: 4.97950 ms | MAX: 5.31250 ms | AVG: 5.04871 ms
Solution 13*2 : MIN: 7.2594 ms | MAX: 10.2251 ms | AVG: 8.18209 ms
Solution 2 : MIN: 7.33320 ms | MAX: 8.42080 ms | AVG: 7.63468 ms
Solution 13*1 : MIN: 8.4053 ms | MAX: 14.0585 ms | AVG: 10.4869 ms
Solution 5 : MIN: 14.79730 ms | MAX: 17.29360 ms | AVG: 15.54762 ms
Bonus Test
Solution 10 : MIN: 1.08750 ms | MAX: 1.13540 ms | AVG: 1.10539 ms
Solution 1 : MIN: 1.09130 ms | MAX: 1.35580 ms | AVG: 1.15636 ms
Solution 3 : MIN: 2.95310 ms | MAX: 3.38000 ms | AVG: 3.04058 ms
Solution 8 : MIN: 5.37370 ms | MAX: 5.74020 ms | AVG: 5.46218 ms
Solution 12 : MIN: 7.44160 ms | MAX: 7.72250 ms | AVG: 7.50184 ms
Solution 13*2 : MIN: 7.4998 ms | MAX: 12.9918 ms | AVG: 8.99166 ms
Solution 13*1 : MIN: 8.2866 ms | MAX: 11.5506 ms | AVG: 9.42119 ms
Solution 2 : MIN: 8.54510 ms | MAX: 8.86510 ms | AVG: 8.64252 ms
Solution 5 : MIN: 16.79520 ms | MAX: 17.75920 ms | AVG: 17.19089 ms
*1 F#Solution
*2 PreCompiledF#Solution
-----------------------------------------------------------------------------------
Tests run: 10
Iterations/test: 1000
Total executions: 10,000
Basic Test
Solution 10 : MIN: 7.24660 ms | MAX: 7.52710 ms | AVG: 7.35793 ms
Solution 1 : MIN: 10.38490 ms | MAX: 10.72050 ms | AVG: 10.53257 ms
Solution 3 : MIN: 29.32480 ms | MAX: 29.52170 ms | AVG: 29.41673 ms
Solution 8 : MIN: 39.87840 ms | MAX: 40.39850 ms | AVG: 40.05387 ms
Solution 12 : MIN: 49.83420 ms | MAX: 50.64540 ms | AVG: 50.08076 ms
Solution 2 : MIN: 73.15800 ms | MAX: 74.28660 ms | AVG: 73.62445 ms
Solution 13*2 : MIN: 78.6023 ms | MAX: 93.5715 ms | AVG: 83.0625 ms
Solution 13*1 : MIN: 88.4249 ms | MAX: 93.8609 ms | AVG: 90.9543 ms
Solution 5 : MIN: 140.52420 ms | MAX: 148.83820 ms | AVG: 143.44404 ms
Bonus Test
Solution 1 : MIN: 9.54210 ms | MAX: 10.12520 ms | AVG: 9.72796 ms
Solution 10 : MIN: 10.84060 ms | MAX: 11.22680 ms | AVG: 10.94738 ms
Solution 3 : MIN: 29.66310 ms | MAX: 30.13180 ms | AVG: 29.79657 ms
Solution 8 : MIN: 54.05920 ms | MAX: 59.03390 ms | AVG: 56.43869 ms
Solution 12 : MIN: 74.48620 ms | MAX: 75.41370 ms | AVG: 74.66223 ms
Solution 13*2 : MIN: 78.3943 ms | MAX: 98.3158 ms | AVG: 83.7617 ms
Solution 2 : MIN: 87.62770 ms | MAX: 92.20400 ms | AVG: 91.17817 ms
Solution 13*1 : MIN: 87.9642 ms | MAX: 99.3946 ms | AVG: 91.8193 ms
Solution 5 : MIN: 168.75850 ms | MAX: 201.33640 ms | AVG: 175.94069 ms
*1 F#Solution
*2 PreCompiledF#Solution
-----------------------------------------------------------------------------------
ЗАПИСКА: с небольшой корректировкой теста, чтобы лучше отражать реальные примеры, многие из решений получили прирост производительности.
Наконец, если я неправильно использовал решение, Пожалуйста, дайте мне знать, чтобы я мог обновить результаты. Если какие-либо авторы внесут изменения, также, пожалуйста, дайте мне знать, и я позабочусь о том, чтобы тесты были повторно запущены и результаты опубликованы.
* старый тест:
скачать[
^]
* старый тест v2:
скачать[
^]
Stuart Dootson
Из inerest я преобразовал ваш бенчмаркинговый код в F#, чтобы измерить мое решение F#, и получил следующие тайминги:
Statistics
----------
Tests run: 10
Iterations/test: 100
-----------------------------------------------------------------------------
F# Solution - Basic: MIN = 2.6989, MAX = 3.2336, AVG = 2.78732
F# Solution - Bonus: MIN = 2.8122, MAX = 3.1169, AVG = 2.8664
Statistics
----------
Tests run: 10
Iterations/test: 1000
-----------------------------------------------------------------------------
F# Solution - Basic: MIN = 25.4413, MAX = 27.2665, AVG = 25.8091
F# Solution - Bonus: MIN = 28.5614, MAX = 30.4572, AVG = 29.0724
Graeme_Grant
Пришлите мне ссылку на код F#, и я запущу его здесь, чтобы обновить результаты. :)
Stuart Dootson
https://gist.github.com/studoot/f883c041cf6aaeaa1a752114963c16b7 Вы (конечно же) имели в виду код синхронизации - глупый я разместил ссылку на свой код решения... Я получу код хронометража, когда смогу добраться до него...
Stuart Dootson
Вот так - то оно и есть. Суть в GitHub
Самый простой способ для вас построить его, вероятно, создать новое консольное приложение F# в VS2015, вставить код в эту суть в исходный файл, созданный шаблоном, а затем построить/запустить вариант выпуска.
Graeme_Grant
Круто... Скомпилировал и запустил в первый раз! Никогда раньше не делал F#.
Я установил свой 3+летний MBP (Macbook Pro работает под управлением Win10 в bootcamp) так же, как и другие тесты скорости, и результаты были следующими:
-----------------------------------------------------------------------------
Tests run: 10
Iterations/test: 100
Basic Test
FsSolution - Basic : MIN: 7.8449 ms | MAX 8.8849 ms | AVG 8.52395 ms
*PreCompiled - Basic : MIN: 7.2831 ms | MAX 8.2619 ms | AVG 7.61122 ms
Bonus Test
FsSolution - Bonus : MIN: 7.7423 ms | MAX 8.4193 ms | AVG 7.92343 ms
*PreCompiled - Bonus : MIN: 7.2196 ms | MAX 10.7562 ms | AVG 7.76979 ms
-----------------------------------------------------------------------------
Tests run: 10
Iterations/test: 1000
Basic Test
FsSolution - Basic : MIN: 77.7693 ms | MAX 80.7415 ms | AVG 78.8205 ms
*PreCompiled - Basic : MIN: 72.5419 ms | MAX 77.3086 ms | AVG 73.9815 ms
Bonus Test
FsSolution - Bonus : MIN: 78.1498 ms | MAX 84.8175 ms | AVG 80.1699 ms
*PreCompiled - Bonus : MIN: 73.0482 ms | MAX 76.4278 ms | AVG 74.1332 ms
-----------------------------------------------------------------------------
Как только PIEBALDconsult закончит свой бит, я обновлю детали с помощью этой таблицы и добавлю ссылку на решение F#.
Stuart Dootson
Это становится еще более странным - этот код F# был частично разработан на рабочей станции Windows 10 (6-ядерный Xeon, около 3 ГГц - вот откуда мои предыдущие времена) и частично на моем (8-летнем) MBP с 2,4 ГГц Core2 Duo. В OS X, с кодом Mono и Visual Studio (с расширением Ionide). Сила кросс-платформенного .NET… В любом случае - мой Mac дал эти тайминги:
Tests run: 10
Iterations/test: 100
-----------------------------------------------------------------------------
Basic Test
FsSolution - Basic : MIN: 8.4053 ms | MAX 14.0585 ms | AVG 10.4869 ms
PreCompiledFsSolution - Basic : MIN: 7.2594 ms | MAX 10.2251 ms | AVG 8.18209 ms
Bonus Test
FsSolution - Bonus : MIN: 8.2866 ms | MAX 11.5506 ms | AVG 9.42119 ms
PreCompiledFsSolution - Bonus : MIN: 7.4998 ms | MAX 12.9918 ms | AVG 8.99166 ms
Tests run: 10
Iterations/test: 1000
-----------------------------------------------------------------------------
Basic Test
FsSolution - Basic : MIN: 88.4249 ms | MAX 93.8609 ms | AVG 90.9543 ms
PreCompiledFsSolution - Basic : MIN: 78.6023 ms | MAX 93.5715 ms | AVG 83.0625 ms
Bonus Test
FsSolution - Bonus : MIN: 87.9642 ms | MAX 99.3946 ms | AVG 91.8193 ms
PreCompiledFsSolution - Bonus : MIN: 78.3943 ms | MAX 98.3158 ms | AVG 83.7617 ms
Graeme_Grant
Глядя на средние значения, тайминги MBP выглядят в соответствии с тем, что я вижу.
PIEBALDconsult
Круто. Если вы использовали код, который я прикрепил к своему (3?), то он, вероятно, не самый последний, потому что я забыл обновить прикрепленный файл кода.
И, если будет сделано несколько запусков, я надеюсь, что вы не выбросили экземпляр между запусками.
Я обновил свой код и статью.
Graeme_Grant
Выше есть ссылка для скачивания, так что вы можете увидеть, как я ее использовал. ;)
PIEBALDconsult
Да, и VS 2010 не может его открыть. :D затем я отредактировал файлы решения и проекта, чтобы VS 2010 мог открыть его, и он не понимает бессмысленный код.
У меня также были проблемы с отправкой ранее. Я обновил свой код и статью.
Graeme_Grant
Ах... вы не используете C#6 в VS2015 ... Какую версию C# вы используете тогда в VS2010? Вы можете использовать VS2015express - это бесплатно... ;)
PIEBALDconsult
- Нет, не помню.
Это все бесплатно с MSDN от моего работодателя. :крутой:
Я скачал и установил VS 2015 на систему junker, и теперь я пытаюсь проверить ваши тесты.
Я отмечаю, что решение 10 не соответствует этим требованиям.
PIEBALDconsult
Нет, если он не заменит "получил" на "стал".
Graeme_Grant
Ах, фильтр плохих слов был установлен на бонус, а не на базовый - спасибо. Ошибка при настройке тестирования. Я обновлю тестовое решение.
Добавьте его к основному, и он пройдет с честью...
статический список<badword10> basic10BadWords = новый список<badword10>()
{
новый BadWord10("POOPHEAD","P**PHEAD",true),
новый BadWord10("poophead","p**phead",false),
новый BadWord10("PHB","boss",true),
новый BadWord10("getted","become",true)
};
статический список<badword10> bonus10BadWords = новый список<badword10>()
{
новый BadWord10("какашка*","П**Р",правда),
новый BadWord10("какашка*","п**р",ложные),
новый BadWord10("PHB!","boss",true)
};
Graeme_Grant
Пришлите мне свои исправления, и я добавлю их в следующее обновление.
PIEBALDconsult
Я не спал до 00:30, пытаясь что-то сделать. Я вернусь к этому сегодня вечером.
PIEBALDconsult
А также некоторые несбалансированные тесты. Например, наличие только "Бонусного" теста для моего (3) по сравнению с "базовыми" тестами для других.
Я собираюсь сделать basicReplacer и посмотреть, как он работает.
статический PIEBALD.Type.ReplaceOmatic replacerBasic = новый PIEBALD.Type.ReplaceOmatic
(
новый Кортеж<string, string="">("PHB", "boss"),
новый Кортеж<string, string="">("getted", "become"),
новый Кортеж<string, string="">("poophead", "p**phead")
);
Graeme_Grant
Почему вы считаете их неуравновешенными? Все тесты работают с одним и тем же кодом как на базовом, так и на бонусном тестах.
PIEBALDconsult
Но с разными конфигурациями.
Graeme_Grant
Конфигурация, да, возможно. Код, нет.
PIEBALDconsult
Возможно, вы также вызовете сборщик мусора между тестами, чтобы раздувание в одном решении не оказало отрицательного влияния на другие.
Graeme_Grant
Не называя ГХ между ними разница? Если это так, опубликуйте изменения, и я добавлю их тоже. Однако, судя по моим тестам, это не имеет никакого значения.
Graeme_Grant
Круто... Вы видели следующий вызов?
Рейтинг:
125
Peter Leow
Я читал о Python в интернете, а потом случайно наткнулся на это какающий ой Я имею в виду вызов кодирования, подумал, почему бы не попробовать это на Python. Вот она, свежая с самого начала. туалет Ой опять Я имею в виду печь.
"""
poop.py
by Peter Leow the pooper
"""
import re
def functionStartWithPoop(m):
wordFound = m.group(0)
if wordFound[:5].lower()=='poop*':
wordRepl = wordFound[0] + '**' + wordFound[3] + wordFound[5:]
else: #wordFound[:4].lower()=='poop':
wordRepl = wordFound[0] + '**' + wordFound[3] + wordFound[4:]
return wordRepl
def functionEndWithPoop(m):
wordFound = m.group(0)
if wordFound[-5:].lower()=='*poop':
wordRepl = wordFound[:-5] + wordFound[-4] + '**' + wordFound[-1]
else: #wordFound[-4:].lower()=='poop':
wordRepl = wordFound[:-4] + wordFound[-4] + '**' + wordFound[-1]
return wordRepl
def main():
originalSentence = '''
poop*ing is in front of make*poop.
Whether poop* or *poop, there are just pOoP!
A POOPHEAD cannot change but an exclaimed POOPHEAD! can.'''
print('Before:')
print(originalSentence)
print()
print('After:')
# Without ! ending
patternStartWithPoop=r'(?<!\S)poop\*?[\S]*'
patternEndWithPoop=r'[\S]*\*?poop(?=[?!,.;]?$|[?!,.;]?\s+)'
# with ! ending
patternStartWithPoopEndWithExclamation = r'(?<!\S)poop\*?[\S]*!(?=\s|$)'
patternEndWithPoopAndExclamation=r'[\S]*\*?poop!(?=[?!,.;]?$|[?!,.;]?\s+)'
# Case sensitive
filteredSentence = re.sub(patternStartWithPoop, functionStartWithPoop, originalSentence, flags=0)
#print(filteredSentence)
filteredSentence = re.sub(patternEndWithPoop, functionEndWithPoop, filteredSentence, flags=0)
#print(filteredSentence)
# Case ignorance
filteredSentence = re.sub(patternStartWithPoopEndWithExclamation, functionStartWithPoop, filteredSentence, flags=re.IGNORECASE)
#print(filteredSentence)
filteredSentence = re.sub(patternEndWithPoopAndExclamation, functionEndWithPoop, filteredSentence, flags=re.IGNORECASE)
print(filteredSentence)
main()
Попробуйте это сделать по адресу
Coding challenge bad word filter | Python Fiddle[
^] и вы должны увидеть следующий выход:
Before:
poop*ing is in front of make*poop.
Whether poop* or *poop, there are just pOoP!
A POOPHEAD cannot change but an exclaimed POOPHEAD! can.
After:
p**ping is in front of makep**p.
Whether p**p or p**p, there are just p**P!
A POOPHEAD cannot change but an exclaimed P**PHEAD! can.
Я игнорировал 'данные' и 'получил', поскольку они слишком тривиальны.
Chris Maunder
-я проигнорировал " PHB " и "Getty", поскольку они просто слишком тривиальны.
:) Кроме того, что они заставляют вас делать "начинается с" и "заканчивается с"
Peter Leow
Ты меня поймал!
PIEBALDconsult
Python никогда не является ответом.
Jon McKee
Если только вопрос не стоит так: "что такое нетрадиционная змея, обитающая в Африке, Азии и Австралии с некоторыми из самых крупных видов змей, известных в настоящее время в ее роде?"; D
Peter Leow
Может быть, на следующей неделе.
Peter Leow
П**п-это, надеюсь, что он также отфильтровывает запах.
Рейтинг:
117
Jon McKee
Не уверен в правилах, просто хотел добавить это для удовольствия. Я люблю сложные задачи с кодом.
Это решение, в отличие от моего другого, уменьшает количество строк, сброшенных во время обработки. Он также использует только один Regex
звоните, чтобы обработать все плохие слова. Несмотря на то, что я создаю дополнительный словарь, мой диагностический инструмент показывает, что я прихожу с меньшим потреблением памяти, чем мое предыдущее решение. Это решение делает все, что делал предыдущий, плюс добавляет дополнительный корпус.
public class BadWordFilter
{
public static string Replace(string input,
IDictionary<string, string> badWordMap)
{
if (string.IsNullOrWhiteSpace(input))
throw new ArgumentException(nameof(input),
"String cannot be null, empty, or whitespace.");
Dictionary<string, string> idMap = new Dictionary<string, string>();
StringBuilder pattern = new StringBuilder();
int idCounter = 0;
//For each bad word pair, create an ID mapped to the value and construct the match pattern using the ID and key
foreach (KeyValuePair<string, string> badWord in badWordMap)
{
string id = "ID" + idCounter++;
idMap.Add(id, badWord.Value);
ConstructMatchPattern(badWord.Key, id, pattern);
}
//Remove the first | from the pattern
pattern.Remove(0, 1);
Regex filter = new Regex(pattern.ToString(), RegexOptions.IgnoreCase);
string[] groupNames = filter.GetGroupNames();
MatchEvaluator evaluator = match =>
{
string replacement = "";
//Find which group was matched and retrieve the replacement value
for (int i = 1; i < groupNames.Length; i++)
if (match.Groups[groupNames[i]].Success)
{
replacement = idMap[groupNames[i]];
break;
}
//Handle casing
if (replacement.StartsWith("!"))
{
replacement = replacement.Remove(0, 1);
//All caps check
if (match.Value == match.Value.ToUpper())
replacement = replacement.ToUpper();
//First letter caps check
else if (match.Value[0] == char.ToUpper(match.Value[0]))
replacement = char.ToUpper(replacement[0]) + replacement.Substring(1);
}
return replacement;
};
return filter.Replace(input, evaluator);
}
private static void ConstructMatchPattern(string badWord, string id,
StringBuilder pattern)
{
if (string.IsNullOrWhiteSpace(badWord))
return;
int patternLength = pattern.Length;
pattern.Append($@"|(?<{id}>(?:\b){badWord.Trim('*')}");
if (badWord.StartsWith("*"))
pattern.Insert(patternLength + id.Length + 11, @"\w*", 1);
if (badWord.EndsWith("*"))
pattern.Append(@"\w*");
pattern.Append(')');
}
}
Используется так же, как и в предыдущем примере, с включением опции"!".
static void Main(string[] args)
{
Dictionary<string, string> badWords = new Dictionary<string, string>()
{
{"poop*", "p**phead"},
{"*HB", "boss"},
{"gotten", "become"},
{"*crap*", "taco supreme"}
};
string input = "My PHB is such a poophead. It's gotten worse since his promotion. In fact you might call him a supercraphead.";
string filteredInput = BadWordFilter.Replace(input, badWords);
Console.WriteLine(filteredInput);
Console.ReadKey();
}
Заменяющий
{"*HB", "boss"}
с
{"*HB", "!boss"}
уступит боссу вместо босса, так как он имеет дело с совпадением своего ключа.
PIEBALDconsult
Единственное правило - "никакой ненормативной лексики". :крутой:
Jon McKee
Я вижу, что ты там сделал /golfclap :)
Рейтинг:
1
Andrei Bozantan
На самом деле утверждение, что эта задача проста, вводит в заблуждение. Обработка текста никогда не бывает легкой, если вы хотите работать с Unicode. Вот мой взгляд на это.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
class WordFilter
{
private static Dictionary<string, string> badWords = null;
private static List<KeyValuePair<string, string>> badWordsStart = new List<KeyValuePair<string, string>>();
private static List<KeyValuePair<string, string>> badWordsEnd = new List<KeyValuePair<string, string>>();
private static List<KeyValuePair<string, string>> badWordsMiddle = new List<KeyValuePair<string, string>>();
private static Dictionary<string, string> badWordsCaseSensitive = new Dictionary<string, string>();
private static Dictionary<string, string> badWordsCaseInsensitive = new Dictionary<string, string>();
private static void Init()
{
if (badWords != null)
{
return;
}
badWords = new Dictionary<string, string>()
{
{"poop*", "p**p"},
{"PHB!", "boss"},
{"gotten", "become"},
{"*crap*", "taco supreme"}
};
foreach (var item in badWords)
{
bool startWord = item.Key.EndsWith("*");
bool endWord = item.Key.StartsWith("*");
bool caseSensitive = item.Key.EndsWith("!");
if (startWord && endWord)
{
badWordsMiddle.Add(new KeyValuePair<string, string>(item.Key.Trim('*').ToLower(CultureInfo.InvariantCulture), item.Value));
}
else if (startWord)
{
badWordsStart.Add(new KeyValuePair<string, string>(item.Key.TrimEnd('*').ToLower(CultureInfo.InvariantCulture), item.Value));
}
else if (endWord)
{
badWordsEnd.Add(new KeyValuePair<string, string>(item.Key.TrimStart('*').ToLower(CultureInfo.InvariantCulture), item.Value));
}
else if (caseSensitive)
{
badWordsCaseSensitive.Add(item.Key.TrimEnd('!'), item.Value);
}
else
{
badWordsCaseInsensitive.Add(item.Key.ToLower(CultureInfo.InvariantCulture), item.Value);
}
}
}
public WordFilter()
{
Init();
}
public string Filter(string s)
{
var word = new StringBuilder();
var sout = new StringBuilder();
foreach (var c in s.GetUTF32Chars())
{
if (c.IsLetter)
{
word.Append(c);
}
else
{
if (word.Length > 0)
{
var niceWord = Replace(word.ToString());
word.Clear();
sout.Append(niceWord);
}
sout.Append(c);
}
}
return sout.ToString();
}
private string Replace(string word)
{
string newWord;
if (badWordsCaseSensitive.TryGetValue(word, out newWord)) return newWord;
var lword = word.ToLower(CultureInfo.InvariantCulture);
if (badWordsCaseInsensitive.TryGetValue(word, out newWord)) return newWord;
newWord = badWordsStart.Where(it => word.StartsWith(it.Key)).Select(it => word.Replace(it.Key, it.Value)).FirstOrDefault();
if (newWord != null) return newWord;
newWord = badWordsEnd.Where(it => word.EndsWith(it.Key)).Select(it => word.Replace(it.Key, it.Value)).FirstOrDefault();
if (newWord != null) return newWord;
newWord = badWordsMiddle.Where(it => word.Contains(it.Key)).Select(it => word.Replace(it.Key, it.Value)).FirstOrDefault();
if (newWord != null) return newWord;
return word;
}
}
public static class StringExtensions
{
public static System.Collections.Generic.IEnumerable<UTF32Char> GetUTF32Chars(this string s)
{
var tee = System.Globalization.StringInfo.GetTextElementEnumerator(s);
while (tee.MoveNext())
{
yield return new UTF32Char(s, tee.ElementIndex);
}
}
}
public struct UTF32Char
{
private string s;
private int index;
public UTF32Char(string s, int index)
{
this.s = s;
this.index = index;
}
public override string ToString()
{
return char.ConvertFromUtf32(this.UTF32Code);
}
public int UTF32Code { get { return char.ConvertToUtf32(s, index); } }
public double NumericValue { get { return char.GetNumericValue(s, index); } }
public UnicodeCategory UnicodeCategory { get { return char.GetUnicodeCategory(s, index); } }
public bool IsControl { get { return char.IsControl(s, index); } }
public bool IsDigit { get { return char.IsDigit(s, index); } }
public bool IsLetter { get { return char.IsLetter(s, index); } }
public bool IsLetterOrDigit { get { return char.IsLetterOrDigit(s, index); } }
public bool IsLower { get { return char.IsLower(s, index); } }
public bool IsNumber { get { return char.IsNumber(s, index); } }
public bool IsPunctuation { get { return char.IsPunctuation(s, index); } }
public bool IsSeparator { get { return char.IsSeparator(s, index); } }
public bool IsSurrogatePair { get { return char.IsSurrogatePair(s, index); } }
public bool IsSymbol { get { return char.IsSymbol(s, index); } }
public bool IsUpper { get { return char.IsUpper(s, index); } }
public bool IsWhiteSpace { get { return char.IsWhiteSpace(s, index); } }
}
class Program
{
static void Main(string[] args)
{
var wf = new WordFilter();
var rawSentence = "My PHB is such a poophead. It's gotten worse since his promotion. In fact you might call him a supercraphead!";
var niceSentence = wf.Filter(rawSentence);
Console.WriteLine(rawSentence);
Console.WriteLine(niceSentence);
}
}
PIEBALDconsult
Как это работает с p00p и cr@p?
Рейтинг:
1
Harrison Pratt
Решение VISUAL PROLOG 7.5.
Примечание: предложения bad_nice/2 могут быть перемещены во внешний файл для обслуживания независимо от остальной части кода.
Это можно было бы привести в порядок больше, но я считал скорость подачи здесь приоритетом.
Харрисон Прэтт
class predicates
badWordFix : ( string TextToFilter ) -> string FixedText.
clauses
badWordFix( Text ) = FixedText :-
Splitters = " .,:!",
FixedText = fixText ( Text, Splitters, "" ).
class predicates
fixText : ( string, string Separators, string Temp ) -> string.
clauses
fixText( "",_,S ) = S :-!.
fixText( S, Separators, Temp ) = FixedText :-
string::splitStringBySeparators(S,Separators,HeadStr,CurrSep,Rest ),
!,
FixedText = fixText( Rest, Separators, string::concat( Temp, filteredWord(HeadStr), string::charToString(CurrSep) )).
fixText( S, Separators, Temp ) = FixedText :-
FixedText = fixText("",Separators,string::concat(Temp,S )).
class predicates
filteredWord : ( string ) -> string .
clauses
filteredWord( S ) = NiceWord :-
CleanS = stripPrefix( stripSuffix(S,"*"), "*" ),
bad_nice( CleanS, NiceWord ), !.
filteredWord( S ) = S.
class predicates
bad_nice : ( string, string [out] ) determ.
clauses
bad_nice( "poophead", "p***head" ).
bad_nice( "PHB", "boss" ).
bad_nice( "gotten", "become" ).
class predicates
stripPrefix : ( string, string ) -> string.
clauses
stripPrefix( S, Pre ) = StripStr :-
string::hasPrefix(S,Pre,StripStr), !.
stripPrefix( S, _ ) = S.
class predicates
stripSuffix : ( string, string ) -> string.
clauses
stripSuffix( S, Suff ) = StripStr :-
string::hasSuffix(S,Suff,StripStr), !.
stripSuffix( S, _ ) = S.
Рейтинг:
1
Stuart Dootson
Я решил использовать F#…
Каждая спецификация фильтра преобразуется в замыкание, которое принимает строку, потенциально очищает ее и возвращает хорошую строку. Чтобы обработать все слова в строке, используйте Regex.Replace с MatchEvaluator, который запускает список фильтров над соответствующим словом.
open System
open System.Text.RegularExpressions
// Define an active pattern to match a string starting with a prefix and return the remnant of the string
let (|Prefix|_|) (ignoreCase:bool) (prefix:string) (s:string) =
let maybePrefix = s.Substring(0, (min prefix.Length s.Length))
match String.Compare(maybePrefix, prefix, ignoreCase) with
| 0 -> Some(maybePrefix, s.Substring(prefix.Length))
| _ -> None
// Define an active pattern to match a string ending with a suffix and return the remnant of the string
let (|Suffix|_|) (ignoreCase:bool) (suffix:string) (s:string) =
let maybeSuffix = s.Substring((max 0 (s.Length - suffix.Length)))
match String.Compare(maybeSuffix, suffix, ignoreCase) with
| 0 -> Some(maybeSuffix, s.Substring(0, s.Length - suffix.Length))
| _ -> None
// Adjust case of a good word to reflect how the bad word's been used
let AdjustCase (suffix:string) (badBit:string) (goodWord:string) (ignoreCase:bool) =
if not ignoreCase then
goodWord
else if badBit = suffix.ToUpper() then
goodWord.ToUpper()
else if badBit = suffix.ToLower() then
goodWord.ToLower()
else
goodWord
// Create a filter (a closure of string -> string) from a filter spec (pair of strings,
// first = bad word, second = replacement). Applying the closure to a string will sanitise it.
let createFilter (spec:string*string) =
let rec createFilterHelper (badWord:string) (goodWord:string) (ignoreCase:bool) =
match badWord with
// Case sensitive?
| Suffix true "!" (_, prefix) -> createFilterHelper prefix goodWord false
// badWord is a prefix (<string>*)
| Suffix true "*" (_, prefix) ->
fun (word:string) ->
match word with
| Prefix ignoreCase prefix (badBit, restOfWord) -> (AdjustCase prefix badBit goodWord ignoreCase) + restOfWord
| anyOtherWord -> anyOtherWord
// badWord is a sufffix (*<string>)
| Prefix true "*" (_, suffix) ->
fun (word:string) ->
match word with
| Suffix ignoreCase suffix (badBit, restOfWord) -> restOfWord + (AdjustCase suffix badBit goodWord ignoreCase)
| anyOtherWord -> anyOtherWord
// badWord is fixed
| anyOtherWord ->
fun (word:string) ->
match String.Compare(word, badWord, ignoreCase) with
| 0 -> AdjustCase badWord word goodWord ignoreCase
| _ -> word
// Invoke createFilterHelper
createFilterHelper (fst spec) (snd spec) true
// Create a filter list from a spec list
let createFilters specs = specs |> List.map createFilter
// Apply a sequence of filters to a word
let filterAWord filters word = filters |> Seq.fold (fun word filter -> filter word) word
// Apply a sequence of filters to each word in a string
let filterWords filters text = Regex.Replace(text, "\w+", MatchEvaluator(fun regexMatch -> filterAWord filters regexMatch.Value))
// These are my test filter specs
let filterSpecs = [ ("poop*", "p**p"); ("PHB!", "boss") ; ("gotten", "become") ]
// And my test filters
let compiledFilters = createFilters filterSpecs
let tests =
printfn "Poophead -> p**phead = %b" ((filterAWord compiledFilters "Poophead") = "p**phead")
printfn "PHB -> boss = %b" ((filterAWord compiledFilters "PHB") = "boss")
printfn "Phb -> Phb = %b" ((filterAWord compiledFilters "Phb") = "Phb")
printfn "gotten -> become = %b" ((filterAWord compiledFilters "gotten") = "become")
printfn "<long string=""> - %b" ((filterWords compiledFilters "My PHB has started his new blog phblog.com. He's SUCH A MISBEGOTTEN POOPHEAD!") = "My boss has started his new blog phblog.com. He's SUCH A MISBEGOTTEN P**PHEAD!")
</long></string></string>
PIEBALDconsult
Указывает ли фильтр, было ли что-то заменено или нет?
Stuart Dootson
Нет, но они могли бы легко сделать это (вернуть кортеж bool &string?). Чтобы остановить фильтрацию после того, как сделаны замены, я полагаю? Если это так, то Seq.fold должен стать "Seq.map |> Seq.find" - это ленивая оценка, поэтому не все фильтры будут обработаны.
Stuart Dootson
На самом деле - самый простой способ добиться этого-это:
1. Измените фильтр, чтобы вернуть string option
(Some string
указывает, что фильтрация была выполнена)
2. Добавьте фильтр "null" (всегда возвращает входное слово) в конце списка фильтров, чтобы убедиться, что всегда есть следующий фильтр.
3. Измените filterAWord
функции:
let filterAWord (filters:(string -> string option) seq) (word:string) =
filters
|> Seq.map (fun filter -> filter word)
|> Seq.find Option.isSome
|> Option.get
Рейтинг:
0
Richard Deeming
Ура! Возможность использовать регулярные выражения, не вызывая старших богов! :)
Начните со структуры для преобразования строки в плохое слово, принимая во внимание правила "бонусных баллов" :
public struct BadWord
{
public BadWord(string word)
{
if (string.IsNullOrWhiteSpace(word)) throw new ArgumentNullException(nameof(word));
int startIndex = 0;
int length = word.Length;
// Skip leading / trailing white-space:
while (length > 0 && char.IsWhiteSpace(word[startIndex]))
{
startIndex++;
length--;
}
while (length > 0 && char.IsWhiteSpace(word[startIndex + length - 1]))
{
length--;
}
// If the word ends with "!", then it's a case-sensitive match:
if (length > 0 && word[startIndex + length - 1] == '!')
{
CaseSensitive = true;
length--;
}
else
{
CaseSensitive = false;
}
// If the word ends with "*", filter anything starting with the word:
if (length > 0 && word[startIndex + length - 1] == '*')
{
Suffix = "(?=\\w*\\b)";
length--;
}
else
{
Suffix = "\\b";
}
// If the word starts with "*", filter anything ending with the word:
if (length > 0 && word[startIndex] == '*')
{
Prefix = "(?<=\\b\\w*)";
startIndex++;
length--;
}
else
{
Prefix = "\\b";
}
Word = length != 0 ? word.Substring(startIndex, length) : null;
}
public string Word { get; }
public string Prefix { get; }
public string Suffix { get; }
public bool CaseSensitive { get; }
public Regex ToRegularExpression()
{
if (string.IsNullOrWhiteSpace(Word)) return null;
string pattern = Prefix + Regex.Escape(Word) + Suffix;
var options = CaseSensitive ? RegexOptions.ExplicitCapture : RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase;
return new Regex(pattern, options);
}
}
Затем класс для представления одного плохого слова и его замены:
РЕДАКТИРОВАТЬ: Теперь с той частью спецификации, которую "клиент" забыл упомянуть! :)
public sealed class WordReplacement
{
public WordReplacement(BadWord word, string replacement)
{
if (string.IsNullOrWhiteSpace(word.Word)) throw new ArgumentNullException(nameof(word));
Pattern = word.ToRegularExpression();
CaseSensitive = word.CaseSensitive;
Replacement = replacement;
if (CaseSensitive || replacement == null || replacement.Any(char.IsUpper))
{
Replacer = (Match m) => Replacement;
}
else
{
Replacer = (Match m) => MatchCase(m.Value, Replacement);
}
}
public WordReplacement(string word, string replacement) : this(new BadWord(word), replacement)
{
}
public Regex Pattern { get; }
public string Replacement { get; }
public bool CaseSensitive { get; }
public MatchEvaluator Replacer { get; }
public static string MatchCase(string wordToReplace, string replacement)
{
if (null == replacement) return string.Empty;
if (wordToReplace.All(char.IsLower)) return replacement;
if (wordToReplace.All(char.IsUpper)) return replacement.ToUpperInvariant();
char[] result = replacement.ToCharArray();
bool changed = false;
if (wordToReplace.Length == replacement.Length)
{
for (int index = 0; index < result.Length; index++)
{
if (char.IsUpper(wordToReplace[index]))
{
char c = result[index];
result[index] = char.ToUpperInvariant(c);
if (result[index] != c) changed = true;
}
}
}
else
{
if (char.IsUpper(wordToReplace[0]))
{
char c = result[0];
result[0] = char.ToUpperInvariant(c);
if (result[0] != c) changed = true;
}
if (char.IsUpper(wordToReplace[wordToReplace.Length - 1]))
{
int index = result.Length - 1;
char c = result[index];
result[index] = char.ToUpperInvariant(c);
if (result[index] != c) changed = true;
}
}
return changed ? new string(result) : replacement;
}
public string Replace(string input) => Pattern.Replace(input, Replacer);
}
И, наконец, класс для представления списка плохих замен слов:
public sealed class Clbuttifier2000
{
public Clbuttifier2000(IEnumerable<KeyValuePair<string, string>> replacements)
{
Replacements = replacements.Select(p => new WordReplacement(p.Key, p.Value)).ToList().AsReadOnly();
}
public IReadOnlyList<WordReplacement> Replacements { get; }
public string Clbuttify(string message)
{
if (!string.IsNullOrWhiteSpace(message))
{
foreach (var replacement in Replacements)
{
message = replacement.Replace(message);
}
}
return message;
}
}
Пример использования:
var filter = new Clbuttifier2000(new Dictionary<string, string>
{
["poop*"] = "p**p",
["PHB!"] = "boss",
["gotten"] = "become",
});
string input = "My PHB has gotten started on his new blog phblog.com. He's SUCH A MISBEGOTTEN Poophead!";
string expected = "My boss has become started on his new blog phblog.com. He's SUCH A MISBEGOTTEN P**phead!";
string actual = filter.Clbuttify(input);
Debug.Assert(actual == expected);
Chris Maunder
Отлично сработано. Однако много строковых распределений. Теперь: делает ли он "Poophead" -> "P**phead"? Этого не было в спецификации, но я не думаю, что это так.
Richard Deeming
Бл**дые клиенты! Вы следуете спецификациям, а затем они жалуются, что он не делает то, что они забыли упомянуть в спецификациях, и это внезапно становится самой важной частью! :)
Richard Deeming
Обновлено для учета отсутствующего требования. :)
Все еще много строковых распределений в Clbuttify
цикл, поэтому ответ, который не использует регулярные выражения, может быть лучше. Но они все должны быть gen-0, так что вам это может сойти с рук.
Chris Maunder
Вы должны попробовать писать опросы один день ;)
Ваш ответ решает проблему. Суть в том, чтобы сделать вопрос немного свободным, чтобы позволить изобретать, совершенствовать и, самое главное, бессмысленные религиозные войны.
Nelek
разрешить бессмысленные религиозные войны за пределами мыльницы? Это создаст прецедент... :rolleyes:
Рейтинг:
0
David O'Neil
РЕДАКТИРОВАТЬ:
* Разобрался как сделать poopoopoop "переходит в" п**р**р**р' (без рекурсии) и другие конструктивные ограничения.
* Придумал "О да - это так просто!" способ сохранить "nincompoop" как "nincompoop" - просто замените его самим собой!
* Сделал класс статичным, что привело к улучшению примерно на 8%. Но п**п**п**п' рутина (replaceRepeatingStrings) за скорость от 0,24 до 0,37 МС МС. Все еще в 7 раз быстрее, чем решение 10 на моей машине.
-КОНЕЦ РЕДАКТИРОВАНИЯ
Цитата:
Смысл в том, чтобы сделать вопрос немного свободным, чтобы позволить ... бессмысленные религиозные войны
Хорошо, я использую несколько "Гото". :)
Я не очень хорошо знаком с регулярным выражением, но считаю, что оно не позволяет вам изменять падеж слова на основе падежей окружающих слов. (Если я ошибаюсь, пожалуйста, просветите меня.) если это так, то " PHB "всегда будет становиться "боссом" в решениях регулярных выражений, независимо от того, "кричал" ли вход фразу.
Следующее решение полностью соответствует C++ и примерно в 7 раз быстрее, чем решение 10 на моей машине, использующее новейшую процедуру синхронизации Гранта. Он действительно учитывает заглавные буквы окружающих слов. Она также будет изменить 'pooppoop" до "п**пп**П', который, кажется, не были указаны в спецификации, хотя по логике должно произойти. Кроме того, он изменяет 'poopoopoop" в "п**п**п**п' без рекурсии.
Цитата:
Еженедельная простая программная задача, которая должна занимать не более получаса или около того
Да, верно, Крис... :rolleyes: эта логика была гребаной болью!
#include <map>
#include <string>
#include <algorithm>
#include <vector>
#include <tuple>
#include <ctime>
#include <iostream>
#include "windows.h" //For QueryPerformanceCounter
using namespace std;
//I am going to use a tuple of: the input word, the small case output word, and the
//capped case output word, so they don't have to be calculated each time.
//So some defines are just for additional clarity:
#define GetTupleWord get<0>
#define GetTupleSmallCase get<1>
#define GetTupleCapCase get<2>
//Utility class for the timing...
class TimerLowRes {
private:
std::clock_t begC;
std::clock_t avgTotC;
std::clock_t diffC;
int numTimesC;
public:
TimerLowRes() : avgTotC(0), numTimesC(0) { }
void start() { begC = std::clock(); }
std::clock_t stop() {
diffC = std::clock() - begC;
avgTotC = avgTotC + diffC;
numTimesC++;
return diffC;
}
std::clock_t getAvg() {
if (numTimesC == 0) return 0;
return avgTotC / numTimesC;
}
void reset() {
numTimesC = 0;
avgTotC = 0;
}
std::clock_t getLapTime() { return std::clock() - begC; }
};
//High precision timer utility class for the timing.
//Derived from https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
class TimerHighRes {
private:
LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds, Frequency;
int numTimesC;
public:
TimerHighRes() : numTimesC(0) {
QueryPerformanceFrequency(&Frequency);
}
void start() {
QueryPerformanceCounter(&StartingTime);
}
LARGE_INTEGER stop() {
QueryPerformanceCounter(&EndingTime);
ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedMicroseconds.QuadPart *= 1000000;
ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
numTimesC++;
return ElapsedMicroseconds;
}
};
class Reformatter {
private:
//Good chance that the implied design requirements involve UNICODE text, but for quick
//prototype of similar design use wstring:
typedef tuple<wstring, wstring, wstring> WordTuples;
vector<WordTuples> repeatingStringsC; //This will address items like 'poopoopoop'
vector<WordTuples> caseSensitiveSubsC; //This means that the inputted word must be
//matched on exact case.
vector<WordTuples> nonCaseSensitiveSubsC; //By this, it is meant that the inputted word
//can be either caps or small case in the text.
vector<WordTuples> asteriskSubsC; //The CapCase will be ignored - only the
//asterisks in the small case will be modified.
wstring resultC;
size_t strLenC;
wstring::const_iterator beginLocC;
wstring::const_iterator curLocC;
wstring::const_iterator endLocC;
bool replaceAsterisks();
bool replaceNonCaseSensitiveBegin();
bool replaceCaseSensitive();
bool replaceRepeatingStrings();
bool previousWordIsCapitalized(wstring::const_iterator & thisWord, wstring::const_iterator & prevWord);
bool nextWordIsCapitalized(wstring::const_iterator & thisWord, wstring::const_iterator & nextWord);
void findBeginningOfPrevious(wstring::const_iterator & thisWord, wstring::const_iterator & prevWord);
void findBeginningOfNext(wstring::const_iterator & thisWord, wstring::const_iterator & nextWord);
bool isWhiteSpaceOrPunctuation(const wstring::const_iterator & temp);
public:
Reformatter() {
repeatingStringsC.push_back(make_tuple(L"poo", L"p**", L"p"));
caseSensitiveSubsC.push_back(make_tuple(L"PHB", L"boss", L"BOSS"));
//The following 'gotten to be' must be defined before 'gotten':
nonCaseSensitiveSubsC.push_back(make_tuple(L"gotten to be", L"become", L"BECOME"));
nonCaseSensitiveSubsC.push_back(make_tuple(L"gotten", L"become", L"BECOME"));
asteriskSubsC.push_back(make_tuple(L"nincompoop", L"nincompoop", L"nincomp##p"));
asteriskSubsC.push_back(make_tuple(L"poop", L"p**p", L"P**P"));
asteriskSubsC.push_back(make_tuple(L"p##p", L"p**p", L"P**P"));
asteriskSubsC.push_back(make_tuple(L"ass", L"a**", L"A**"));
}
void reformat(const wstring & str);
void outputResult();
};
void Reformatter::outputResult() {
wcout << L"OUTPUT: " << resultC << endl << endl;
}
bool Reformatter::isWhiteSpaceOrPunctuation(const wstring::const_iterator & it) {
if (*it == L' ' || *it == L'\t' || *it == L'.' || *it == L'!' || *it == L'_' ||
*it == L'\r' || *it == L'\n') return true;
return false;
}
void Reformatter::findBeginningOfNext(wstring::const_iterator & thisWord, wstring::const_iterator & nextWord) {
//There is the possibility that there is no next word, but there is whitespace.
//If that is the case, return prevWord = thisWord = curLocC.
//Go to the whitespace at the end of the current word:
while (thisWord != endLocC && !isWhiteSpaceOrPunctuation(thisWord)) ++thisWord;
//Move 'thisWord' back to the beginning of the whitespace:
nextWord = thisWord;
//Now skip any additional whitespace:
while (nextWord != endLocC && isWhiteSpaceOrPunctuation(nextWord)) ++nextWord;
if (nextWord == endLocC) {
nextWord = endLocC;
thisWord = endLocC;
return;
}
}
void Reformatter::findBeginningOfPrevious(wstring::const_iterator & thisWord, wstring::const_iterator & prevWord) {
//There is the possibility that there is no previous word, but there is whitespace.
//If that is the case, return prevWord = thisWord = (beginning of word).
//Go to the whitespace before the current word:
while (thisWord != beginLocC && !isWhiteSpaceOrPunctuation(thisWord)) --thisWord;
//Move 'thisWord' back to the beginning (one space forward), and set 'prevWord' current pos:
prevWord = thisWord;
++thisWord;
//Now skip any additional whitespace:
while (prevWord != beginLocC && isWhiteSpaceOrPunctuation(prevWord)) --prevWord;
if (prevWord == beginLocC) {
if (isWhiteSpaceOrPunctuation(prevWord)) prevWord = thisWord;
return;
}
//We are now on the last character of the previous word. Iterate to the beginning of it:
while (prevWord != beginLocC && !isWhiteSpaceOrPunctuation(prevWord)) --prevWord;
//Check for the case where the user starts the input with a space character:
if (isWhiteSpaceOrPunctuation(prevWord)) ++prevWord;
}
bool Reformatter::previousWordIsCapitalized(wstring::const_iterator & thisWord,
wstring::const_iterator & prevWord) {
//We are working from the 'curLocC' position in the string.
//Create a temporary iterator and find the beginning of the previous word.
//If it reaches the beginning of the string, return 'true' so the 'shouting'
//routines only rely on the following word.
findBeginningOfPrevious(thisWord, prevWord);
if (thisWord == prevWord) return true; //We will default to 'true' for the previous word
//and 'false' for the next word, so next word processing can occur.
//Now find the case of each letter until the next whitespace:
while (!isWhiteSpaceOrPunctuation(prevWord)) {
wchar_t temp = *prevWord;
if (iswlower(*prevWord)) return false;
++prevWord;
}
return true;
}
bool Reformatter::nextWordIsCapitalized(wstring::const_iterator & thisWord, wstring::const_iterator & nextWord) {
//We are working from the 'curLocC' position in the string.
//Create a temporary iterator and find the beginning of the previous word.
//If it reaches the beginning of the string, return 'true' so the 'shouting'
//routines only rely on the following word.
findBeginningOfNext(thisWord, next
Robert g Blair
Дэвид - мое решение COBOL заняло всего 15 минут, чтобы написать (и я не кодировал COBOL в течение многих лет).
И он будет бежать ослепительно быстро. Все обрабатывается в одной встроенной, оптимизированной компилятором команде.
Я не добавил "лишние" биты, которые у вас есть, потому что их не было в спецификации, и я не хотел тратить на это время.
Для реализации "pooppoop" -> "p**pp**p" - это простая операция замены.
Для реализации "poopoopoop" - и GT; "п**р**р**р" повлечет за собой замену "ПОО" перед первым "П**" - и рекурсией.
David O'Neil
Мне нравится простота вашего решения COBOL! Я вижу в своей голове эквивалент C++ STL, который был бы немного длиннее, но сохраняйте свою простоту. Из любопытства, можете ли вы определить, как быстро ваша процедура сравнивается с выходом Грэма для решения 10 на вашем компьютере? Мне просто любопытно, насколько эффективны внутренние механизмы - я никогда с ними не играл.
Ваш комментарий "замена "ПУ" перед первоначальным "п**"" вызвал мой новый подход к решению этой части проблемы - без рекурсии! Спасибо!
Robert g Blair
Дэвид - это очень трудно сравнить.
Если вы удалите оператор Display, он будет работать менее чем за миллисекунду, скажем, на мэйнфрейме Fujitsu.
Он работает за пару миллисекунд на коробке Ubuntu с GnuCobol.
Но то, что я кодировал, было очень удобно для компилятора - жестко закодированные литералы, один оператор и т. д.
К вашему сведению: когда я говорю "внутренний", я имею в виду в этом смысле: https://en.wikipedia.org/wiki/Intrinsic_function
То есть компилятор оптимизирует обработку строк, участвующих в выполнении инструкции INSPECT. Делает ли он это хорошо или нет, зависит от компетентности компилятора.
Рейтинг:
0
Jon McKee
Правка: Ах, какие чудеса может сотворить сон. Несмотря на то, что решение уже было выбрано, у меня была идея улучшить свое решение.
Это было весело. К сожалению, я не слишком часто использую регулярные выражения в своей повседневной жизни.
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
public class BadWordFilter
{
public static void Replace(ref string input,
IDictionary<string, string> badWordMap)
{
if (string.IsNullOrWhiteSpace(input))
throw new ArgumentException(nameof(input),
"String cannot be null, empty, or whitespace.");
foreach (KeyValuePair<string, string> badWord in badWordMap)
ReplaceBadWord(ref input, badWord);
}
private static void ReplaceBadWord(ref string input,
KeyValuePair<string, string> badWord)
{
if (string.IsNullOrWhiteSpace(badWord.Key))
throw new ArgumentException(nameof(badWord.Key),
"Key cannot be null, empty, or whitespace.");
string pattern = GetReplacementPattern(badWord.Key);
MatchEvaluator evaluator = match =>
{
string replacement = badWord.Value;
if (match.Value == match.Value.ToUpper())
{
if (badWord.Key != badWord.Key.ToUpper())
replacement = badWord.Value.ToUpper();
}
else if (match.Value[0] == char.ToUpper(match.Value[0]))
replacement = char.ToUpper(badWord.Value[0]) + badWord.Value.Substring(1);
return replacement;
};
input = Regex.Replace(input, pattern, evaluator, RegexOptions.IgnoreCase);
}
private static string GetReplacementPattern(string badWordKey)
{
StringBuilder pattern = new StringBuilder(
$@"(?:\b){badWordKey.Trim('*')}"
);
if (badWordKey.StartsWith("*"))
pattern.Insert(6, @"\w*", 1);
if (badWordKey.EndsWith("*"))
pattern.Append(@"\w*");
return pattern.ToString();
}
}
Ты используешь его вот так:
static void Main(string[] args)
{
Dictionary<string, string> badWords = new Dictionary<string, string>()
{
{"poop*", "p**phead"},
{"*HB", "boss"},
{"gotten", "become"},
{"*crap*", "taco supreme"}
};
string input = "My PHB is such a poophead. It's gotten worse since his promotion. In fact you might call him a supercraphead!";
BadWordFilter.Replace(ref input, badWords);
Console.WriteLine(input);
Console.ReadKey();
}
//Output:
//My boss is such a p**phead. It's become worse since his promotion. In fact you might call him a taco supreme!
Ручки *текст*, и *текст* подстановочные знаки. Не будет прописывать замену слова, которое по умолчанию пишется с заглавной буквы (напр. PHB -> босс). При необходимости поддерживает заглавную букву первой буквы. Также поддерживайте любой код регулярных выражений, который вы хотели бы поместить в ключ, так как я непосредственно вводлю его. Например
{"p[oO0]+p*", "p**phead"}
будет ловить какашки, какашки, p0OPhead или даже PoO0o0Ophead.
Рейтинг:
0
Kornfeld Eliyahu Peter
Я видел, что никто не обращался к самолету JavaScript...
Я сделал...
function Bad2Good(text)
{
var T = {
'poophead': 'p**phead',
'PHB!': 'boss',
'gotten': 'become',
'poop*': 'p**p',
'*poop': 'p**p'
};
for (var prop in T)
{
var flags = 'ig';
// starts with
if (prop[prop.length-1] == '*')
{
prop = /(\s|[!?.,])poop(\w*)/;
}
// ends with
if (prop[0] == '*')
{
prop = /(\w*)poop(\s|[!?.,])/;
}
// case sensitive
if (prop[prop.length - 1] == '!')
{
flags = 'g';
prop = prop.substr(0, prop.length - 1);
}
text = text.replace(new RegExp(prop, flags), function (match, p1, p2, p3, p4)
{
if (Number.isInteger(p1))
{
if (match.toUpperCase() == match)
{
// case sensitive
if (T[match.toLowerCase()] == undefined)
{
return (T[match + '!']);
}
// shouting
else
{
return (T[match.toLowerCase()].toUpperCase());
}
}
// normal
else
{
return (T[match]);
}
}
else
{
// starts with
if(/(\s|[!?.,])/.test(p1))
{
match = match.substr(p1.length, match.length - p1.length - p2.length);
return (p1 + ((match.toUpperCase() == match) ? T[match.toLowerCase() + '*'].toUpperCase() : T[match.toLowerCase() + '*']) + p2);
}
// ends with
else if (/(\s|[!?.,])/.test(p2))
{
match = match.substr(p1.length, match.length - p1.length - p2.length);
return (p1 + ((match.toUpperCase() == match) ? T['*' + match.toLowerCase()].toUpperCase() : T['*' + match.toLowerCase()]) + p2);
}
else
{
return (match);
}
}
});
}
return (text);
}
Добраться до дома. Есть время. Увидел несколько моих жучков и разбил их...
function Bad2Good(text)
{
var T = {
'PHB!': 'boss',
'gotten': 'become',
'poop*': 'p**p',
'*poop': 'p**p'
};
for (var prop in T)
{
var flags = 'ig';
// case sensitive
if (prop[prop.length - 1] == '!')
{
flags = 'g';
prop = prop.substr(0, prop.length - 1);
}
// starts with
if (prop[prop.length - 1] == '*')
{
prop = '(\\b)' + prop.substr(0, prop.length - 1) + '(\\w*)(\\b)';
}
// ends with
if (prop[0] == '*')
{
prop = '(\\b)(\\w*)' + prop.substr(1, prop.length) + '(\\b)';
}
text = text.replace(new RegExp(prop, flags), function (match, len, p1)
{
var lookup;
var replace;
var good;
if(Number.isInteger(len))
{
replace = match;
lookup = match;
}
else
{
if(match.startsWith(p1))
{
replace = match.replace(p1, '');
lookup = '*' + replace;
}
else
{
replace = match.replace(p1, '');
lookup = replace + '*';
}
}
good = T[lookup.toLowerCase()] || T[lookup + '!'];
if(T[lookup + '!'] == undefined)
{
if((replace.toUpperCase() == replace))
{
good = good.toUpperCase();
}
else if((replace.toLowerCase() == replace))
{
good = good.toLowerCase();
}
}
return(match.replace(replace, good));
});
}
return (text);
}
PIEBALDconsult
Ваши замены жестко запрограммированы?
Kornfeld Eliyahu Peter
Как жук... Я только что обновился с домашней версией :-)
Рейтинг:
0
ernst zwecker
Я фанат чистого кода.
Что для меня по крайней мере так:
- Оно должно быть выразительным-> многозначительные имена
- Чем меньше, тем лучше -> методы должны быть короткими
- Может быть легко расширен любым другим разработчиком -> При необходимости изменения легко адаптируются
- Чтение кода должно быть приятным-потому что вы можете следовать логике, не перенапрягая свой мозг
Вот он (BadWordsMatch.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text;
using System.Text.RegularExpressions;
namespace BadWords
{
public enum eHow { easy, hard };
public class BadWordsMatchAndReplace
{
public enum eMatchMode { startsWith, Endswith, Equals };
public struct sWildCardAndCase
{
public eMatchMode matchMode;
public bool respectCase;
public string key;
}
public static Dictionary<sWildCardAndCase, string> dictBadWordsEasy = new Dictionary<sWildCardAndCase, string>(){
{new sWildCardAndCase() { matchMode = eMatchMode.Equals, respectCase = true, key ="poophead"}, "p**phead"},
{new sWildCardAndCase() { matchMode = eMatchMode.Equals, respectCase = true, key ="PHB"}, "boss"},
{new sWildCardAndCase() { matchMode = eMatchMode.Equals, respectCase = true, key ="gotten"} , "become"}
};
public static Dictionary<sWildCardAndCase, string> dictBadWordsHard = new Dictionary<sWildCardAndCase, string>(){
{new sWildCardAndCase() { matchMode = eMatchMode.startsWith, respectCase = false, key = "poop" }, "p**p" },
{new sWildCardAndCase() { matchMode = eMatchMode.Equals, respectCase = true, key = "PHB" }, "boss" },
{new sWildCardAndCase() { matchMode = eMatchMode.Equals, respectCase = false, key = "gotten" }, "become" }
};
// shouting is retrieved by allowing an exclamation mark in words and later on compared to last char therein
public static Regex r = new Regex(@"([!\w])+", RegexOptions.None);
public static bool shouting = false;
/// <summary>
/// checks an input sentence for bad words and replaces them by safer expressions
/// </summary>
/// <param name="input">sentence to check</param>
/// <param name="how">easy or hard substitution rule</param>
/// <returns>corrected string</returns>
///
public static string MatchAndReplace(string input, eHow how)
{
string output = input;
string s = string.Empty;
shouting = false;
Dictionary<sWildCardAndCase, string> baseDict = (how == eHow.easy ? dictBadWordsEasy : dictBadWordsHard);
Match m = r.Match(input);
while (m.Success)
{
s = m.Value;
bool found = false;
foreach (KeyValuePair<sWildCardAndCase, string> kvp in baseDict)
{
bool respectCase = kvp.Key.respectCase;
if ( s.EndsWith("!"))
{
// case insensitive and make replacement uppercase
shouting = true;
respectCase = false;
}
// looks for matches under given constraints and updates output accordingly
found = ProcessWord(s, kvp.Key.key, respectCase, kvp.Key.matchMode, kvp.Value, ref output);
if (found) break;
}
m = m.NextMatch();
}
return output;
}
/// <summary>
/// looks for a match of word under given constraints and updates output accordingly
/// </summary>
/// <param name="word">word from input string</param>
/// <param name="key">bad word</param>
/// <param name="respectCase">match only case exactly</param>
/// <param name="matchMode">wildcard specifier</param>
/// <param name="value">replacement</param>
/// <param name="output">output -> output with replacement</param>
/// <returns>no match -> false else true</returns>
///
private static bool ProcessWord(string word, string key, bool respectCase, eMatchMode matchMode, string value, ref string output)
{
if (respectCase && !word.Contains(key)) return false;
if (!respectCase && !word.ToLower().Contains(key.ToLower())) return false;
if (!ConstraintsMet( word, key, respectCase, matchMode)) {
return false;
}
if (respectCase)
{
output = output.Replace(key, value);
}
else
{
if (word.Any(char.IsUpper))
output = output.Replace(key.ToUpper(), value.ToUpper());
else if (shouting)
output = output.Replace(key, value.ToUpper());
else
output = output.Replace(key, value);
}
return true;
}
/// <summary>
/// checks for constraints
/// </summary>
/// <param name="word">word from input string</param>
/// <param name="key">bad word</param>
/// <param name="respectCase">match only case exactly</param>
/// <param name="matchMode">wildcard specifier</param>
/// <returns>constraints met -> true else false</returns>
///
private static bool ConstraintsMet( string word, string key, bool respectCase, eMatchMode matchMode)
{
string wordToCompare = word;
if (shouting) { wordToCompare = wordToCompare.TrimEnd('!'); };
switch (matchMode)
{
case eMatchMode.Equals:
if (respectCase && !wordToCompare.Equals(key)) { return false; }
else if (!respectCase && !wordToCompare.ToLower().Equals(key.ToLower())) { return false; }
break;
case eMatchMode.startsWith:
if (respectCase && !wordToCompare.StartsWith(key)) { return false; }
else if (!respectCase && !wordToCompare.ToLower().StartsWith(key.ToLower())) { return false; }
break;
case eMatchMode.Endswith:
if (respectCase && !wordToCompare.EndsWith(key)) { return false; }
else if (!respectCase && !wordToCompare.ToLower().EndsWith(key.ToLower())) { return false; }
break;
}
return true;
}
}
}
Это должно быть самоописанием.
Самое главное: я концентрирую требования о чувствительности к регистру и подстановочным правилам в структуре данных, которая освобождает меня от поиска *s Перед или
после ключа поиска.
Для "легкой части" у меня не было бы необходимости в этом, но однажды я сделал это для трудной
часть хорошо иметь его и для этой части, потому что код может быть один для обоих
(
Dictionary<sWildCardAndCase, string> baseDict = (how == eHow.easy ? dictBadWordsEasy : dictBadWordsHard);
Для тестирования моего кода я предоставил небольшую консоль Main, которая находится здесь (Program.cs):
using System;
using System.Collections.Generic;
using System.Linq;
namespace BadWords
{
class Program
{
static void Main(string[] args)
{
string input = string.Empty;
string s = string.Empty;
string output = string.Empty;
while (!(s.Equals("q")))
{
while (!(s = Console.ReadLine()).Equals("."))
{
input = input + s;
}
Console.WriteLine("------------- Easy Match ---------------");
Console.WriteLine(input);
output = BadWordsMatchAndReplace.MatchAndReplace(input, eHow.easy);
Console.WriteLine(output);
Console.WriteLine("------------- Bonus Match ---------------");
Console.WriteLine(input);
output = BadWordsMatchAndReplace.MatchAndReplace(input, eHow.hard);
Console.WriteLine(output);
Console.WriteLine("-------------- Next Input ---------------");
s = Console.ReadLine();
input = s;
}
}
}
}
Использование: Вставьте свой вход (многоканальный пускают), потом типа &ЛТ;возвращение&ГТ;.&ЛТ;возвращение&ГТ;
Если вы хотите прекратить эту программу, то типа &ЛТ;возвращение&ГТ;г&ЛТ;возвращение&ГТ;
PIEBALDconsult
О, человечество! Расточительно, расточительно. Вы выбрасываете совершенно хорошую информацию, которая может быть использована позже.
Вы даже не допускаете, чтобы несколько ключей соответствовали слову.