Advent of Code 2018 День 2 Часть 1 — Решение и объяснение

Саму задачу можно найти здесь.

Обратите внимание:

  • В этой статье рассматривается только одно решение задачи, для которой существует несколько решений.
  • Решение в полном объеме можно найти внизу.
  • В решении используется главная функция, объекты, циклы, условия, методы массивов и метод Object.values().

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

abcdef
bababc
abbcde
abcccd
aabcdd
abcdee
ababab

Прежде чем перейти к основной части задачи, мы должны преобразовать то, что написано выше, в JavaScript, чтобы затем преобразовать его в массив строк (каждая строка будет идентификатором ящика).

Как же нам интерпретировать семь строк текста? Другими словами, как мы можем перевести (так сказать) их в JavaScript?

Ну, мы рассматриваем их как одну длинную строку с переносами строк:

'abcdefnbababcnabbcdenabcccdnaabcddnabcdeenababab'
Войти в полноэкранный режим Выход из полноэкранного режима

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

Если мы выведем строку в консоль, то увидим, что она является исходным вводом:

abcdef
bababc
abbcde
abcccd
aabcdd
abcdee
ababab

Итак, мы знаем, что все правильно.

Давайте сохраним строку в переменную examplePuzzleInput.

const examplePuzzleInput = 'abcdefnbababcnabbcdenabcccdnaabcddnabcdeenababab'
Вход в полноэкранный режим Выход из полноэкранного режима

Далее, давайте…

  1. воспользуемся методом split() для преобразования длинной строки в массив строк
  2. сохраним результат в переменной boxIDsArr.
const boxIDsArr = examplePuzzleInput.split('n')
Вход в полноэкранный режим Выйти из полноэкранного режима

Что записывает boxIDsArr?

['abcdef', 'bababc', 'abbcde', 'abcccd', 'aabcdd', 'abcdee', 'ababab']

Итак, теперь у нас есть наш массив, и мы можем перейти к основной части задачи.

Наша задача состоит в том, чтобы…

  1. подсчитать количество идентификаторов ящиков, в которых есть ровно 2 любые буквы
  2. подсчитать количество идентификаторов ящиков, в которых ровно 3 любые буквы
  3. сделать это, чтобы получить контрольную сумму: количество двоек * количество троек.

(Если требуется больше ясности, сама задача содержит некоторые пояснения).

Сначала мы создадим функцию getMatches, которая принимает параметр input. Input — это псевдоним, который мы будем использовать для массива идентификаторов ящиков, показанного выше.

function getMatches(input) {

}
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку мы хотим подсчитать…

  1. количество идентификаторов ящиков, в которых есть ровно ДВЕ любые буквы
  2. количество идентификаторов ящиков, в которых есть ровно ТРИ любые буквы

…следующее, что мы сделаем, это создадим объект matches, который будет вести подсчет количества найденных совпадений для каждой из этих двух категорий.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку нам нужно получить доступ к каждой из строк массива input, мы собираемся пройти через input с помощью цикла forEach. Каждое значение, к которому мы будем обращаться в этом цикле, является строкой идентификатора ящика, поэтому назовем его boxIdStr.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {

    })  
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Но на самом деле нам нужно иметь доступ к каждой букве в каждой строке. Поэтому, поскольку мы уже находимся в каждой строке, мы собираемся пройтись по каждой строке с помощью еще одного цикла forEach.

Обратите внимание, что мы применяем метод split() к каждой строке, чтобы преобразовать строку в массив букв (каждая буква является строкой). Поэтому boxIdStr.split('') для первой строки массива input (первая строка: 'abcdef') выглядит следующим образом:

['a', 'b', 'c', 'd', 'e', 'f']

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        boxIdStr.split('').forEach(letter => {

        })
    })
}
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку мы собираемся подсчитывать количество вхождений каждой буквы в каждой строке, следующее, что мы сделаем, это создадим еще один объект, на этот раз под названием counts, который будет начинаться как пустой объект.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        let counts = {}
        boxIdStr.split('').forEach(letter => {

        })
    })
}
Вход в полноэкранный режим Выход из полноэкранного режима

Как мы будем подсчитывать количество вхождений каждой буквы в каждой строке?

В качестве примера возьмем вторую строку в массиве input (вторая строка: 'bababc'). После преобразования в массив букв он выглядит следующим образом:

['b', 'a', 'b', 'a', 'b', 'c']

Когда цикл переходит по 'b' (первая буква в строке):
Если counts[letter] (другими словами, counts['b']) существует (другими словами, если объект counts имеет ключ b), то увеличиваем значение b на 1. Но если counts[letter] (другими словами, counts['b']) еще не существует (другими словами, если объект counts еще не имеет ключа b), то дайте объекту counts ключ b и присвойте b значение 1.

В приведенном ниже коде обратите внимание, что if в предложении counts[letter] является сокращением для counts.hasOwnProperty(letter), которое является сокращением для counts.hasOwnProperty(letter) === true. Причина, по которой counts[letter] работает здесь, заключается в том, что если он возвращает undefined, то он возвращает что-то фальшивое, а если он возвращает значение, то он возвращает что-то истинное.

