Chris Maunder Ответов: 17

Проблема кодирования: фильтр плохих слов


Это первая из наших еженедельных задач по кодированию. Раз в неделю мы публикуем простую программную задачу, и человек, который публикует лучший ответ, выигрывает футболку. Победитель определяется голосами, либо комментариями, либо смелостью их ответа. Используйте в своем ответе любой язык, кроме ненормативной лексики.

Представьте себе, что вы разрешаете пользователям вашего сайта оставлять комментарии, но некоторые пользователи, в основном австралийцы, становятся немного красочными со своим языком. Вы решили внедрить фильтр плохих слов, который заменит определенные слова в предложении более безопасными версиями этого слова.

Список плохих слов и их замен таков

Плохо: "poophead", "PHB", "gotten"
Замена: "p**phead", "boss", "become"

Итак, "мой PHB-такой придурок. Все стало еще хуже с тех пор, как его повышение" должно быть", мой босс-такой п**ФЭД. С тех пор как его повысили, стало еще хуже".

Мы также должны позволить себе кричать. Так

"Мой ФБ такой придурок!" должно стать "Мой босс такой придурок!"

Призовое очко:

Давайте сделаем это сложнее. Если "плохое" слово начинается с"*", то это означает любое слово, которое заканчивается этим словом. Если она заканчивается звездой, то любое слово, начинающееся с нее. Если он заканчивается на"!", то это означает, что он должен делать матч с учетом регистра.

Плохие слова: "poop*", "PHB!", "gotten"
Замена: "п**п", "босс", "стать"

"Мой PHB начал свой новый блог phblog.com-да. Он такой мерзкий придурок!"

должна стать

"Мой босс начал свой новый блог phblog.com-да. Он такой мерзкий ублюдок!"

Что я уже пробовал:

Помните: можно использовать любой язык программирования.

Suvendu Shekhar Giri

Это действительно потрясающая инициатива. Скоро мы найдем решение. Спасибо Крис :)

PIEBALDconsult

"Мой PHB-такой придурок!" должно стать "Мой ПГБ босс это такой п**ФЭД!"
Так ведь?

Chris Maunder

Правильно, и спасибо, что указали на это. Обновленный

Jochen Arndt

Крайний срок для публикации ответов?

Chris Maunder

В конце дня? В понедельник? Мы довольно спокойно относимся к этому, но есть предел нашему вниманию.

PIEBALDconsult

Оооо... если у меня есть локальная переменная, указывающая на слово, и я называю ее tw, это нормально, но я не могу иметь другую локальную переменную, указывающую на слово после этого имени twat?

Chris Maunder

Я тебя игнорирую.

PIEBALDconsult

Пффф. Это не так, как я спрашивал о первом неразрешенном ядре символов.

Chris Maunder

Если бы я мог поднять комментарии, я бы это сделал.

Dave Kreskowiak

Будет ли добавление этого следующим вызовом для кодирования?

PIEBALDconsult

- На Рождество я получила фею сахарной сливы."

Chris Maunder

Видите, как ужасно и ненужно это слово? Мы делаем миру одолжение здесь.

PIEBALDconsult

Я уверена, что он просто не успел исправить свою дурную привычку.

Aless Alessio

Не ясно различие поведения, когда '!' принадлежит слову в словаре из запрещенных слов (напр.: "PHB!") и когда оно встречается в комментарии (напр.: ".. POOPHEAD!")

Спасибо

PIEBALDconsult

Я звоню буллпупу.

Jörgen Andersson

Насчет обработки совершенно законно такие слова, как размазня?

PIEBALDconsult

Кто-то сказал "какашки"?

PIEBALDconsult

Может быть, вам следовало сделать это еще одним конкурсом "код скудный и злой".

Jon McKee

Не могу дождаться еще одного. Я люблю такие вещи =D

Graeme_Grant

Это было интересно... Спасибо! С нетерпением жду следующего! :)

thewazz

2 опечатки в 1-м пункте [be - > by].

thewazz

