简体   繁体   English

JS / lodash-将数组数组转换为对象

[英]JS/lodash - converting an array of arrays to object

I'm working on Node/JS with lodash, and am trying to convert an array of arrays to a hash object such that: 我正在使用lodash处理Node / JS,并尝试将数组数组转换为哈希对象,例如:

[ [ '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' ] ]

becomes this kind of hash/tree structure: 变成这种哈希/树结构:

{ uk: { 
    london: [ 'british museum', 'tate modern' ],
    cambridge: [ 'fitzwilliam museum' ]
    },
russia: {
    moscow: [ 'tretyakovskaya gallery' ],
    'st petersburg': ['hermitage', 'winter palace', 'russian museum']
    }
}

So far I've used this kind of code: 到目前为止,我已经使用了这种代码:

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's not an array of arrays, return it
        return arr;
    }
}

I'm wondering if there is a cleaner, more functional way of doing this than what I've programmed so far. 我想知道是否有比我到目前为止编写的程序更干净,更实用的方法。 Ideally, I'd like to function to be able to accept arrays of arrays of arbitrary (but constant, such that all inner arrays are the same) length (not just 3) 理想情况下,我希望能够接受任意长度(但常量,以便所有内部数组都相同)长度(不只是3)的数组

Here's a lodash solution that works for any arrays with a variable length. 这是一个lodash解决方案,适用于任何长度可变的数组。

  1. Use lodash#reduce to reduce the array into its object form. 使用lodash#reduce将数组简化为object形式。

  2. In each reduce iteration: 在每个reduce迭代中:

    2.1. 2.1。 We get the path of the value that we want to set, eg uk.london , using lodash#initial . 我们使用lodash#initial获得要设置的值的path ,例如uk.london

    2.2. 2.2。 Use lodash#last , to get the value from inner array that we want to concatenate. 使用lodash#last ,从要连接的内部数组中获取值。

    2.3 Use lodash#get to get any existing array from the object using the path , if it doesn't get any value then it defaults to an empty array. 2.3使用lodash#get使用pathobject获取任何现有数组,如果它没有获取任何值,则默认为空数组。 After getting the value, we concatenate the last item of the inner array towards the obtained value. 获得值后,我们将内部数组的最后一项与获得的值连接起来。

    2.4 Use lodash#set to set the resulting object from the value taken from 2.3 using the path taken from 2.1. 2.4使用lodash#set使用从2.1获取的path ,从2.3获取的value设置结果object


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> 

You could use Array#reduce for creating/accessing the nested data structure and push the last element of the array. 您可以使用Array#reduce创建/访问嵌套的数据结构,并推送数组的最后一个元素。

EDIT: This solution works for an arbitrary length of the inner arrays. 编辑:此解决方案适用于内部数组的任意长度。

 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 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; } 

As functional as it gets and without using lodash, in this solution the inner arrays can be of variable length: 在不使用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); 

Maybe grouping and mapping is not the more efficent way to solve your problem. 也许分组和映射不是解决问题的更有效方法。

A better approach, as you elaborate your data just once, should be to create your map while you read your list. 一次详细说明数据时,一种更好的方法是在阅读列表时创建地图。

And I would bail out of the function as soon as possible if prerequisite are not met, the code is easier to understand, to me. 如果不满足先决条件,我会尽快退出该功能,对我来说代码更容易理解。

The other issue I would not get in account is returning the first element of the input array in case the list has length just one: given a correct input format, any cardinality, you should return the same output type. 我不会考虑的另一个问题是,如果列表的长度只有一个,则返回输入数组的第一个元素:给定正确的输入格式,任何基数,都应返回相同的输出类型。

Your function return three different ones: 您的函数返回三个不同的函数:

  • anything (in case the list is not recognized) 任何内容(如果无法识别列表)
  • an array (in case the input list is ok but contains just one element) 一个数组(如果输入列表可以,但仅包含一个元素)
  • an object with the mappings you need in other cases 具有其他情况下所需映射的对象

I would throw an exception in case the list is not recognized (you already have the input) or return an object with the mappings in all other cases 如果列表无法识别(您已经有输入),或者在所有其他情况下返回带有映射的对象,我将抛出异常

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, {});
}

if you really need a _lodash version you can start from this 如果您确实需要_lodash版本,则可以从此处开始

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, {});
}

in both cases you can obtain your data (the try catch enclosure is optional) with: 在这两种情况下,您都可以通过以下方式获取数据(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
}

References on MDN 关于MDN的参考

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM