Kata — это кодовые головоломки, которые помогут вам отточить свои навыки.

Мне нравится создавать и решать кодовые ката. Код Ката — это головоломки по программированию, которые помогут вам отточить свои навыки программирования. 

Я написал статью под названием «Learn to Kata and Kata to Learn» для книги «97 вещей, которые должен знать каждый Java-программист», и ссылка на статью доступна бесплатно здесь, в публикации на Medium.

Удивительный мир Wordle

Wordle — это очень популярная онлайн игра головоломка, в которой у вас есть шесть шансов угадать слово из пяти букв. 

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

Вы можете узнать больше об увлечении Wordle в этой статье.

Wordle explained: Everything you need to know to master the viral word game

Wordle — это вирусная словесная игра, недавно приобретенная New York Times за несколько миллионов. Но…

www.cnet.com

JLDD =  Jet Lag Driven Development

В прежние времена, когда технические конференции и путешествия по миру были обычным явлением, несколько Java чемпионов(Хосе Помар, Нихил Нанивадекар, и я) делились задачами по кодированию в Твиттере, пока мы были на конференциях (обычно JavaOne/Oracle CodeOne). 

У всех нас была разная степень смены часовых поясов во время конференций, поэтому мы решили использовать хэштег JLDD (разработка, управляемая сменой часовых поясов), когда публиковали задачи по программированию. 

Чаще всего задачи программирования были направлены на решение проблем с Java-коллекциями или потоками. Обычно я размещал решения, используя Eclipse Collections.

На протяжении всей пандемии мы время от времени делились друг с другом проблемами JLDD в Твиттере, несмотря на то что джетлаг уже давно прекратился. Несколько недель назад я поделился Haiku Kata, используя Java Text Blocks и Eclipse Collections.

Haiku на Java с использованием текстовых блоков

Творческое письмо в сочетании с текстовыми блоками Java и коллекциями Eclipse

medium.com

Хосе Помар затем сделал все возможное и закодировал задачу JLDD в прямом эфире в 25-минутном видео JEP Cafe # 9. Он проделывает потрясающую работу, объясняя решения Eclipse Collections и Java 17. Отличная работа!

Wordle Ката

Хосе Помар на этой неделе мне прислали вызов Wordle Kata JLDD в виде теста, для которого мне нужно было написать код, проходящий этот тест. Мне нравится этот тип ката, который следует классическому стилю TDD с использованием подхода «сначала тест». Выше приведен тестовый код для kata с использованием простых утверждений JUnit 5.

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

Правила, основанные на этом тесте, довольно просты.

  1. Если буква в строке предположения не соответствует букве в скрытом слове, замените символ в выводе на «.»

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

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

  4. Если буква совпадает, но появляется в угаданной строке больше раз, чем в скрытом слове, то замените дополнительные символы в выводе на «.».

Мое первое решение с использованием Eclipse Collections

Решение, которое я придумал, выглядело следующим образом и прошло все тесты.

Код для угадывания Wordle с использованием коллекций Eclipse
Код для угадывания Wordle с использованием коллекций Eclipse

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

 Метод collectWithIndex типа CharAdapter преобразует строку guessChars в один символ за раз. Сначала я думал, что никакие char значения не будут упакованы как объекты Character, но оказалось, что я ошибался. 

Метод collectWithIndex принимает CharIntToObjectFunction, что означает, что значения char для каждого выходного символа будут упакованы как объекты Character.

Это также означает, что у нас нет чисто примитивной версии collectWithIndex в Eclipse Collections, как в случае с collectChar. Я думаю, что это, вероятно, приемлемо в большинстве случаев и не должно быть слишком дорогостоящим для этого конкретного сценария использования. 

Я не думаю, что добавление чисто примитивной версии с именем collectCharWithIndex имело бы смысл.

Однако существует более серьезная проблема, чем упаковка char значений в виде Character объектов перед созданием выходных данных String. Я обнаружил, что отсутствует тестовый пример и дополнительное правило, которое мы должны добавить в ката.

Правило: отдавайте предпочтение прямым совпадениям, а не косвенным.

Я добавил следующий тестовый пример, который приводит к сбою моего первого решения.

Слово, имеющее прямое совпадение по одной букве, но косвенное совпадение предшествует прямому совпадению.
Слово, имеющее прямое совпадение по одной букве, но косвенное совпадение предшествует прямому совпадению.

Позвольте мне увеличить масштаб, чтобы вы могли видеть тестовый пример более четко.

Вывод для слова «ббабб» с догадкой «ааааа» должен быть «..А..»
Вывод для слова «ббабб» с догадкой «ааааа» должен быть «..А..»

В этом случае буква «а» будет иметь прямое совпадение в третьей позиции, но косвенные совпадения в догадке следует игнорировать в пользу прямого совпадения в третьей позиции.

Мое обновленное решение

Для тех, кто меня знает, вы должны знать, что я несколько одержим обеспечением хорошей симметрии в API Eclipse Collection. 

В этом конкретном сценарии использования было бы идеально, если бы существовал эквивалент selectWithIndex и rejectWithIndex, имеющихся для примитивных типов коллекций в Eclipse Collections. Этот конкретный вариант использования может заставить меня согласиться и добавить недостающие методы.

Однако существует альтернативный метод, который я могу использовать для реализации эквивалента этих двух методов. Это метод injectIntoWithIndex.

