оптимизация построения массива из вложенных массивов объектов

#javascript #arrays #object

#javascript #массивы #объект

Вопрос:

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

Я не совсем уверен, как объяснить это словами, поэтому вот некоторый код, объясняющий, что я пытаюсь оптимизировать:

 // test case
var first = [
    { second: [ { id:  1}, { id:  2}, { id:  3} ] },
    { second: [ { id:  4}, { id:  5}, { id:  6} ] },
    { second: [ { id:  7}, { id:  8}, { id:  9} ] },
    { second: [ { id: 10}, { id: 11}, { id: 12} ] }
];

// where the id values will be stored
var arrIDs = [];

// extracting the id values
for (var i=0; i<first.length; i  ){
    for (var j=0; j<first[j].second.length; j  ){
        // I want to avoid all these push() calls
        arrIDs.push(first[j].second[j].id);
    }
}
  

И это конечный результат, которого я хочу достичь:

 arrIDs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
  

Мне может понадобиться это с 3 или 4 вложенными уровнями, но я могу использовать другую структуру, чтобы уменьшить ее до 2 уровней, если невозможно оптимизировать больше, чем это.

Что я хотел бы оптимизировать больше всего, так это на самом деле все эти вызовы Array().push() внутри циклов for.

Кто-нибудь знает хороший способ сделать это?

Редактировать

Я забыл упомянуть, что мне нужно было бы использовать это в среде, где IE8 является лучшим вариантом, который у нас есть. Таким образом, ES6 — это не вариант.

Комментарии:

1. пожалуйста, добавьте, как выглядит 3-й или 4-й уровень.

2. точно так же, как на первом-втором уровнях. Каждый второй уровень будет иметь свойство, содержащее массив, каждое поле массива является другим объектом, и так далее до 4 уровней глубиной.

Ответ №1:

Для вашей структуры данных вы можете использовать reduce() и map() .

 var first = [
  { second: [ { id:  1}, { id:  2}, { id:  3} ] },
  { second: [ { id:  4}, { id:  5}, { id:  6} ] },
  { second: [ { id:  7}, { id:  6}, { id:  9} ] },
  { second: [ { id: 10}, { id: 11}, { id: 12} ] }
];

var result = first.reduce(function(r, o) {
  r = r.concat(o.second.map(function(e) {
    return e.id;
  }))
  return r;
}, []);
console.log(result)  

Версия ES6

 var first = [
  { second: [ { id:  1}, { id:  2}, { id:  3} ] },
  { second: [ { id:  4}, { id:  5}, { id:  6} ] },
  { second: [ { id:  7}, { id:  6}, { id:  9} ] },
  { second: [ { id: 10}, { id: 11}, { id: 12} ] }
];

var result = first.reduce((r, o) => r.concat(o.second.map(e => e.id)) , []);
console.log(result)  

Комментарии:

1. Глядя на решения в комментариях, сравнивая каждое, кажется, что это решение является лучшим. Однако требуется ли для этого ES6? Среда, в которой я буду его запускать, будет иметь максимум IE8, а ES6 недоступен…

Ответ №2:

Вы могли бы использовать эту рекурсивную функцию ES6. Это работает для любого уровня, и вы можете передать ему массив, обычный объект или что-либо еще (в последнем случае вы получите пустой массив в качестве результата):

 function getAll(obj, key) {
    return obj !== Object(obj) ? [] 
        : Object.keys(obj).reduce ( (acc, k) => 
            acc.concat(k == key ? obj[k] : getAll(obj[k], key)), [] ); 
}

// sample data with different levels and mix of array / object alterations
var first = [
  { a: 2, id:  1}, { second: [ { id:  2}, { id:  3} ] },
  { second: [ { id:  4}, { id:  5}, { id:  6} ] },
  { second: [ { id:  7}, { third: {id:  6}}, { id:  9} ] },
  { second: [ { id: 10}, { id: 11}, { id: 12} ] }
];

var result = getAll(first, 'id');

console.log(result);  
 .as-console-wrapper { max-height: 100% !important; top: 0; }  

Для совместимости с IE8:

 function getAll(obj, key) {
    if (typeof obj != 'object' || obj == null) return [];
    var acc = [];
    for (var k in obj) {
        acc = acc.concat( k == key ? obj[k] : getAll(obj[k], key) );
    }
    return acc; 
}

// sample data with different levels and mix of array / object alterations
var first = [
  { a: 2, id:  1}, { second: [ { id:  2}, { id:  3} ] },
  { second: [ { id:  4}, { id:  5}, { id:  6} ] },
  { second: [ { id:  7}, { third: {id:  6}}, { id:  9} ] },
  { second: [ { id: 10}, { id: 11}, { id: 12} ] }
];

var result = getAll(first, 'id');

console.log(result);  
 .as-console-wrapper { max-height: 100% !important; top: 0; }  

Комментарии:

1. Выглядит довольно аккуратно, но мне нужно использовать его в среде, где доступен максимум IE8. ES6 недоступен в IE < 9. Но все равно отличное решение 🙂

2. Ах, это похоже на важную информацию. Не могли бы вы добавить это к своему вопросу?

3. Добавлено примечание об ES6 в вопросе. Версия вашего кода для совместимости также выглядит довольно неплохо. Спасибо, что уделили этому время!

Ответ №3:

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

 function getValues(o, key) {
    return o !== null amp;amp; typeof o === 'object' amp;amp; Object.keys(o).reduce(function (r, k) {
        return r.concat(k === key ? o[k] : getValues(o[k], key));
    }, []) || [];
}

var first = [{ second: [{ id: 1 }, { id: 2 }, { id: 3 }] }, { second: [{ id: 4 }, { id: 5 }, { id: 6 }] }, { second: [{ id: 7 }, { id: 6 }, { id: 9 }] }, { second: [{ id: 10 }, { id: 11 }, { id: 12 }] }];

console.log(getValues(first, 'id'));  
 .as-console-wrapper { max-height: 100% !important; top: 0; }  

Комментарии:

1. Разве это не то же самое, что циклы for, просто написано по-другому? Я имею в виду, что есть два вызова forEach (), которые являются просто сокращением для обычного for (var i=0; i<a.length; i )

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

3. Вы правы, циклы будут циклами, независимо от того, как вы их напишете.