Перебор массива объектов со ссылками друг на друга и преобразование их из строки в объект - PullRequest
3 голосов
/ 11 июля 2019

Я создаю систему пресетов, в которой пресеты могут «включать» другие пресеты.

Пресет A включает в себя пресеты B и C, в то время как пресет B включает D и E.

Все ониимеют следующую структуру: - id - имя (строка, используемая в качестве ссылки в include) - content (массив строк) - include (массив имени, соответствующий имени prop)

Содержимое - это то, что включается.

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

function getIncludes (original) {
    let output = [];

    function recursion (package) {
        if (package.content) output.push(package.content.join(' '));
        if (package.include) {
            return package.include.forEach(str => {
                let c = presets.find(obj => obj.name === str);
                if (c.content) output.push(c.content.join(' '));
                    recursion(c)
            });
        }
    }

    recursion(original);
    return output.join(' ');
}

пример пресетов obj

[
  {
    "id": 0,
    "name": "videoFormats",
    "content": ["(avi|mkv|mov|mp4|mpg|wmv)"],
    "hidden": true,
    "include": ["imageFormats"]
  },
  {
    "name": "audioFormats",
    "id": 1,
    "content": ["(ac3|flac|m4a|mp3|ogg|wav|wma)"],
    "hidden": true,
    "include": ["imageFormats"]
  },
  {
    "id": 2,
    "name": "imageFormats",
    "content": ["(bmp|gif|jpg|jpeg|png|psd|tif|tiff)"],
    "hidden": true
  },
  {
    "id": 3,
    "name": "media",
    "title": "Media",
    "include": ["videoFormats", "audioFormats"],
    "hidden": false
  }
]

Мне нужна функция, которая дает мне список пресетов,выбранный пресет зависит от.

Функция, подобная этой, будет работать.

getIncludes("media") returning ["videoFormats", "audioFormats", "imageFormats"]

Ответы [ 2 ]

1 голос
/ 11 июля 2019

Сначала нам нужно подумать о некотором типе T, который позволяет эффективно искать конкретную предустановку с помощью name.Массивы не предоставляют такой возможности, поэтому мы конвертируем из Array в желаемый тип, T.В этом случае мы будем использовать Map -

// type preset =
//   { id: number
//   , name: string
//   , content: string array
//   , hidden: bool
//   , include: string array
//   }

// type t =
//   (string, preset) map

Выше мы видим t как map, который имеет string клавиш, каждая из которых указывает на значение preset.Теперь мы можем написать fromArray -

// fromArray : preset array -> t
const fromArray = (a = []) =>
  a.reduce((r,x) => r.set(x.name, x), new Map)

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

// traverse : (t, string) -> preset generator
const traverse = function* (t = new Map, name = "") {
  if (!t.has(name)) return
  yield* traverse1(t, t.get(name))
}

// traverse1 : (t, preset) -> preset generator
const traverse1 = function* (t = new Map, preset = {}) {
  yield preset
  for (const i of preset.include || [])
    yield* traverse(t, i)
}

Теперь наша функция getIncludes может быть простой программой.Ему больше не нужно заниматься обходом дерева, и вместо этого он может сосредоточиться на преобразовании линейной последовательности preset элементов в желаемый набор строк -

const getIncludes = (t = new Map, name = "") =>
{ const r = new Set
  for (const p of traverse(t, name))
    if (r.has(p.name) || p.name === name)
      continue
    else
      r.add(p.name)
  return Array.from(r)
}

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

const tree =
  fromArray(presets)

getIncludes(tree, "media")
// [ "videoFormats", "imageFormats", "audioFormats" ]

getIncludes(tree, "audioFormats")
// [ "imageFormats" ]

getIncludes(tree, "imageFormats")
// []

Разверните фрагмент ниже, чтобы проверить результаты в вашем собственном браузере -

const presets = 
[ { id: 0
  , name: "videoFormats"
  , content: ["(avi|mkv|mov|mp4|mpg|wmv)"]
  , hidden: true
  , include: ["imageFormats"]
  }
, { id: 1
  , name: "audioFormats"
  , content: ["(ac3|flac|m4a|mp3|ogg|wav|wma)"]
  , hidden: true
  , include: ["imageFormats"]
  }
, { id: 2
  , name: "imageFormats"
  , content: ["(bmp|gif|jpg|jpeg|png|psd|tif|tiff)"]
  , hidden: true
  }
, { id: 3
  , name: "media"
  , title: "Media"
  , include: ["videoFormats", "audioFormats"]
  , hidden: false
  }
]

const fromArray = (a = []) =>
  a.reduce((r,x) => r.set(x.name, x), new Map)

const traverse = function* (t = new Map, name = "") {
  if (!t.has(name)) return
  yield* traverse1(t, t.get(name))
}

const traverse1 = function* (t = new Map, preset = {}) {
  yield preset
  for (const i of preset.include || [])
    yield* traverse(t, i)
}

const getIncludes = (t = new Map, name = "") =>
{ const r = new Set
  for (const p of traverse(t, name))
    if (r.has(p.name) || p.name === name)
      continue
    else
      r.add(p.name)
  return Array.from(r)
}

const tree =
  fromArray(presets)

console.log(getIncludes(tree, "media"))
// [ "videoFormats", "imageFormats", "audioFormats" ]

console.log(getIncludes(tree, "audioFormats"))
// [ "imageFormats" ]

console.log(getIncludes(tree, "imageFormats"))
// []
1 голос
/ 11 июля 2019

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

function getIncludes(original) {
    if (!presets[original]) return [];
    return [].concat(presets[original].include || []).reduce(
        (r, name) => [...r, ...getIncludes(name)],
        [original]
    );
}

var data = [{ id: 0, name: "videoFormats", content: ["(avi|mkv|mov|mp4|mpg|wmv)"], hidden: true, include: "imageFormats" }, { id: 1, name: "audioFormats", content: ["(ac3|flac|m4a|mp3|ogg|wav|wma)"], hidden: true, include: "imageFormats" }, { id: 2, name: "imageFormats", content: ["(bmp|gif|jpg|jpeg|png|psd|tif|tiff)"], hidden: true }, { id: 3, name: "media", title: "Media", include: ["videoFormats", "audioFormats"], hidden: false }],
    presets = data.reduce((r, o) => (r[o.name] = o, r), {});

console.log(getIncludes('videoFormats'));
console.log(getIncludes('audioFormats'));
console.log(getIncludes('imageFormats'));
console.log(getIncludes('media'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

function getIncludes(original) {
    var stack = [original],
        name,
        result = [];

    while (stack.length) {
        name = stack.shift();
        if (result.includes(name) || !presets[name]) continue;
        result.push(name);
        stack.push(...[].concat(presets[name].include || []));
    }
    return result;
}

var data = [{ id: 0, name: "videoFormats", content: ["(avi|mkv|mov|mp4|mpg|wmv)"], hidden: true, include: "imageFormats" }, { id: 1, name: "audioFormats", content: ["(ac3|flac|m4a|mp3|ogg|wav|wma)"], hidden: true, include: "imageFormats" }, { id: 2, name: "imageFormats", content: ["(bmp|gif|jpg|jpeg|png|psd|tif|tiff)"], hidden: true }, { id: 3, name: "media", title: "Media", include: ["videoFormats", "audioFormats"], hidden: false }],
    presets = data.reduce((r, o) => (r[o.name] = o, r), {});

console.log(getIncludes('videoFormats'));
console.log(getIncludes('audioFormats'));
console.log(getIncludes('imageFormats'));
console.log(getIncludes('media'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...