2 опечатки в 1-м пункте [be - > by].

Robert g Blair

Мне это нравилось. Я не кодировал в COBOL уже 30 лет.

Следующий Я сделаю на UCSD Pascal, или RPG III, или, может быть, FORTRAN IV.
(в зависимости от проблемы).

Robert g Blair

Крис:

Я считаю, что уместно дать награду за "наименьшее количество строк кода".

Очень объективная метрика, и служит более высокой цели борьбы с раздуванием кода.

17 Ответов

Рейтинг:
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 не соответствует этим требованиям.

Graeme_Grant

LMAO...

Я закатил глаза. Версия Dot Net 4.0 для вас с помощью VS2013

Решение 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

Не называя ГХ между ними разница? Если это так, опубликуйте изменения, и я добавлю их тоже. Однако, судя по моим тестам, это не имеет никакого значения.

Stuart Dootson

Итак, я сделал версию этого вызова Haskell (см. ниже) и использовал Пакет критерий сравнительного анализа При этом создается HTML-отчет, показывающий среднее время выполнения итерации. Вот один из них для моего решения Haskell.:



Это аккуратная библиотека/инструмент...

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

Рейтинг:
1

Stuart Dootson

Решение Хаскелла… По сути, грубый перевод моего F# one!

{-# LANGUAGE OverloadedStrings, TemplateHaskell, QuasiQuotes #-}

module Main where

import Criterion.Main
import Data.CaseInsensitive (mk, CI)
import Data.Char as C
import Data.List (find)
import Data.Maybe (fromJust, isJust)
import Data.Stringable
import Data.Text (Text)
import qualified Data.Text as T
import Text.Regex.PCRE.Heavy

data FilterSpec = FilterSpec { badPattern::Text, goodWord::Text }
type FilterSpecs = [FilterSpec]
type Filter = Text -> Maybe Text
type Filters = [Filter]

double :: Text -> Text
double match = (match `T.append` match)

wordRegex = [re|\w+|]

stringsAreEqual ignoreCase left right
  | ignoreCase && (mk left) == (mk right) = True
  | (not ignoreCase) && left == right = True
  | otherwise = False

adjustCase badBit actualBit goodWord ignoreCase
  | not ignoreCase = goodWord
  | actualBit == (T.toUpper badBit) = T.toUpper goodWord
  | actualBit == (T.toLower badBit) = T.toLower goodWord
  | (T.head actualBit) == (C.toUpper $ T.head badBit) = makeFirstCharUpper
  | otherwise = goodWord
  where
    upperFirstChar = C.toUpper $ T.head goodWord
    makeFirstCharUpper = T.cons upperFirstChar (T.tail goodWord)

prefixFilter :: Text -> Text -> Bool -> Text -> Maybe Text
prefixFilter badWord goodWord ignoreCase word
  | stringsAreEqual ignoreCase prefix badWord = Just $ T.append caseAdjusted rest
  | otherwise = Nothing
  where
    (prefix, rest) = T.splitAt (T.length badWord) word
    caseAdjusted = adjustCase badWord prefix goodWord ignoreCase

suffixFilter :: Text -> Text -> Bool -> Text -> Maybe Text
suffixFilter badWord goodWord ignoreCase word
  | stringsAreEqual ignoreCase suffix badWord = Just $ T.append rest caseAdjusted
  | otherwise = Nothing
  where
    (rest, suffix) = T.splitAt (T.length word - T.length badWord) word
    caseAdjusted = adjustCase badWord suffix goodWord ignoreCase
    
wordFilter :: Text -> Text -> Bool -> Text -> Maybe Text
wordFilter badWord goodWord ignoreCase word
  | stringsAreEqual ignoreCase word badWord = Just goodWord
  | otherwise = Nothing
  where
    caseAdjusted = adjustCase badWord word goodWord ignoreCase

nullFilter :: Text -> Maybe Text
nullFilter = Just

createFilter :: FilterSpec -> Filter
createFilter spec = createFilterHelper (badPattern spec) (goodWord spec) True
  where
    createFilterHelper spec goodWord ignoreCase
      | (T.last spec) == '!' = createFilterHelper (T.init spec) goodWord False
      | (T.last spec) == '*' = prefixFilter (T.init spec) goodWord ignoreCase
      | (T.head spec) == '*' = suffixFilter (T.tail spec) goodWord ignoreCase
      | otherwise = wordFilter spec goodWord ignoreCase

createFilters specs = (map createFilter specs) ++ [nullFilter]

applyFilters :: Filters -> Text -> Text
applyFilters filters word = fromJust activeFilter
  where
    filteredWords = map ($ word) filters
    activeFilter = fromJust $ find isJust filteredWords 

filterWords :: Filters -> Text -> Text
filterWords filters text = gsub wordRegex sanitiseWord text
  where
    sanitiseWord word = applyFilters filters word


Рейтинг:
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

Robert g Blair

COBOL имеет потрясающую обработку строк:

identification division.
program-id. FixBadWords.

data division.
  
working-storage section.
   
	01 wsData.
	    03 wsComment occurs 5 times pic a(255).
	01 i         pic 9(1).

procedure division. 
	
	move "My PHBis such a poophead. It's gotten worse since his promotion" 
            to wscomment(1)
	move "My PHB is such a POOPHEAD!" to wscomment(2)

	perform varying i from 1 by 1 until i > 5
	    
		inspect wsComment(i) 
			replacing all 'poophead' by 'p**phead'
				      'POOPHEAD' BY 'P**PHEAD'
				      'PHB'      BY 'boss'
				      'gotten'   BY 'become'
		
		display wsComment(i)

	end-perform

stop run
.


Chris Maunder

Так легко, что это почти обман!

Robert g Blair

Ну, вы могли бы сделать многое, чтобы сделать его более хорошим:

- Строковые литералы могут быть переменными, которые вы можете получить из источника ввода, например, базы данных, формы ввода, xml, чего угодно.

- Вы также можете использовать подсчет и перед началом работы ...

Но дело - дело в COBOL-это не так-то просто.
Извините, но для COBOL solutions вы должны выбрать случай. Верхний или нижний.

Дело-это просто социальный конструкт, понимаете ли :)

