Генераторы ES6

Итак, мне нравится учиться чему-то, сочетая то, что я хочу изучить, с примерами использования в реальном мире. Слишком часто концепции/идеи сочетаются с подходами к обучению, которые никогда не приживаются, потому что они сочетаются с примерами, которые никогда не используются. Таким образом, концепции не затвердевают и даже не обосновываются.

Итак, давайте рассмотрим генераторы и поиск в глубину (DFS). Так вот, за все годы программирования мне ни разу не приходилось делать поиск в глубину. Это не значит, что это упражнение бесполезно. Во многих интервью есть вопрос о DFS, а DFS закрепилась в лексиконе программирования, так как она действительно удовлетворяет нашему «реальному варианту использования».

Итак, что такое поиск в глубину?
DFS — это алгоритм обхода или поиска в древовидных или графовых структурах данных. Начинают с корня (выбирая какой-нибудь произвольный узел в качестве корня в случае графа) и исследуют как можно дальше каждую ветвь, прежде чем возвращаться назад. (— википедия)

Как это будет выглядеть на практике?

Итак, учитывая приведенное выше определение и наше изображение слева, мы можем лучше понять, как работает DFS. Мы начинаем с самого верхнего узла (в данном случае 1) и двигаемся влево вниз (в этом примере), насколько это возможно (3 в этом примере) и т. д. Но давайте просто запишем последовательность путей: br /> 1, 2, 3, 2, 4, 2, 1, 5, 6, 5, 7

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

Таким образом, мы можем вернуться к ранее посещенным узлам, чтобы затем найти новые узлы.

Теперь, когда у нас есть представление о том, как выглядит DFS, давайте перейдем к тому, как мы можем реализовать DFS с генераторами. Мне нужно будет предположить, что вы уже разбираетесь в генераторах. Теперь, когда я перейду к примеру кода, я буду подробно рассказывать о том, что я делаю и что происходит, я просто предполагаю, что вы можете преодолеть поток с вашим текущим пониманием. Кстати, вы всегда можете связаться со мной по электронной почте, и я буду более чем счастлив подробно объяснить все, что угодно.

Круто, приступим.

Давайте создадим наш класс Tree. Я дам все это вперед, тогда мы препарируем его.

class Tree {
  constructor(value = null, children = []) {
    this.value = value;
    this.children = children
  }
// Depth First Solution
*printValues() {
  yield this.value;
  for(let child of this.children) {
    yield* child.printValues();
  }
 }
}
// DEPTH first
// See explanations below for a bit more succinct way to 
// generate this tree.
const tree = 
  new Tree(1, [
    new Tree(2, [
       new Tree(5, [
         new Tree(9),
         new Tree(10)
       ]),
       new Tree(6)
     ]),
    new Tree(3),
    new Tree(4, [
       new Tree(7, [
          new Tree(11), 
          new Tree(12)
        ] ), 
        new Tree(8)])
])
const values = []
for(let value of tree.printValues()) {
  values.push(value);
}
values
// our results are:
// [1, 2, 5, 9, 10, 6, 3, 4, 7, 11, 12, 8]

Теперь я создал простое/общее дерево классов (имейте в виду, что класс не является обязательным требованием).

Наша структура данных — это всего лишь представление о родительско-дочерних отношениях. Мы можем изменить их (я сделаю это позже), но давайте посмотрим на наш код. Я просто дам краткое объяснение того, что происходит в конструкторе, так как это не является предметом этой истории.

constructor(value = null, children = []) {
    this.value = value;
    this.children = children
  }

Просто посмотрите на это как на функцию, которая принимает два аргумента, значение и дочерние элементы.
Если мы сравним эту функцию с сигнатурой (т. е. "арность" или "количество аргументов"), она прекрасно сочетается с:

new Tree(5, [new Tree(9),new Tree(10)])
this.value = 5;
this.children = [new Tree(9),new Tree(10)]
As you can see, the children is an array of "Tree's", that have their own "value" and "children". 

Давайте перейдем к нашему методу «printValues()», здесь происходит наше волшебство генератора.

*printValues() {
  yield this.value;
for(let child of this.children) {
    yield* child.printValues();
  }
 }

Что мы здесь замечаем? «*»
Что это за чертовщина? Именно так мы обозначаем функцию «генератор», применяя к ней звездочку.

*printValues() {
  yield this.value;
1. yield - The yield keyword is used to pause and resume a generator function.
2. Here we are "yielding" out this.value, for our current 'this' context. If this was the first iteration of our tree: new Tree(1, then our value is 1. Based on our explanation (see #1), at this point we pause. So, we yielded out 1, now what?

Вот наш второй фрагмент кода. Теперь, когда мы сделали паузу после того, как выдали 1. Наша вторая секунда здесь — это резюмеаспект нашего генератора. Мы перейдем к тому, «как» мы возобновим работу, ниже. Но давайте предположим, что сейчас мы вернемся к этой части кода.

for(let child of this.children) {
    yield* child.printValues();
  }
 }
// Here are some other ways to show the data rather than calling 
// 'new Tree' inline everywhere. We can create a helper function
// to recursively go thru everything.
const tree = {
  1: { 
    2: {
       5: {
         9: {},
         10: {},
       6: {}
    },
    3: {},
    4: {
       7: {
         11: {},
         12: {}
        }, 
       8: {}
    }
  }
}
}
const tree = [1,[ 2,[5, [9,10],6,3,4,7,[ 11, 12],8]]]

Упс: не понял, что я не закончил эту историю. :-( , скоро обновлю.