Привет ! Меня зовут Ксавье Жувено, и вот третья часть длинной серии о Пришествии кода. Предыдущую часть можно найти здесь

В этом новом посте мы собираемся решить проблему от 5 декабря 2015 года под названием «Разве у него нет для этого эльфов-стажеров?». Решение я предложу на C++, но рассуждения можно применить и к другим языкам.

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

Назад к дню 4

TheFlamefire, пользователь Reddit, дал мне несколько хороших комментариев, чтобы помочь мне улучшить решение проблемы 4-го дня. Его комментарии напрямую перекликаются с принципом единственной ответственности, о котором я хотел рассказать в этой задаче на 5-й день (хорошее совпадение 😄).

Принцип единой ответственности, что это такое?

Как хорошо описано в Википедии:

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

Применяя этот принцип, вы получите очень модульный код с меньшим и более понятным фрагментом кода, который может читать и работать каждый. Есть много других больших преимуществ принятия этого принципа, но я не буду описывать их здесь. Более того, этот принцип является первой частью принципов SOLID, которая заслуживает нескольких постов в блогах для правильного объяснения.

Принцип единой ответственности в День 4

В решении дня 4 мы создали функцию с именем getMd5, которая передает закрытый ключ алгоритму MD5 И преобразует результат из массива unsigned char в std::string. Выделенное мной «И» вызывает подозрение, поскольку оно указывает на то, что функция выполняет 2 действия, что нарушает принцип единой ответственности. Чтобы исправить это, мне пришлось разделить функцию getMd5 на две функции:

  • getMd5, который возвращает только массив байтов, соответствующий хешу MD5, сгенерированному из закрытого ключа.
  • bytesToString, который преобразует массив unsigned char в std::string

Я могу только призвать вас взглянуть на реализацию этих функций на моем Github, а теперь давайте перейдем к 5-му дню! 😈

Часть 1

Проблема

Полную версию этой проблемы можно найти непосредственно на сайте Пришествие кода, здесь я опишу лишь суть проблемы:

Санте нужна помощь, чтобы выяснить, какие строки в его текстовом файле являются непослушными или хорошими. Хорошая строка — это строка со всеми следующими свойствами:

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

Решение

Как и в пункте 4-го дня, для этой задачи мы будем использовать принцип единой ответственности. Действительно, эта проблема хорошо адаптирована для ее введения, поскольку каждый пункт в списке хороших строковых свойств может быть помещен в одну функцию с одной ответственностью. Таким образом, мы получаем три функции:

  • bool hasThreeVowels(const std::string_view str)
  • bool hasLettersAppearingTwiceInRow(const std::string_view str)
  • bool hasForbiddenSubString (const std::string_view str)

Давайте углубимся в эти функции.

Во-первых, для функции hasThreeVowels мы можем напрямую использовать алгоритм std::count_if, чтобы получить правильное поведение:

return 3 <= std::count_if(
  std::begin(str),
  std::end(str),
  [](const auto& letter)
  {
     return letter == 'a' ||
            letter == 'e' ||
            letter == 'i' ||
            letter == 'o' ||
            letter == 'u';
  });

Чтобы соблюдать принцип единой ответственности, мы могли бы создать функцию isVowel вместо того, чтобы напрямую включать условие в используемую здесь лямбду.

Для второй функции, hasLettersAppearingTwiceInRow, я не нашел стандартного алгоритма, позволяющего мне иметь удобное решение, поэтому я использую наивное решение, используя цикл for с диапазоном:

char previousCharacter = CHAR_MIN;
for(const auto& character : str)
{
  if(character == previousCharacter) { return true; } 
  previousCharacter = character;
}
return false;

И для последней функции, hasForbiddenSubString, мы можем использовать метод std::string_view::find:

return str.find("ab") != std::string_view::npos ||
       str.find("cd") != std::string_view::npos ||
       str.find("pq") != std::string_view::npos ||
       str.find("xy") != std::string_view::npos;

Теперь, когда у нас есть эти 3 функции, осталось сделать 2 вещи. Прежде всего, мы должны создать функцию, определяющую, что такое хорошая строка, объединив предыдущие функции в условии:

bool isNice(const std::string_view str)
{
  return hasThreeVowels(str) &&
         hasLettersAppearingTwiceInRow(str) &&
         ! hasForbiddenSubString(str);
}

И примените эту функцию к каждой строке ввода:

auto numberOfNiceString{0};
foreachLineIn(fileContent,
              [&numberOfNiceString](const std::string& str)
              { 
                 if(isNice(str))
                 {
                    ++numberOfNiceString;
                 }
              });
std::cout << numberOfNiceString;

И вуаля, у нас есть красивое решение, и оно применяет принцип единой ответственности при поиске приятных ниточек Санты. 👼

Часть 2

Проблема

На самом деле Санта допустил какую-то ошибку, и критерии, используемые для поиска непослушных и хороших ниточек, были неправильными. Действительно, хорошая строка обладает следующими свойствами:

  • Он содержит пару любых двух букв, которые встречаются в строке не менее двух раз и не пересекаются.
  • Он содержит по крайней мере одну букву, которая повторяется ровно с одной буквой между ними.

Решение

Черт возьми, так ты говоришь мне, что мы должны сделать это снова?! 😮

Ну на самом деле нет. Поскольку мы применили принцип единой ответственности, логика нашей программы осталась прежней, нам нужно только адаптировать метод isNice, используя две новые функции, по одной для каждого свойства строк nice:

bool isNice(const std::string_view str)
{
  return hasTwiceAPairOfLetters(str) && 
         hasOneLetterAppearingTwiceWithOnlyOneLetterBetween(str);
}

Теперь все, что нам нужно сделать, это реализовать эти функции. Во-первых, давайте посмотрим на реализацию hasTwiceAPairOfLetters:

for(size_t i = 0; i < str.size() - 2; ++i)
{
  std::string_view str_toSearch(&str[i], 2); // The first 2 elements of the string
  std::string_view str_toSearchIn(&str[i + 2], str.size() - i - 2); // The rest of the string
  if(str_toSearchIn.find(str_toSearch) != std::string_view::npos)
  {
     return true;
  }
}
return false;

Мне нечего сказать об этой реализации, за исключением того, что std::string_view делает довольно хорошую работу 😃

А теперь функция hasOneLetterAppearingTwiceWithOnlyOneLetterBetween:

for(size_t i = 0; i < str.size() - 2; ++i)
{
  if(str[i] == str[i + 2])
  {
     return true;
  }
}
return false;

И, наконец, теперь у нас есть красивое определение красивых струн, которые понравятся Санте 🎅.

Вывод

Как всегда, вы можете заметить, что решения, написанные в этом посте, включают не все исходники для запуска программ, а только интересную часть исходников для решения этой проблемы. Если вы хотите увидеть программы от начала до конца, вы можете зайти на мою учетную запись GitHub, изучить полное решение, добавить комментарии или задать вопросы, если хотите.

Вот список std-методов и контейнеров, которые мы использовали, я не могу убедить вас взглянуть на их определения:

Спасибо, что читаете, надеюсь, вам понравилось. Со своей стороны, я получаю от этого удовольствие 😃

И до следующей части, получайте удовольствие, учась и развиваясь.

Первоначально опубликовано на http://10xlearner.com 1 июля 2019 г.