Robert g Blair

Вы можете протестировать этот код здесь: https://www.tutorialspoint.com/compile_cobol_online.php

Рейтинг:
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

Jacques Sineriz

Быстрый и грязный Javascript

var bad = ["poop*", "PHB!", "gotten"];
var replacement = ["p**p", "boss", "become"];
function escapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function replaceBad(sentence){
	return sentence.split(/\b/).map(w => {
		for(var i=0, m=bad.length; i<m; i++){
			var badToken = bad[i];
			if(badToken.slice(-1)==='*') { // ends with *
				return w.replace(new RegExp('^' + escapeRegExp(badToken.slice(0,-1)) + '(.*)$', 'i'), replacement[i]+'$1');
			}
			if(badToken.slice(0)==='*') { // starts with *
				return w.replace(new RegExp('^(.*)'+ escapeRegExp(badToken.slice(1)) + '$', 'i'), '$1' + replacement[i]);
			}
			if(badToken.slice(-1)==='!') { // ens with !
				return w.replace(new RegExp('^'+ escapeRegExp(badToken.slice(0,-1)) + '$', ''), replacement[i]);
			}
			return w.replace(new RegExp('^'+badToken+'$', 'i'), replacement[i]);
		}
	}).join('');
}

var test=['Hello poophead; glad your PHB has "gotten" less rotten',
'Hi poopheadpoop; glad your PhB has "undergotten" less NOPHB'
];

test.forEach(s => {
	console.log(s + ' => ' + replaceBad(s)); 
})


Рейтинг:
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

О, человечество! Расточительно, расточительно. Вы выбрасываете совершенно хорошую информацию, которая может быть использована позже.
Вы даже не допускаете, чтобы несколько ключей соответствовали слову.