简体   繁体   中英

How do I flatten part of a multidimensional array one level only?

I have a complex JSON object. I'm trying to process it to create an array that looks like:

[
    [ "Name", "Spec", "Spec" ],
    [ "Name", "Spec", "Spec" ]
]

This is where I am stuck:

let array = products.map((product) => {
    return [
        product.name,
        product.attributes
            .map((attribute) => (
                attribute.spec
            ))
            .reduce((accumulator, currentValue) => {
                return accumulator.concat(currentValue);
            }, [])
    ];
});

That gives the result:

[
    [ "Name", [ "Spec", "Spec" ] ],
    [ "Name", [ "Spec", "Spec" ] ]
]

Admittedly, I don't entirely understand the reduce method and it's initialValue argument here. I know using that method can flatten the array at the top level use of map , but at the deeper level, seems to do nothing.

I've searched online but have only found answers that involve completely flattening deep arrays. And the flatten() method is not an option due to lack of compatibility.

Can someone please advise on how to flatten the second level only? If possible, I'd like to accomplish this through mutating the array.

You don't need the reducer there - it's only making things unnecessarily complicated. Map the attributes to their spec property, and then use spread:

const array = products.map(({ name, attributes }) => {
  const specs = attributes.map(attribute => attribute.spec);
  return [name, ...specs];
});

1. Why does this fail?

You put your reduce in the wrong place. You're flattening the list of specs, which was already a flat array. You want to flatten the list that has the name and the list of specs. Here is one possibility:

const array = products.map(prod => [
  prod.name,
  prod.attributes.map(attr => attr.spec)
].reduce((acc, curr) => acc.concat(curr), []));

2. What's a better solution?

As CertainPerformance points out, there is a simpler version, which I might write slightly differently as

const array = products.map(({name, attributes}) =>
  [name, ...attributes.map(attr => attr.spec)]
);

3. What if I need to reuse flatten in other places?

Extract it from the first solution as a reusable function. This is not a full replacement for the new Array flatten method, but it might be all you need:

const flatten = arr => arr.reduce((acc, curr) => acc.concat(curr), [])

const array = products.map(prod => flatten([
    prod.name,
    prod.attributes.map(attr => attr.spec)
  ])
)

4. How does that reduce call flatten one level?

We can think of [x, y, z].reduce(fn, initial) as performing these steps

  1. Call fn(initial, x) , yielding value a
  2. Call fn(a, y) , yielding value b
  3. Call fn(b, z) , yielding value c
  4. Since the array is exhausted, return value c

In other words [x, y, z].reduce(fn, initial) returns fn(fn(fn(initial, x), y), z) .

When fn is (acc, val) => acc.concat(val) , then we can think of ['name', ['spec1', 'spec2']].reduce(fn, []) as fn(fn([], 'name'), ['spec1', 'spec2']) , which is the same as ([].concat('name')).concat(['spec1', 'spec2']) , which, of course is ['name', 'spec1', 'spec2'] .

5. Was there anything wrong with my question?

I'm glad you asked. :-)

There was one significant failing. You didn't include any sample data. To help with this problem required one to try to reconstruct your data formats from your code. It would have been easy enough to give a minimal example such as:

const products = [
  {name: 'foo', attributes: [{spec: 'bar'}, {spec: 'baz'}]},
  {name: 'oof', attributes: [{spec: 'rab'}, {spec: 'zab'}]}
]

with a matching expected output:

[
  ["foo", "bar", "baz"], 
  ["oof", "rab", "zab"]
]

6. How about my output structure?

Now that you mention it, this seems a strange structure. You might have good reasons for it, but it is odd.

Arrays generally serve two purposes in Javascript. They are either arbitrary-length lists of elements of the same type or they are fixed length lists, with specific types at each index (aka tuples .)

But your structure combines both of these. They are arbitrary- (at least so it seems) length lists, where the first entry is a name, and subsequent ones are specs. While there might be justification for this, you might want to think about whether this structure is particularly useful.

7. How can I do this without mutating?

If possible, I'd like to accomplish this through mutating the array.

I refuse to take part in such horrors.

Seriously, though, immutable data makes for so much easier coding. Is there any real reason you listed that as a requirement?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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