JS / lodash - преобразование массива массивов в объект

1

Я работаю над 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)

Теги:
arrays
lodash

4 ответа

3
Лучший ответ

Вот решение lodash, которое работает для любых массивов с переменной длиной.

  1. Используйте lodash#reduce чтобы уменьшить массив в его object форме.

  2. В каждой итерации сокращения:

    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>
  • 0
    Ничего себе, это довольно элегантно, и, насколько я тестировал, работает на несколько длин. Спасибо, Рибаллар
2

Вы можете использовать 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; }
  • 0
    Спасибо Нина, работает почти идеально, но я вижу {"foo": {"bar": {"baz": ["baz"]}}, а не {"foo": {"bar": {"baz": [42]}}, по какой-то причине при запуске кода.
  • 0
    @alexvicegrab, спасибо за подсказку, это был неправильный индекс для толкания. a
Показать ещё 1 комментарий
1

Как функциональный, так и без использования 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);
0

Возможно, группировка и сопоставление не являются более эффективным способом решения вашей проблемы.

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

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

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

Ваша функция возвращает три разных:

  • ничего (в случае, если список не распознается)
  • массив (в случае, если входной список одобрен, но содержит только один элемент)
  • объект с сопоставлениями, которые вам нужны в других случаях

Я бы выбрал исключение, если список не распознан (у вас уже есть вход) или возвращает объект с отображением во всех остальных случаях

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
}

Ссылки на MDN

  • 0
    Благодаря Enieki, я забыл упомянуть изначально, что я хотел общее решение, которое также работает, когда внутренние массивы имеют переменную длину (2, 3, 4 и т. Д.)
  • 0
    @alexvicegrab есть ли у вас массив, содержащий только штат и города с 0 или более точками интереса?
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню