Следующая операция после отображения списков, такая же по важности операция — это фильтрация. Они часто используются вместе. Давайте её разберём.
Самый простой пример в нашей библиотеке — это удаление элемента, например, заголовков.
import {
node, append, make,
} from 'hexlet-html-tags'
const html1 = append(make(), node('h2', 'header1'))
const html2 = append(html1, node('h2', 'header2'))
const html3 = append(html2, node('p', 'content'))
const processedHtml = removeHeaders(html3)
Здесь создаётся html, в который добавляются ноды двух типов: заголовок и параграф. И после этого применяется функция removeHeaders
. В свою очередь она возвращает новый html. И если мы его распечатаем, то увидим, что остался только параграф и больше ничего:
toString(processedHtml) // <p>content</p>
Удаление тегов
import {
l, isEmpty, is, head, tail, cons,
} from 'hexlet-pairs-data'
export const removeHeaders = (elements) => {
if (isEmpty(elements)) {
return l()
}
const element = head(elements)
const tailElements = tail(elements)
if (is('h2', element)) {
return removeHeaders(tailElements)
}
return cons(element, removeHeaders(tailElements))
}
Давайте посмотрим, как устроена внутри эта функция. Она работает практически так же, как и map
, за единственным исключением: мы внутри проверяем, является ли элемент тегом, в данном случае заголовком. Если он является заголовком, то мы рекурсивно вызываем функцию removeHeaders
с "хвостом" нашего списка tailElements
. Если нет, то мы вызываем cons
и добавляем текущий элемент и рекурсивный вызов "хвоста". Если наш элемент является заголовком, то мы не делаем cons
и просто пропускаем его. В конечном итоге нам вернётся список с отфильтрованными значениями.
Фильтрация
Эта операция называется filter
, также называется функция, которая её выполняет. Иногда её называют и по-другому, но чаще всего именно так. Как видно из картинки, filter
не делает никаких преобразований. Задача этой функции исключительно — оставить только то, что нужно.
import {
node, append, make, filter,
} from 'hexlet-html-tags'
const html1 = append(make(), node('h2', 'header1'))
const html2 = append(html1, node('h2', 'header2'))
const html3 = append(html2, node('p', 'content'))
const processedHtml = filter(element =>
!is('h2', element), html3)
toString(processedHtml) // <p>content</p>
Посмотрим реализацию, используя функцию высшего порядка filter
, которая производит изменения. Здесь тот же самый пример. Единственное отличие — мы вызываем не removeHeaders
, а функцию filter
. На вход передаётся 2 параметра: функция и HTML. Это стандарт для подобного вида функций. При этом функция, которую мы передаём, называется предикат, потому что она возвращает значение истина или ложь в зависимости от того, нужно ли сохранять элемент. Если она возвращает true
, то элемент остаётся в списке, если false
, то его не будет в новом списке.
Часто эта функция достаточно простая. Наш случай не исключение. Функция делает простую проверку, является ли элемент заголовком второго уровня. И если он этим заголовком не является, то всё хорошо и элемент остаётся в списке. Если мы распечатаем processedHtml
, то будет выведен ожидаемый список с одним элементом — тегом p
.
Ниже еще несколько примеров использования функции filter
:
import { l, filter, toString } from '@hexlet/pairs-data'
const list = l('', 0, 10, 'go go', -5, 'string')
const list2 = filter(Number.isInteger, list)
console.log(toString(list2)) // => (0, 10, -5)
const list3 = filter(item => typeof item === 'string', list)
console.log(toString(list3)) // => (, go go, string)
Как устроена фильтрация
export const filter = (func, elements) => {
if (isEmpty(elements)) {
return l()
}
const current = head(elements)
const tailElements = tail(elements)
if (func(current)) {
return cons(current, filter(func, tailElements))
}
return filter(func, tailElements)
}
Посмотрим, как фильтрация устроена внутри. Это фактически тот же самый map
. Мы получаем первый элемент с помощью функции head
и после этого функцией func
делаем проверку. В этом и заключается главное отличие. Если результат этой функции истина, то мы вызываем cons
, т.е. добавляем текущий элемент в новый список. Если она возвращает false
, то мы рекурсивно запускаем фильтр, пропуская этот элемент.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.