JavaScript: нужен общий массив между рекурсиями для отправки результатов в - PullRequest
0 голосов
/ 27 августа 2018

Существует массив, в котором есть семейное древо, в котором есть имена людей и их пол, а также имена их родителей (отца и матери).

Теперь я пытаюсь перечислить имена всех детей в этой семье (безуспешно):

function allChildren(parent) {
  var children = ancestors.filter(
    function(person) {
      if (parent.sex == 'm') {
        return person.father == parent.name
      } else {
        return person.mother == parent.name
      }
    }
  )
  if (children.length == 0) {
    console.log(parent.name);
  } else {
    children.forEach(allChildren);
  }
}

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

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

Ответы [ 5 ]

0 голосов
/ 28 августа 2018

Вот подход, аналогичный подходу от KarelG. Однако он достаточно отличается, чтобы оправдать свой ответ.

Отличия

  • Этот параметр передает предков в качестве параметра, а не сохраняет их в качестве свободной переменной в функции. Свободные переменные усложняют понимание и тестирование кода.

  • Он отделяет рекурсивную функцию от публичной функции, удерживая их вместе в замыкании. Это менее распространенная техника сейчас, когда люди используют какой-то модуль во многих местах, но он все еще может быть полезен, особенно в Интернете. Это синтаксис (() => {/* ... return some function */ })().

  • Он использует набор в качестве рекурсивного аккумулятора, чтобы избежать дублирования.

  • Он игнорирует пол. Я не вижу причины для проверки. Если ребенок говорит, что мать такая-то, я не вижу смысла перепроверять этот `mother.sex === 'f'``. Я мог бы что-то здесь упустить.

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

  • Я назвал это allDescendents, поскольку это казалось более точным, чем allChildren.

код

const allDescendents = (() => {
  const all = (family, name, children = new Set()) => {
    const kids = family.filter(p => p.father === name || p.mother === name).map(k => k.name)
    kids.forEach(kid => (children.add(kid), all(family, kid, children)))
    return children
  }
  
  return (family, person) => Array.from(all(family, person.name))
})()

const bourbons = [ // https://en.wikipedia.org/wiki/Bourbon_family_tree
  {name: 'Philip III', sex: 'm', father: 'Louis IX', mother: 'Margaret'},
  {name: 'Robert', sex: 'm', father: 'Louis IX', mother: 'Margaret'},
  {name: 'Charles', sex: 'm', father: 'Philip III', mother: '?'},
  {name: 'Louis I', sex: 'm', father: 'Robert', mother: 'Beatrice'},
  {name: 'Philip IV', sex: 'm', father: 'Charles', mother: '?'},
  {name: 'Isabella', sex: 'f', father: 'Charles', mother: '?'},
  {name: 'Peter I', sex: 'm', father: 'Louis I', mother: 'Mary'},
  {name: 'James I', sex: 'm', father: 'Louis I', mother: 'Mary'},
  {name: 'John II', sex: 'm', father: 'Philip IV', mother: '?'},
  {name: 'Peter II', sex: 'm', father: 'James I', mother: 'Jeanne'},
  {name: 'John I', sex: 'm', father: 'James I', mother: 'Jeanne'},
  {name: 'Charles V', sex: 'm', father: 'John II', mother: '?'},
  {name: 'Joanna', sex: 'f', father: 'Peter I', mother: 'Isabella'},
  {name: 'Louis II', sex: 'f', father: 'Peter I', mother: 'Isabella'},
  {name: 'James II', sex: 'm', father: 'John I', mother: 'Catherine'},
  {name: 'Louis', sex: 'm', father: 'John I', mother: 'Catherine'},
  /* .. */
]

const louis1st = bourbons[3]

console.log(allDescendents(bourbons, louis1st))

Варианты

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

Возврат Set

Возможно, имеет смысл возвращать Set значений, а не Array. Предположительно, вы не хотите, чтобы эта коллекция содержала дубликаты. Set - правильная структура данных для этого. Это решение использует Set внутри, и сделать его общедоступным интерфейсом тривиально. Просто уберите звонок на Array.from:

-   return (family, person) => Array.from(all(family, person.name))
+   return (family, person) => all(family, person.name)

Введите имя

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

-   return (family, person) => Array.from(all(family, person.name))
+   return (family, name) => Array.from(all(family, name))

Возвращение людей

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

-  const all = (family, name, children = new Set()) => {
-    const kids = family.filter(p => p.father === name || p.mother === name).map(k => k.name)
+  const all = (family, person, children = new Set()) => {
+   const kids = family.filter(p => p.father === person.name || p.mother === person.name)

-  return (family, person) => Array.from(all(family, person.name))
+  return (family, person) => Array.from(all(family, person))

Как избежать параметров по умолчанию

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

-   const all = (family, name, children = new Set()) => {
+   const all = (family, name, children) => {

-   return (family, person) => Array.from(all(family, person.name))
+   return (family, person) => Array.from(all(family, person.name, new Set()))

Создание модуля

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

const all = (family, name, children = new Set()) => {
  const kids = family.filter(p => p.father === name || p.mother === name).map(k => k.name)
  kids.forEach(kid => (children.add(kid), all(family, kid, children)))
  return children
}

const allDescendents = (family, person) => Array.from(all(family, person.name))

export {allDescendents}
0 голосов
/ 28 августа 2018

Вот решение, которое я придумала для себя, которое отлично работает. Он использует рекурсивную функцию, а также использует концепцию замыкания в JavaScript.

Итак, я обернул рекурсивную логику, о которой я упоминал ранее в своем вопросе, в отдельную внутреннюю функцию (которая называется «все» - быстро и плохо, я знаю -) внутри основной функции (которая называется по имени "allChildren"):

function allChildren(parent) {
    var firstParentName = parent.name;
    var allChildrenArray = [];

    function all(parent) {
        var children = ancestors.filter(
            function(person) {
                if (parent.sex == 'm') {
                    return person.father == parent.name
                } else {
                    return person.mother == parent.name
                }
            }
        )
        if (children.length == 0) {
            if (allChildrenArray.indexOf(parent.name) == -1 &&
                parent.name != firstParentName) {
                allChildrenArray.push(parent.name);
            }
            return allChildrenArray;
        } else {
            children.map(function(child) {
                if (allChildrenArray.indexOf(child.name) == -1) {
                    allChildrenArray.push(child.name);
                }
            });
            children.forEach(all);
            return allChildrenArray;
        }
    }

    return all(parent);
}

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

Наконец, вызов этой функции с использованием предоставленных данных здесь , например:

  console.log(allChildren(byName['Pauwels van Haverbeke'])); //byName is just a simple map from names to objects of the family tree. I haven't put the definition for the sake of brevity.

успешно дает этот результат:

(24) ["Lieven van Haverbeke", "Pieter Haverbeke", "Lieven Haverbeke", "Willem Haverbeke", "Daniel Haverbeke", "Jan Haverbeke", "Pieter Bernard Haverbeke", "Angela Haverbeke", "Jan Francies Haverbeke", "Pieter Antone Haverbeke", "Carel Haverbeke", "Carolus Haverbeke", "Emile Haverbeke", "Philibert Haverbeke", "Maria Haverbeke", "Livina Haverbeke", "Bernardus de Causmaecker", "Petronella de Decker", "Joanna de Causmaecker", "Maria van Brussel", "Elisabeth Haverbeke", "Laurentia Haverbeke", "Jacobus Bernardus van Brussel", "Jan Frans van Brussel"]
0 голосов
/ 27 августа 2018

Вот один короткий способ сделать это (если вы можете использовать функцию стрелки и деструктуризацию массива, но вы все равно это понимаете):

function allChildren(parent) {
    const children = ancestors.filter((person) => 
        (parent.sex === 'm' && person.father === parent.name) || (parent.sex === 'f' && person.mother === parent.name));
    return [
        parent, 
        ...children.reduce((grandChildren, child) => 
            [...grandChildren, ...allChildren(child)], [])
   ];
}
0 голосов
/ 28 августа 2018

Я прокомментировал с

что не так с использованием хвостовой рекурсии? Это сделает вашу жизнь проще ...

Что я сделал, так это написал функцию, которая выполняет задачу с хвостовой рекурсией. Используя второй аргумент в качестве держателя данных, вы можете передать массив данных через дерево рекурсии. Более подробную информацию о хвостовой рекурсии можно найти в этом посте: Что такое хвостовая рекурсия?

Я создал функцию (интерактивный пример внизу)

function showChildrenOf(person, children = []) {
  // ...
}

children = [] означает, что массив [] используется в качестве аргумента по умолчанию, если аргумент не был предоставлен. Концепция default parameters. Поэтому в начале дерева рекурсии вы можете просто вызвать функцию с помощью showChildrenOf(person), чтобы получить массив всех его потомков. В теле функции я заполняю children, если у человека есть дети. Затем я снова вызываю функцию с showChildrenOf(child, children), так что следующий вызов функции происходит с дочерним элементом, чтобы проверить, есть ли у него дочерние элементы и заполненный массив в качестве держателя данных. Поскольку я предоставил аргумент, следующая обработка функции имеет children=[...] вместо пустого массива.

const persons =
[
 {name: 'Who Am I', father: 'Dex Bob', mother: 'Alice Mania', g:'m'}
,{name: 'Mic Mac', father: 'Who Am I', mother: 'Mega Wonder', g:'m'}
,{name: 'Ani Mani', father: 'Bob Y', mother: 'Women Wonder', g:'f'}
,{name: 'Jan Jot', father: 'Who Am I', mother: 'Wunder Bra', g:'m'}
,{name: 'Kit Kat', father: 'Jan Jot', mother: 'Unknown', g:'m'}
,{name: 'Tiny Bell', father: 'Kit Kat', mother: 'Unknown', g:'f'}
,{name: 'Bill Kid', father: 'Kit Kat', mother: 'Unknown', g:'m'}
,{name: 'Billy Wuz', father: 'Oreos Oo', mother: 'Tiny Bell', g:'m'}
];

function showChildrenOf(person, children = []) {
  const tmp = persons.filter(p => (person.g === 'm' ? person.name === p.father : person.name === p.mother));
  if (tmp.length) {
    // has children
    children.push(...tmp);
    tmp.forEach(child => showChildrenOf(child, children));
  }
  else {
    // does not have children: do nothing
  }
  return children;
}

console.log(showChildrenOf(persons[0])); // showing 'Who Am I' children
console.log(showChildrenOf(persons[1])); // showing 'Mic Mac' children
0 голосов
/ 27 августа 2018

Теперь с функцией объема

function allChildren(parent) {
  var getChildrenRecursive = function(parent, elements){
    var children = ancestors.filter(
      function(person) {
        if (parent.sex == 'm') {
          return person.father == parent.name
        } else {
          return person.mother == parent.name
        }
      }
    )
    if (children.length == 0) {
      console.log(parent.name);
    } else {
      elements = elements.concat(children);
      children.forEach(function(child){elements = elements.concat(getChildrenRecursive(child, elements));})
      return elements;
    }
  };

  return getChildrenRecursive(parent, []);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...