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

时间:2017-06-29 12:13:08

标签: javascript arrays lodash

我正在使用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' ] ]

成为这种哈希/树结构:

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

我想知道是否有比我迄今为止编程的更清洁,更实用的方法。理想情况下,我希望能够接受任意(但是常量,所有内部数组都相同)长度(不仅仅是3)数组的数组。

4 个答案:

答案 0 :(得分:3)

这是一个适用于任何长度可变数组的lodash解决方案。

  1. 使用lodash#reduce将数组缩减为object格式。

  2. 在每次reduce迭代中:

    2.1。我们得到了我们想要设置的值的path,例如uk.london,使用lodash#initial

    2.2。使用lodash#last,从我们想要连接的内部数组中获取值。

    2.3使用lodash#get使用objectpath获取任何现有数组,如果它没有获得任何值,则默认为空数组。获取值后,我们将内部数组的最后一项连接到获得的值。

    2.4使用lodash#set使用取自2.1的objectvalue中取得path的结果var result = _.reduce(array, function(object, item) { var path = _.initial(item); var value = _.get(object, path, []).concat(_.last(item)); return _.set(object, path, value); }, {});

  3. 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>
    {{1}}

答案 1 :(得分:2)

您可以使用Array#reduce创建/访问嵌套数据结构并推送数组的最后一个元素。

编辑:此解决方案适用于任意长度的内部数组。

&#13;
&#13;
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);
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
&#13;
&#13;

ES6

&#13;
&#13;
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);
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
&#13;
&#13;

答案 2 :(得分: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);

答案 3 :(得分:-1)

分组和映射可能不是解决问题的更有效方法。

一种更好的方法,因为您只需一次详细说明数据,就应该在阅读清单时创建地图。

如果不满足先决条件,代码更容易理解,我会尽快摆脱这个功能。

我不会考虑的另一个问题是返回第一个元素 如果列表的长度只有一个,则为输入数组:如果给定正确的输入格式,任何基数,则应返回相同的输出类型。

你的函数返回三个不同的函数:

  • 任何事情(如果列表未被识别)
  • 一个数组(如果输入列表没问题,但只包含一个元素)
  • 具有其他情况下所需映射的对象

如果列表未被识别(您已经有输入)或者在所有其他情况下返回带有映射的对象,我会抛出异常

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

如果你真的需要一个_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, {});
}

在这两种情况下,您都可以使用以下方法获取数据(try catch enclosure是可选的):

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上的参考文献