Также обратите внимание, что мы можем заменить приведенный ниже оператор if...else на counts[letter] = (counts[letter] || 0) + 1. Это просто более лаконичный способ добиться того же самого.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        let counts = {}
        boxIdStr.split('').forEach(letter => {
            if (counts[letter]) {
                counts[letter]++
            } else {
                counts[letter] = 1
            }
        })
        console.log(counts)
    })
}
Вход в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что console.log(counts) включен в приведенный выше код только для того, чтобы мы могли видеть, как именно выглядит объект counts для каждой строки после добавления к нему всего, что будет добавлено. Например, объект counts для второй строки выглядит следующим образом:

{b: 3, a: 2, c: 1}

Таким образом, мы знаем, что во второй строке a встречается два раза, b — три раза, а c — один раз.

Конечно, то, что мы исследовали здесь со второй строкой, повторяется для каждой строки в массиве input.

Теперь нам нужно определить для каждого объекта counts, содержит или не содержит он значения 2 или значения 3. Обратите внимание, что мы не подсчитываем количество значений в каждом объекте counts, которые являются 2 или 3. (Если это различие все еще неясно, оно описано более подробно ниже).

Для этого мы используем метод includes().

Поскольку includes() возвращает true или false, Object.values(counts).includes(2), например, не интересуется тем, сколько значений объекта counts являются 2, а скорее тем, есть ли у этого объекта counts значения, которые являются 2.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        let counts = {}
        boxIdStr.split('').forEach(letter => {
            if (counts[letter]) {
                counts[letter]++
            } else {
                counts[letter] = 1
            }
        })
        console.log(counts)
        if (Object.values(counts).includes(2)) {

        }
        if (Object.values(counts).includes(3)) {

        }
    })
}
Вход в полноэкранный режим Выход из полноэкранного режима

И если объект count содержит значения 2 (независимо от того, сколько значений объекта 2), мы увеличим значение ключа twice объекта matches на 1.

Другими словами, с таким объектом, как этот {a: 1, b: 2, c: 1, d: 1, e: 1}, значение ключа matches объекта twice увеличится на 1, и это, вероятно, довольно очевидно. Но что может быть неочевидно, так это то, что даже с таким объектом, как этот {a: 2, b: 1, c: 1, d: 2}, значение ключа twice объекта matches будет увеличиваться на 1, а не на 2. Опять же, это связано с тем, как работает includes().

И если объект count содержит значения 3 (независимо от того, сколько значений объекта равно 3), мы увеличим значение ключа matches объекта thrice на 1.

Другими словами, с таким объектом, как этот {a: 1, b: 1, c: 3, d: 1}, значение ключа thrice объекта matches увеличится на 1, и это, вероятно, довольно очевидно. Но что может быть неочевидно, так это то, что даже с таким объектом, как этот {a: 3, b: 3}, значение ключа thrice объекта matches будет увеличиваться на 1, а не на 2. Опять же, это связано с тем, как работает includes().

И это, вероятно, не нужно говорить, но, поскольку ясности больше, давайте включим это: С таким объектом, как этот {a: 2, b: 3, c: 1, d: 1, e: 3, f: 2, g: 3}, значение ключей twice и thrice объекта matches будет увеличено только на 1.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        let counts = {}
        boxIdStr.split('').forEach(letter => {
            if (counts[letter]) {
                counts[letter]++
            } else {
                counts[letter] = 1
            }
        })
        console.log(counts)
        if (Object.values(counts).includes(2)) {
            matches.twice++
        }
        if (Object.values(counts).includes(3)) {
            matches.thrice++
        }
    })
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        let counts = {}
        boxIdStr.split('').forEach(letter => {
            if (counts[letter]) {
                counts[letter]++
            } else {
                counts[letter] = 1
            }
        })
        console.log(counts)
        if (Object.values(counts).includes(2)) {
            matches.twice++
        }
        if (Object.values(counts).includes(3)) {
            matches.thrice++
        }
    })

    return matches.twice * matches.thrice
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь наш последний шаг: Вызовите функцию getMatches, передав ей boxIDsArr, и запишите вызов этой функции в консоль.

function getMatches(input) {
    let matches = {
        twice: 0,
        thrice: 0
    }
    input.forEach(boxIdStr => {
        let counts = {}
        boxIdStr.split('').forEach(letter => {
            if (counts[letter]) {
                counts[letter]++
            } else {
                counts[letter] = 1
            }
        })
        console.log(counts)
        if (Object.values(counts).includes(2)) {
            matches.twice++
        }
        if (Object.values(counts).includes(3)) {
            matches.thrice++
        }
    })

    return matches.twice * matches.thrice
}

console.log(getMatches(boxIDsArr)) // 12 // 12 is the checksum
Вход в полноэкранный режим Выход из полноэкранного режима

Надеюсь, это было понятно и полезно!

Спасибо за прочтение!

Оцените статью
devanswers.ru
Добавить комментарий