首页 > 解决方案 > Is this the most efficient use of ES6 to find factors without a loop?

问题描述

I am trying to find the least verbose way to find the factors for each number in an array without using loops. I have a snippet of ES6 code that I could use in a .map to avoid a loop I think, but I'm at a loss as to what it is doing in the second line.

I've looked at the .filter and .from methods on MDN, so we're shallow copying an array instance from an iterable, seemingly empty by just calling Array(), but then I'm at a loss in describing it in English after that, which makes me feel uneasy.

let evens = [2,4,6,80,24,36];

Here's the ES6 snippet I'm trying to deconstruct/explain in English

const factor = number => Array
    .from(Array(number), (_, i) => i)
    .filter(i => number % i === 0)

so I dropped it into this .map like so

const factors = evens.map((number => {
    return factors(number)
}))

console.log(factors)

I get an array of arrays of the factors as shown here

[ [ 1 ],
  [ 1, 2 ],
  [ 1, 2, 3 ],
  [ 1, 2, 4, 5, 8, 10, 16, 20, 40 ],
  [ 1, 2, 3, 4, 6, 8, 12 ],
  [ 1, 2, 3, 4, 6, 9, 12, 18 ] ]

So...it works, but what is happening in that second line? I love that it is succinct, but when I try to reverse engineer it into non-ES6 I'm left wondering.

Thank you in advance, advanced ES6 folks.

标签: javascriptarraysecmascript-6factors

解决方案


There are a number of things to unpack here.

First of all, "without using loops." Can you explain your reason for that requirement? Not that I'm unsympathetic, as I usually avoid explicit loops, but you should really be able to explain why you want to do this. There are two fundamentally different ways to process an ordered collection: iterative looping and recursion. If you're not using recursive code, there's probably a loop hiding somewhere. It might be buried inside a map, filter, etc., which is most often an improvement, but that loop is still there.

Second, the layout of this snippet is fairly misleading:

const factor = number => Array
    .from(Array(number), (_, i) => i)
    .filter(i => number % i === 0)

Usually when a number of lines start .methodName(...) each of these methods operates on the data supplied by the previous line. But from here is just a static method of Array; separating them like this is confusing. Either of these would be better, as would many other layouts:

const factor = number => 
    Array.from(Array(number), (_, i) => i)
    .filter(i => number % i === 0)
const factor = number => Array.from(
    Array(number), 
    (_, i) => i
).filter(i => number % i === 0)

Third, as comments and another answer have pointed out, Array.from accepts an iterable and a mapping function and returns an array, and Array(number) will give you an array with no values but which reports its length as number, so will serve as an appropriate iterable. There are a number of equivalent ways one might write this, for instance:

Array.from({length: number}, (_, i) => i)
[...Array(number)].map((_, i) => i)

Fourth, you mention this:

const factors = evens.map((number => {
    return factor(number)
}))

(typo fixed)

While there's nothing exactly wrong with this, you might want to recognize that this can be written much more cleanly as

const factors = evens.map(factor)

Finally, that factoring code is missing a major performance tweak. You test every possible value up to n, when you really can find factors in pairs, testing only up to sqrt(n). That is a major difference. There is no known efficient factoring technique, which is probably a good thing as modern encryption depends upon this being a difficult problem. But you most likely don't want to make it much worse than it has to be.


推荐阅读