Перечисление рекурсивного массива в JS - PullRequest
0 голосов
/ 31 марта 2020

Скажите, пожалуйста, как перебрать массив с неизвестной вложенностью? Ситуация такова, я практикую js и reactjs на начальном уровне, я делаю приложение с Pokemon и хочу получить компонент с помощью эволюции :). API возвращает объект следующей формы:

{
  baby_trigger_item: null,
  chain: {
    evolution_details: [],
    evolves_to: [
      {
        evolves_to: [
          {
            evolves_to: [],
            is_baby: false,
            species: {
              name: "poliwrath",
              url: "https://pokeapi.co/api/v2/pokemon-species/62/"
            }
          },
          {
            evolves_to: [],
            is_baby: false,
            species: {
              name: "politoed",
              url: "https://pokeapi.co/api/v2/pokemon-species/186/"
            }
          }
        ],
        is_baby: false,
        species: {
          name: "poliwhirl",
          url: "https://pokeapi.co/api/v2/pokemon-species/61/"
        }
      }
    ],
    is_baby: false,
    species: {
      name: "poliwag",
      url: "https://pokeapi.co/api/v2/pokemon-species/60/"
    }
  },
  id: 26
};

Количество вложенных evolves_to массивов может быть много. И внутри "evolves_to" может быть несколько объектов, то есть разработка одного уровня. в нескольких видах. Должно получиться (думаю, такая структура будет удобна в дальнейшей работе):

[
    [
      {
        name: "",
        url: ""
      }
    ],
    [
      {
        name: "",
        url: ""
      },
      {
        name: "",
        url: ""
      }
    ][
      {
        name: "",
        url: ""
      }
    ]
  ]

То есть первый массив с первым уровнем эволюции, второй массив, второй уровень массива с двумя типами, третий массив, соответственно, третий уровень, et c. Если вложенность неизвестна, то мне нужно как-то оперировать рекурсией? Я пытался использовать map, reduce ... Я пытался внедрить в них рекурсию ... но, к сожалению, я еще не думал об этом. Если это не затруднит - помогите. Заранее спасибо)

UPD: вот код, который evolves_to собирает .species для одного объекта и добавляет к массиву. но он не учитывает наличие нескольких видов в evolves_to. если они находятся на одном уровне, они должны быть в одном массиве:

data={
  baby_trigger_item: null,
  chain: {
    evolution_details: [],
    evolves_to: [
      {
        evolves_to: [
          {
            evolves_to: [],
            is_baby: false,
            species: {
              name: "poliwrath",
              url: "https://pokeapi.co/api/v2/pokemon-species/62/"
            }
          },
          {
            evolves_to: [],
            is_baby: false,
            species: {
              name: "politoed",
              url: "https://pokeapi.co/api/v2/pokemon-species/186/"
            }
          }
        ],
        is_baby: false,
        species: {
          name: "poliwhirl",
          url: "https://pokeapi.co/api/v2/pokemon-species/61/"
        }
      }
    ],
    is_baby: false,
    species: {
      name: "poliwag",
      url: "https://pokeapi.co/api/v2/pokemon-species/60/"
    }
  },
  id: 26
};

let resultUPD = [];
resultUPD.push([data.chain.species]);
const getEvolves = object => {
  if (Array.isArray(object.evolves_to) && !object.evolves_to.length) {
    return object.evolves_to;
  }
  const resultNew = object.evolves_to.map(evolves =>
    getEvolves(evolves)
  );
  return [...object.evolves_to, ...resultNew].flat();
};
getEvolves(data.chain).map(elem =>
  resultUPD.push([elem.species])
);
console.log("result", resultUPD);

Ответы [ 2 ]

1 голос
/ 02 апреля 2020

Я действительно пытался выявить основные требования в комментариях. К сожалению, мы не могли нормально общаться. Либо ОП не понял, о чем я спрашивал, либо я не мог следить за ответами ОП.

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

Но сначала давайте поговорим о структурах данных.

Деревья

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

Это представление дерева, возможно, материнского генеалогического дерева с Betty в root:

                           Betty
                        _____|_____
                       /           \
                    Kathy          Grace
                   ___|___           |
                  /   |   \          |
             Alice  Carrie Helen   Faith
                    __|__          __|__
                   /     \        /     \
                Denise  Julie  Irene   Ellie
                                 |
                                 |
                              Louisa 