Вот мое обновленное решение, использующее injectIntoWithIndex для создания CharBag с оставшимися символами, которые не имеют прямых совпадений.

Решение для угадывания Wordle, которое обрабатывает все правила
Решение для угадывания Wordle, которое обрабатывает все правила

Если вы хотите понять, как метод injectIntoWithIndex работает, вы можете прочитать следующий блог про injectInto (EC by Example: InjectInto). Метод injectInto можно использовать для реализации большинства шаблонов итерации, что также относится к injectIntoWithIndex. Это одновременно магические и мощные методы.

Обновление: альтернативное решение с использованием zipChar

Иногда, когда вы работаете над одним и тем же кодом в течение 18 лет, вы кое-что забываете. К счастью, друг будет время от времени напоминать вам о вещах, которые вы, возможно, забыли. Это случилось на этой неделе, когда Vladimir Zakharov поделился другим решением для Wordle JLDD Kata в Твиттере, используя метод с именем zipChar.

Решение Влада довольно крутое, и это определенно не тот подход, о котором я бы подумал. Я забыл, что zip в Eclipse Collections была доступна примитивная версия. Каждый примитивный тип OrderedIterable в Eclipse Collections поддерживает zip одного и того же типа. Итак, у a CharAdapter есть метод zipChar

Вскоре я вспомнил, что был блог, который я написал более четырех лет назад о примитиве zip.

Как только я увидел решение Влада, я понял, что могу написать альтернативу своему собственному решению, используя zipChar для замены injectIntoIndex и collectWithIndex.

Wordle Kata с использованием zipChar с reject и collectChar
Wordle Kata с использованием zipChar с reject и collectChar

Два решения, которые используют zipChar, существенно различаются по алгоритму, который они используют для вычисления окончательного результата строки предположения.

Исходник моего финального решения с Eclipse Collections

Я создал свое окончательное решение задачи Wordle Kata JLDD с использованием коллекций Eclipse. Я с нетерпением жду возможности увидеть решение на чистой Java 17 от Хосе Помар.

. Я всегда узнаю от Хосе что-то новое и интересное о Java, чего раньше не знал.

import org.eclipse.collections.api.bag.primitive.MutableCharBag;
import org.eclipse.collections.impl.factory.Strings;
import org.eclipse.collections.impl.factory.primitive.CharBags;
import org.eclipse.collections.impl.string.immutable.CharAdapter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class WordleTest
{
    @Test
    public void wordleTest()
    {
        Assertions.assertEquals(".....", new Wordle("aaaaa").guess("bbbbb"));
        Assertions.assertEquals("A....", new Wordle("aaaaa").guess("abbbb"));
        Assertions.assertEquals(".A...", new Wordle("aaaaa").guess("babbb"));
        Assertions.assertEquals("..A..", new Wordle("aaaaa").guess("bbabb"));
        Assertions.assertEquals("...A.", new Wordle("aaaaa").guess("bbbab"));
        Assertions.assertEquals("....A", new Wordle("aaaaa").guess("bbbba"));

        Assertions.assertEquals(".a...", new Wordle("abbbb").guess("caccc"));
        Assertions.assertEquals("..a..", new Wordle("abbbb").guess("ccacc"));
        Assertions.assertEquals("...a.", new Wordle("abbbb").guess("cccac"));
        Assertions.assertEquals("....a", new Wordle("abbbb").guess("cccca"));

        Assertions.assertEquals("A....", new Wordle("abbbb").guess("accca"));
        Assertions.assertEquals("A....", new Wordle("abbbb").guess("accaa"));
        Assertions.assertEquals("A..a.", new Wordle("aabbb").guess("accaa"));
        Assertions.assertEquals("AA...", new Wordle("aabbb").guess("aacaa"));
        Assertions.assertEquals("...aa", new Wordle("aabbb").guess("cccaa"));

        Assertions.assertEquals("..A..", new Wordle("bbabb").guess("aaaaa"));

        Assertions.assertEquals("AAAAA", new Wordle("aaaaa").guess("aaaaa"));
        Assertions.assertEquals("BRAVO", new Wordle("bravo").guess("bravo"));
    }

    record Wordle(String string)
    {
        Wordle(String string)
        {
            this.string = string.toLowerCase();
        }

        public String guess(String guess)
        {
            CharAdapter guessChars = Strings.asChars(guess.toLowerCase());
            CharAdapter hiddenChars = Strings.asChars(this.string);
            MutableCharBag remaining = hiddenChars
                    .injectIntoWithIndex(
                            CharBags.mutable.empty(),
                            (bag, each, i) -> guessChars.get(i) != each ? bag.with(each) : bag);
            return guessChars.collectWithIndex((each, i) -> hiddenChars.get(i) == each ?
                            Character.toUpperCase(each) : this.replaceDifferentPositionOrNoMatch(remaining, each))
                    .makeString("");
        }

        private char replaceDifferentPositionOrNoMatch(MutableCharBag remaining, char each)
        {
            return remaining.remove(each) ? each : '.';
        }
    }
}

Заключительные соображения

Надеюсь, вам понравился этот блог о моих решениях задачи JLDD Kata с использованием коллекций Eclipse. Я, конечно, был бы рад видеть другие решения этих задач, чтобы мы все могли изучить новые и разные решения задач программирования. Эти решения могут быть на Java с использованием ваших любимых библиотек или даже на другом языке программирования.

Спасибо, что нашли время прочитать!

Комментарии (0)