Я работаю над Node/JS с lodash и пытаюсь преобразовать массив массивов в хэш-объект, чтобы:
[ [ 'uk', 'london', 'british museum' ],
[ 'uk', 'london', 'tate modern' ],
[ 'uk', 'cambridge', 'fitzwilliam museum' ],
[ 'russia', 'moscow', 'tretyakovskaya gallery' ],
[ 'russia', 'st. petersburg', 'hermitage' ],
[ 'russia', 'st. petersburg', 'winter palace' ],
[ 'russia', 'st. petersburg', 'russian museum' ] ]
становится такой структурой хэша/дерева:
{ uk: {
london: [ 'british museum', 'tate modern' ],
cambridge: [ 'fitzwilliam museum' ]
},
russia: {
moscow: [ 'tretyakovskaya gallery' ],
'st petersburg': ['hermitage', 'winter palace', 'russian museum']
}
}
До сих пор я использовал такой код:
function recTree(arr) {
// We only take in arrays of arrays
if (arr.constructor === Array && arr[0].constructor === Array) {
// If array has a single element, return it
if (arr.length === 1) {
return arr[0];
}
// Group array by first element
let grouped = _.groupBy(arr, function(o) {
return o[0]
});
let clean = _.mapValues(grouped, function(o) {
return _.map(o, function(n) {
// Cut off first element
let tail = n.slice(1);
if (tail.constructor === Array && tail.length == 1) {
// If there is a single element, return it
return tail[0];
} else {
return tail;
}
});
});
return _.mapValues(clean, recTree)
} else {
// If it not an array of arrays, return it
return arr;
}
}
Мне интересно, есть ли более чистый, более функциональный способ сделать это, чем то, что я запрограммировал до сих пор. В идеале я хотел бы функционировать, чтобы иметь возможность принимать массивы массивов произвольных (но постоянных, так что все внутренние массивы одинаковы) длиной (не только 3)
Вот решение lodash, которое работает для любых массивов с переменной длиной.
Используйте lodash#reduce
чтобы уменьшить массив в его object
форме.
В каждой итерации сокращения:
2.1. Мы получаем path
к значению, которое мы хотим установить, например, uk.london
, используя lodash#initial
.
2.2. Используйте lodash#last
, чтобы получить значение из внутреннего массива, который мы хотим объединить.
2.3. Используйте lodash#get
чтобы получить любой существующий массив из object
с помощью path
, если он не получает никакого значения, а затем по умолчанию используется пустой массив. После получения значения мы объединяем последний элемент внутреннего массива по отношению к полученному значению.
2.4. Используйте lodash#set
чтобы установить результирующий object
из value
взятого из 2.3, используя path
взятый из 2.1.
var result = _.reduce(array, function(object, item) {
var path = _.initial(item);
var value = _.get(object, path, []).concat(_.last(item));
return _.set(object, path, value);
}, {});
var array = [
['uk', 'london', 'british museum'],
['uk', 'london', 'tate modern'],
['uk', 'cambridge', 'fitzwilliam museum'],
['russia', 'moscow', 'tretyakovskaya gallery'],
['russia', 'st. petersburg', 'hermitage'],
['russia', 'st. petersburg', 'winter palace'],
['russia', 'st. petersburg', 'russian museum']
];
var result = _.reduce(array, function(object, item) {
var path = _.initial(item);
var value = _.get(object, path, []).concat(_.last(item));
return _.set(object, path, value);
}, {});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Вы можете использовать Array#reduce
для создания/доступа к вложенной структуре данных и нажать последний элемент массива.
EDIT: Это решение работает для произвольной длины внутренних массивов.
var array = [['foo', 'bar', 'baz', 42], ['uk', 'london', 'british museum'], ['uk', 'london', 'tate modern'], ['uk', 'cambridge', 'fitzwilliam museum'], ['russia', 'moscow', 'tretyakovskaya gallery'], ['russia', 'st. petersburg', 'hermitage'], ['russia', 'st. petersburg', 'winter palace'], ['russia', 'st. petersburg', 'russian museum']],
object = {};
array.forEach(function (a) {
a.slice(0, -1).reduce(function (o, k, i, kk) {
return o[k] = o[k] || kk.length - 1 - i && {} || [];
}, object).push(a[a.length - 1]);
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6
var array = [['foo', 'bar', 'baz', 42], ['uk', 'london', 'british museum'], ['uk', 'london', 'tate modern'], ['uk', 'cambridge', 'fitzwilliam museum'], ['russia', 'moscow', 'tretyakovskaya gallery'], ['russia', 'st. petersburg', 'hermitage'], ['russia', 'st. petersburg', 'winter palace'], ['russia', 'st. petersburg', 'russian museum']],
object = {};
array.forEach(
a => a
.slice(0, -1)
.reduce((o, k, i, kk) => o[k] = o[k] || kk.length - 1 - i && {} || [], object)
.push(a[a.length - 1])
);
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
a
Как функциональный, так и без использования lodash, в этом решении внутренние массивы могут иметь переменную длину:
var arrayOfArrays = [ [ 'uk', 'london', 'british museum' ], [ 'uk', 'london', 'tate modern' ], [ 'uk', 'cambridge', 'fitzwilliam museum' ], [ 'russia', 'moscow', 'tretyakovskaya gallery' ], [ 'russia', 'st. petersburg', 'hermitage' ], [ 'russia', 'st. petersburg', 'winter palace' ], [ 'russia', 'st. petersburg', 'russian museum' ] ]
var resultingObject = arrayOfArrays.reduce(function(obj, arr) {
arr.reduce(function(parent, value, i) {
if (i < arr.length - 1) {
parent[value] = parent[value] || (i < arr.length - 2 ? {} : []);
return parent[value];
}
parent.push(value);
}, obj);
return obj;
}, {});
console.log(resultingObject);
Возможно, группировка и сопоставление не являются более эффективным способом решения вашей проблемы.
Лучший подход, когда вы разрабатываете свои данные только один раз, должен состоять в создании вашей карты, когда вы читаете свой список.
И я как можно скорее избавлюсь от функции, если предварительное условие не будет выполнено, код мне легче понять.
Другая проблема, которую я бы не получил в учетной записи, - это возврат первого элемента входного массива в случае, если список имеет длину всего одну: при правильном формате ввода, любой мощности, вы должны вернуть тот же тип вывода.
Ваша функция возвращает три разных:
Я бы выбрал исключение, если список не распознан (у вас уже есть вход) или возвращает объект с отображением во всех остальных случаях
var x = [
[ 'uk', 'london', 'british museum' ],
[ 'uk', 'london', 'tate modern' ],
[ 'uk', 'cambridge', 'fitzwilliam museum' ],
[ 'russia', 'moscow', 'tretyakovskaya gallery' ],
[ 'russia', 'st. petersburg', 'hermitage' ],
[ 'russia', 'st. petersburg', 'winter palace' ],
[ 'russia', 'st. petersburg', 'russian museum' ]
];
/*
* function to add a single point of interest at the
* right place in the map
*
*/
function remap(map, location) {
var state, city, poi;
[state, city] = location;
poi = location.slice(2);
if (!map[state]) {
// create a new state property
map[state]={};
}
if (!map[state][city]) {
// first time we encounter a city: create its slot
map[state][city]=[];
}
// Add the points of interest not in the list
map[state][city].concat(
poi.filter(function(p) {
return !map[state][city].includes(p);
})
);
return map;
}
function recTree(arr) {
// We only take in arrays of arrays
// if not, better to throw an exception!
if (arr.constructor !== Array || arr[0].constructor !== Array) {
throw "recTree: invalid parameter, we need an array of array";
}
return x.reduce(remap, {});
}
если вам действительно нужна версия llodash, вы можете начать с этого
function recTree(arr) {
// We only take in arrays of arrays
// if not, better to throw an exception!
if (arr.constructor !== Array || arr[0].constructor !== Array) {
throw "recTree: invalid parameter, we need an array of array";
}
return _.reduce(x, remap, {});
}
в обоих случаях вы можете получить свои данные (приложение try catch не является обязательным) с:
try {
// if you really trust your code use just the following line
var mymap = recTree(arr);
} catch(e) {
// do anything you can from recover for the error or rethrow to alert
// developers that something was wrong with your input
}