У Бетти есть две дочери, Kathy и Grace. У Кэти три дочери; У Грейс есть один, et c.

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

                             B
                        _____|_____
                       /           \
                      K             G
                   ___|___          |
                  /   |   \         |
                 A    C    H        F
                    __|__         __|__
                   /     \       /     \
                  D       J     I       E
                                |
                                |
                                L 

Если нам не нужно поддерживать форму дерева, а нужно просто посетить каждый узел, тогда мы можем действовать двумя способами. Мы можем go в ширину или в глубину . Ширина в ширину эквивалентна сканированию построчно, так что мы бы посетили это дерево в следующем порядке: Betty, Kathy, Grace, Alice, Carrie, Helen, * 1035. *, Denise, Julie, Irene, Ellie и Louisa.

Depth-first имеет несколько вариантов, предварительный заказ и post -порядок . (Для бинарных деревьев существует другой вариант, называемый по порядку , но он здесь не актуален.) В любом из них мы тщательно исследуем одну ветвь дерева, прежде чем перейти к другой. В предзаказе мы посещаем узел перед посещением его дочерних элементов, что дает нам порядок обхода для этого дерева Betty, Kathy, Alice, Carrie, Denise, Julie, Helen, Grace, Faith, Irene, Louisa и Ellie. В последующем порядке мы посещаем детей, прежде чем посетим узел, что дает порядок обхода Alice, Denise, Julie, Carrie, Helen, Louisa, Irene, Ellie, Faith, Grace и Betty.

(Все эти типы обхода также имеют обратную версию, которую я редко использую и не буду здесь упоминать.)

Ваше дерево покемонов

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

Во всех них мы передаем функцию для описания наших отношений родитель-ребенок. Идея заключается в том, что основы обхода остаются одинаковыми для многих различных структур. Если у нас есть узел и функция, которая позволяет нам перечислять его дочерние элементы, то мы можем повторно использовать наши обходы через эту и многие другие структуры. Я считаю, что стоит сохранить их, даже если вы не собираетесь повторно использовать код в другом месте сейчас. Это облегчает абстрактное размышление о проблеме и оставляет подробности в другом месте. Таким образом, каждая обсуждаемая основная функция принимает параметр getChildren, который должен быть функцией от узла к массиву дочерних элементов. Во всех этих примерах мы просто передаем его node => node.evolves_to. Но в другой структуре это может быть node => node.children или что-то более сложное.

Сначала в глубину, обход предварительного заказа

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

const depthFirst = (getChildren) => (node) => 
  [
    node, 
    ... (getChildren (node) || []) .flatMap (depthFirst (getChildren)),
  ]

const makePokeList = (pokes) =>
  depthFirst (node => node.evolves_to) (pokes .chain) 
    .map (({species}) => species)


const pokes = {baby_trigger_item: null, chain: {evolution_details: [], evolves_to: [{evolves_to: [{evolves_to: [], is_baby: false, species: {name: "poliwrath", url: "https://pokeapi.co/api/v2/pokemon-species/62/"}}, {evolves_to: [], is_baby: false, species: {name: "politoed", url: "https://pokeapi.co/api/v2/pokemon-species/186/"}}], is_baby: false, species: {name: "poliwhirl", url: "https://pokeapi.co/api/v2/pokemon-species/61/"}}], is_baby: false, species: {name: "poliwag", url: "https://pokeapi.co/api/v2/pokemon-species/60/"}}, id: 26};

console .log (
  makePokeList (pokes)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
  • depthFirst преобразует наше дерево в массив в предварительном порядке, в глубину. Это generi c, и его можно адаптировать ко многим различным древовидным структурам, передав соответствующую функцию getChildren. (Если вам нужен пост-порядок, то он тривиален: просто поменяйте местами две строки в возвращаемом массиве, возвращая строку node после строки ... getChildren ( ... ).) Обратите внимание, что все, что делает эта функция, это выравнивает это дерево в списке. в правильном порядке.

  • makePokeList более конкретно c для этой проблемы. Он преобразует дерево с помощью нашей общей функции node => node.evolvesTo в список, удаляя дочерние узлы из каждого элемента, а затем извлекая узел species из наших элементов. Он также начинается с извлечения узла дерева (poke .chan) из большей входной структуры. Это дает нам вывод, подобный следующему:

[
    {name: "poliwag", url: "https://pokeapi.co/api/v2/pokemon-species/60/"},
    {name: "poliwhirl", url: "https://pokeapi.co/api/v2/pokemon-species/61/"},
    {name: "poliwrath", url: "https://pokeapi.co/api/v2/pokemon-species/62/"},
    {name: "politoed", url: "https://pokeapi.co/api/v2/pokemon-species/186/"}
]

Функцию depthFirst можно использовать другими способами. Если вы не хотите map просматривать результаты, вы также можете отфильтровать их, уменьшить их до одного значения или даже просто посетить каждый с шаблоном посетителей:

const depthFirst = (getChildren) => (tree) => 
  [
    tree, 
    ... (getChildren (tree) || []) .flatMap (depthFirst (getChildren)),
  ]

const visitPokes = (visitor) => (pokes) => 
  depthFirst (node => node.evolves_to) (pokes .chain) 
    .forEach (({evolves_to, ...rest}) => visitor (rest))

const pokes = {baby_trigger_item: null, chain: {evolution_details: [], evolves_to: [{evolves_to: [{evolves_to: [], is_baby: false, species: {name: "poliwrath", url: "https://pokeapi.co/api/v2/pokemon-species/62/"}}, {evolves_to: [], is_baby: false, species: {name: "politoed", url: "https://pokeapi.co/api/v2/pokemon-species/186/"}}], is_baby: false, species: {name: "poliwhirl", url: "https://pokeapi.co/api/v2/pokemon-species/61/"}}], is_baby: false, species: {name: "poliwag", url: "https://pokeapi.co/api/v2/pokemon-species/60/"}}, id: 26};

visitPokes (console .log) (pokes)
  • visitPokes принимает функцию, которая принимает узел и что-то с ним делает. Он ничего не делает с этими результатами, так что в основном это хорошо для генерации побочных эффектов (таких как console.log ging узла или сохранение его в DOM.) Здесь мы проверяем это, просто регистрируя каждый узел.

Обход в ширину

С другой стороны, мы могли бы обойти это в ширину. Это, напомним, посещает всех потомков на одном уровне, прежде чем перейти на следующий. Таким образом, он посещает root, затем всех root детей, затем всех их детей и так далее. Эта версия ломается так же, как и предыдущая:

const breadthFirst = (getChildren) => (nodes) => {
  const children = nodes .flatMap (node => getChildren (node) || [])
  return [ 
    ... nodes, 
    ... (children.length ? breadthFirst (getChildren) (children) : [])
  ]
}

const makePokesList = (pokes) => 
  breadthFirst (node => node .evolves_to) ([pokes .chain])
    .map ((({species}) => species) )

const pokes = {baby_trigger_item: null, chain: {evolution_details: [], evolves_to: [{evolves_to: [{evolves_to: [], is_baby: false, species: {name: "poliwrath", url: "https://pokeapi.co/api/v2/pokemon-species/62/"}}, {evolves_to: [], is_baby: false, species: {name: "politoed", url: "https://pokeapi.co/api/v2/pokemon-species/186/"}}], is_baby: false, species: {name: "poliwhirl", url: "https://pokeapi.co/api/v2/pokemon-species/61/"}}], is_baby: false, species: {name: "poliwag", url: "https://pokeapi.co/api/v2/pokemon-species/60/"}}, id: 26};

console .log (
  makePokesList (pokes)
)

Вы заметите, что результаты выглядят так же, как и на глубине выше. Это только потому, что ваше дерево так просто. Для чего-то более сложного они, вероятно, будут другими.

Функция breadthFirst рекурсивно принимает массив узлов. Но для начала у нас есть один root узел. Здесь мы имеем дело с этим, заключая узел root в массив перед вызовом внутри makePokeList. Часто было бы лучше сделать эту внутреннюю вспомогательную функцию и написать функцию-обертку, которая не делает ничего, кроме выполнения переноса массива, что-то вроде этого:

const _breadthFirst = (getChildren) => (nodes) => {
  const children = nodes .flatMap (node => getChildren (node) || [])
  return [ 
    ... nodes, 
    ... (children.length ? _breadthFirst (getChildren) (children) : [])
  ]
}

const breadthFirst = (getChildren) => (node) => _breadthFirst (getChildren) ([node])

Тогда, конечно, makePokesList вызовет он использует (poke .chain), а не ([poke .chain]).

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

Map ping наше дерево

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

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

Вот одна из реализаций, снова использующая функцию getChildren, и:

const mapTree = (getChildren, setChildren) => (fn) => (node) =>
  setChildren ((getChildren (node) || []) .map (mapTree (getChildren, setChildren) (fn)), fn (node))

const makePokeList = pokes => mapTree 
  (node => node .evolves_to, (children, node) => ({...node, evolves_to: children}))
  (node => node .species)
  (pokes.chain)

const pokes = {baby_trigger_item: null, chain: {evolution_details: [], evolves_to: [{evolves_to: [{evolves_to: [], is_baby: false, species: {name: "poliwrath", url: "https://pokeapi.co/api/v2/pokemon-species/62/"}}, {evolves_to: [], is_baby: false, species: {name: "politoed", url: "https://pokeapi.co/api/v2/pokemon-species/186/"}}], is_baby: false, species: {name: "poliwhirl", url: "https://pokeapi.co/api/v2/pokemon-species/61/"}}], is_baby: false, species: {name: "poliwag", url: "https://pokeapi.co/api/v2/pokemon-species/60/"}}, id: 26};

console .log (
  makePokeList (pokes)
)

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

. Это приводит к выводу, подобному следующему:

{
  "name": "poliwag",
  "url": "https://pokeapi.co/api/v2/pokemon-species/60/",
  "evolves_to": [
    {
      "name": "poliwhirl",
      "url": "https://pokeapi.co/api/v2/pokemon-species/61/",
      "evolves_to": [
        {
          "name": "poliwrath",
          "url": "https://pokeapi.co/api/v2/pokemon-species/62/",
          "evolves_to": []
        },
        {
          "name": "politoed",
          "url": "https://pokeapi.co/api/v2/pokemon-species/186/",
          "evolves_to": []
        }
      ]
    }
  ]
}

Если вам не нужны пустые children узлы, вы можете заменить (children, node) => ({...node, evolves_to: children}) на (children, node) => ({...node, ...(children.length ? {evolves_to: children} : {})}). Или, если вам нужно другое имя для дочерних узлов, вы можете написать (children, node) => ({...node, kids: children}) или просто (children, node) => ({...node, children}).

Это, конечно, не дает вам никакого конкретного порядка итерации; это просто создает новое дерево. Но это может быть то, что вам нужно.

Вложенный массив на основе уровней

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

[
  ['Betty'],
  ['Kathy', 'Grace'],
  ['Alice', 'Carrie', 'Helen', 'Faith'],
  ['Denise', 'Julie', 'Irene', 'Ellie'],
  ['Louisa'] 
]

. Лично я считаю, что это довольно странная структура. Например, он не позволит вам различить guish между этими двумя деревьями:

                 B                               B            
            _____|_____                     _____|_____       
           /           \                   /           \      
          K             G                 K             G     
       ___|___          |              ___|___        __|__     
      /   |   \         |             /       \      /     \
     A    C    H        F            A         C    H       F     
        __|__         __|__          |         |    |       |
       /     \       /     \         |         |    |       |  
      D       J     I       E        D         J    I       E 
                    |                |                
                    |                |                      
                    L                L 

Но если это то, что вы хотите, мы можем создать что-то подобное из расширения нашей глубины. Первый поиск. Хитрость заключается в том, чтобы захватить некоторую дополнительную информацию для наших узлов. Если мы отслеживаем происхождение нашего узла из root, а также его содержимое, то это легко построить, сгруппировав по длине массива предков. Это также позволило бы нам делать более сложные вещи, такие как создание узлов, таких как poliwag > poliwhirl > poliwrath и poliwag > poliwhirl > politoed из вашего ввода, что может быть полезно.

Так что в этой версии depthFirst содержит дополнительный параметр вокруг узла, называемого path:

const depthFirst = (getChildren) => (tree, path) => 
  [
    {node: tree, path}, 
    ... (getChildren (tree) || []) .flatMap (node => depthFirst (getChildren) (node, [... path, tree]))
  ]

const makePokes = pokes => 
  Object .values (depthFirst (node => node .evolves_to) (pokes .chain, []) 
    .map (({node, path}) => ({node, depth: path .length})) 
    .reduce ((a, {node, depth}) => ({...a, [depth]: [...(a [depth] || []), node .species]}), {}))

const pokes = {baby_trigger_item: null, chain: {evolution_details: [], evolves_to: [{evolves_to: [{evolves_to: [], is_baby: false, species: {name: "poliwrath", url: "https://pokeapi.co/api/v2/pokemon-species/62/"}}, {evolves_to: [], is_baby: false, species: {name: "politoed", url: "https://pokeapi.co/api/v2/pokemon-species/186/"}}], is_baby: false, species: {name: "poliwhirl", url: "https://pokeapi.co/api/v2/pokemon-species/61/"}}], is_baby: false, species: {name: "poliwag", url: "https://pokeapi.co/api/v2/pokemon-species/60/"}}, id: 26};

console .log (
  makePokes (pokes)
)

Это избыточное решение для нашей текущей проблемы, и мы могли бы просто пропустить параметр depth. Но функции, подобные этой, должны быть довольно общими c, и, поскольку у нас есть весь путь, кажется, что лучше предоставить его, позволяя нашему коду проблемы, makePokes, иметь дело только с использованием соответствующего бита, length массива paths.

makePokes здесь более сложный. Мы используем эту версию depthFirst для сбора массива {node, path} объектов, а затем группируем свойства species узлов в массивы в соответствии с длиной пути, затем вызываем Object.values, чтобы взять только эти массивы .

Выводы

Нет, неважно, я слишком устал. Я надеюсь, что один из этих методов работает для вас.

0 голосов
/ 31 марта 2020

Или мой общий итератор, где уродливая рекурсия заканчивается в его конструкторе?

var json = {"baby_trigger_item":null,"chain":{"evolves_to":[{"evolves_to":[{"evolves_to":[],"is_baby":false,"species":{"name":"venusaur","url":"https://pokeapi.co/api/v2/pokemon-species/3/"}}],"is_baby":false,"species":{"name":"ivysaur","url":"https://pokeapi.co/api/v2/pokemon-species/2/"}}],"is_baby":false,"species":{"name":"bulbasaur","url":"https://pokeapi.co/api/v2/pokemon-species/1/"}},"id":1};

function iterate(glyphs) {
    var i = new JIterator(glyphs);
    var el;
    do {
        el = i.FindKey('name')
        if (!el) break;
        console.log(el.key, el.value);
        i.SetCurrent(el);
    } while(i.DepthFirst());
    // Deeper analyse
    i.Reset();
    var rep = ''
    do {
        rep += '->';
        if (i.Current.HasSimpleValue) rep += i.Key + ':' + i.Current.value + '\n';
        rep += ' Path:' + i.Path;
        rep += '\n Raw path array:' + JSON.stringify(i.RawPath()) + '\n';
    } while(i.DepthFirst());
    var pre = document.createElement('PRE');
    pre.innerText = rep;
    document.body.appendChild(pre)
}

'use strict';
var JNode = (function (jsNode) {
    function JNode(_json, _parent, _pred, _key, _value) {
        this.parent = _parent;
        this.pred = _pred;
        this.node = null;
        this.next = null;
        this.key = _key;
        this.value = _value;
        this.json = _json;
    }
    JNode.prototype = {
        get HasOwnKey() { return this.key && (this.key.constructor !== Number); },
        get HasStringValue() { return this.value.constructor === String; },
        get HasSimpleValue() {
            return this.value !== null
                && !(this.value instanceof Array)
                && !(this.value instanceof Object)
            }
    };
    return JNode;
})();
var JIterator = (function (json) {
    var JNodePrivates = (function (parent) {
        function JNodePrivates() {
            this.root = null;
            this.current = null;
        }
        JNodePrivates.prototype = {
            get Root() {
                return this.root;
            },
            setRoot: function (newRoot) {
                return (this.root = newRoot);
            },
            get Current() {
                return this.current;
            },
            setCurrent: function (newCurrent) {
                return (this.current = newCurrent);
            }
        };
        return JNodePrivates;
    })();
    var maxLevel = -1;
    function JIterator(json, parent) {
        if (this._privates === undefined) this._privates = new JNodePrivates();
        if (parent === undefined) parent = null;
        var pred = null, localCurrent;
        for (var child in json) {
            var obj = json[child] instanceof Object;
            if (json instanceof Array) child = parseInt(child); // non-associative array
            if (!this._privates.Root) this._privates.setRoot(localCurrent = new JNode(json, parent, null, child, json[child]));
            else {
                localCurrent = new JNode(json[child], parent, pred, child, obj ? ((json[child] instanceof Array) ? [] : {}) : json[child]);
            }
            if (pred) pred.next = localCurrent;
            if (parent && parent.node == null) parent.node = localCurrent;
            pred = localCurrent;
            if (obj) {
                var memPred = pred;
                JIterator.call(this, json[child], pred);
                pred = memPred;
            }
        }
        if (!this._privates.Current && this._privates.Root) this._privates.setCurrent(this._privates.Root);
        this.Level = 0;
    }
    JIterator.prototype = {
        // Public Getters
        get Current() { return this._privates.Current; },
        SetCurrent: function (newCurrent) { return this._privates.setCurrent(newCurrent); },
        get Path() {
            var steps = [], level = this._privates.Current;
            do {
                if (level != null && level.value instanceof Object) {
                    if (level.value instanceof Array) {
                        if (steps.length > 0) {
                            steps.push(level.key + '[' + steps.pop() + ']');
                        } else {
                            steps.push(level.key);
                        }
                    } else {
                        steps.push(level.key);
                    }
                } else {
                    if (level != null) steps.push(level.key);
                    else break;
                }
                level = level.parent;
            } while (level != null);
            steps.forEach(function (el, i) {
                if (!isNaN(el)) steps[i] = '[' + el + ']';
            });
            return steps.reverse().join('.');
        },
        // Public Setters
        set current(value) {
            console.log('Use SetCurrent(' + value + ') !');
            throw 'Access to current Denied !';
        },
        // Public methods
        Parent: function () {
            var retVal = this._privates.Current.parent;
            if (retVal == null) return false;
            this.Level--;
            return this._privates.setCurrent(retVal);
        },
        Pred: function () {
            var retVal = this._privates.Current.pred;
            if (retVal == null) return false;
            return this._privates.setCurrent(retVal);
        },
        Node: function () {
            var retVal = this._privates.Current.node;
            if (retVal == null) return false;
            this.Level++;
            return this._privates.setCurrent(retVal);
        },
        Next: function () {
            var retVal = this._privates.Current.next;
            if (retVal == null) return false;
            return this._privates.setCurrent(retVal);
        },
        get Key() {
            if (this._privates.Current) return this._privates.Current.key;
            return undefined;
        },
        KeyDots: function () { return (!this.HasOwnKey) ? '' : (this._privates.Current.key + ':'); },
        get Value() {
            if (this._privates.Current) return this._privates.Current.value;
            return undefined;
        },
        Reset: function () {
            this._privates.setCurrent(this._privates.Root);
            this.Level = 0;
        },
        RawPath: function () {
            var steps = [], level = this._privates.Current;
            do {
                if (level != null && level.value instanceof Object) {
                    steps.push(level.key + (level.value instanceof Array ? '[]' : '{}'));
                } else {
                    if (level != null) steps.push(level.key);
                    else break;
                }
                level = level.parent;
            } while (level != null);
            var retVal = '';
            retVal = steps.reverse();
            return retVal;
        },
        DepthFirst: function () {
            if (this._privates.Current == null) return 0; // exit sign
            if (this._privates.Current.node != null) {
                this._privates.setCurrent(this._privates.Current.node);
                this.Level++;
                if (maxLevel < this.Level) maxLevel = this.Level;
                return 1; // moved down
            } else if (this._privates.Current.next != null) {
                this._privates.setCurrent(this._privates.Current.next);
                return 2; // moved right
            } else {
                while (this._privates.Current != null) {
                    if (this._privates.Current.next != null) {
                        this._privates.setCurrent(this._privates.Current.next);
                        return 3; // returned up & moved next
                    }
                    this.Level--;
                    this._privates.setCurrent(this._privates.Current.parent);
                }
            }
            return 0; // exit sign
        },
        BreadthFirst: function () {
            if (this._privates.Current == null) return 0; // exit sign
            if (this._privates.Current.next) {
                this._privates.setCurrent(this._privates.Current.next);
                return 1; // moved right
            } else if (this._privates.Current.parent) {
                var level = this.Level;
                while (this.DepthFirst() && level !== this.Level);
                if (this._privates.Current) return 2; // returned up & moved next
                do {
                    this.Reset();
                    level++;
                    while (this.DepthFirst() && level !== this.Level);
                    if (this._privates.Current) return 3; // returned up & moved next
                } while (maxLevel >= level);
                return this._privates.Current != null ? 3 : 0;
            } else if (this._privates.Current.node) {
                this._privates.setCurrent(this._privates.Current.node);
                return 3;
            } else if (this._privates.Current.pred) {
                while (this._privates.Current.pred) this._privates.setCurrent(this._privates.Current.pred);
                while (this._privates.Current && !this._privates.Current.node) this._privates.setCurrent(this._privates.Current.next);
                if (!this._privates.Current) return null;
                else return this.DepthFirst();
            }
        },
        ReadArray: function () {
            var retVal = {};
            var item = this._privates.Current;
            do {
                if (item.value instanceof Object) {
                    if (item.value.length === 0) retVal[item.key] = item.node;
                    else retVal[item.key] = item;
                } else retVal[item.key] = item.value;
                item = item.next;
            } while (item != null);
            return retVal;
        },
        FindKey: function (key) {
            var pos = this._privates.Current;
            while (this._privates.Current && this._privates.Current.key !== key) {
                if (!this.DepthFirst()) {
                    this._privates.setCurrent(pos);
                    return null;
                }
            }
            if (this._privates.Current.key === key) {
                var retVal = this._privates.Current;
                this._privates.setCurrent(pos);
                return retVal;
            } else {
                this._privates.setCurrent(pos);
                return null;
            }
        },
        FindValue: function (val) {
            var pos = this._privates.Current;
            while (this._privates.Current && this._privates.Current.value !== val) this.DepthFirst();
            if (this._privates.Current.value === val) {
                var retVal = this._privates.Current;
                this._privates.setCurrent(pos);
                return retVal;
            } else {
                this._privates.setCurrent(pos);
                return null;
            }
        },
        FindPair: function (key, value, move2) {
            var pos = this._privates.current;
            while (this._privates.current) {
                if (this._privates.current.key === key && this._privates.current.value === value) {
                    break;
                } else this.DepthFirst();
            }
            if (move2) return this._privates.current;
            var retVal = this._privates.current;
            this.SetCurrent(pos);
            return retVal;
        },
        // Debug info methods
        PathDetails: function (brief) {
            var steps = [], level = this._privates.Current;
            do {
                if (level != null && level.value instanceof Object) {
                    var size = 0;
                    var items = level.node;
                    if (!level.HasOwnKey && !brief) steps.push('[' + level.key + ']');
                    else {
                        if (brief) {
                            if (level.HasOwnKey) steps.push(level.key);
                        } else {
                            while (items) {
                                size++;
                                items = items.next;
                            }
                            var type = (level.value instanceof Array ? '[]' : '{}');
                            var prev = steps[steps.length - 1];
                            if (prev && prev[0] === '[') {
                                var last = prev.length - 1;
                                if (prev[last] === ']') {
                                    last--;
                                    if (!isNaN(prev.substr(1, last))) {
                                        steps.pop();
                                        size += '.' + prev.substr(1, last);
                                    }
                                }
                            }
                            steps.push(level.key + type[0] + size + type[1]);
                        }
                    }
                } else {
                    if (level != null) {
                        if (!level.HasOwnKey) steps.push('[' + level.key + ']');
                        else steps.push(level.key);
                    }
                    else break;
                }
                level = level.parent;
            } while (level != null);
            var retVal = "";
            retVal = steps.reverse();
            return retVal;
        },
        Move2path: function (json) {
            var nd = this._privates.current;
            var pth = [nd];
            while (nd.parent) {
                nd = nd.parent;
                pth.push(nd);
            }
            pth.reverse();
            for (var x in pth) json = json[pth[x].key];
            return json;
        },
        CreateNode: function (json, key, value) {
            var current = this._privates.Current;
            if (current) {
                current.node = new JNode(json, current, current.pred, key, value);
                if (isNaN(key)) json[key] = value;
                return current.node;
            } else {
                current = new JNode(json, null, null, key, value);
                json[key] = value;
                this._privates.setCurrent(current);
                this._privates.setRoot(current);
                return current;
            }
        }
    };
    return JIterator;
})();

iterate(json);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...