Generate cuboids with integer sides and diagonals: how to improve the time complexity?

99 views Asked by At

I quickly wrote this code to test when all sides and diagonals of a cuboid a,b,c,d,e and f are all integers. It seems to work just fine but it takes way too much time for higher and higher values to loop through (for ex 10000+).

Is there any way to make this code run faster ? or maybe someway to get rid of that nasty three nested for loops ?

I thought of a few ways to reduce the complexity of this code but it doesn't go through all combinations which is something I want to happen.

const generate_good_cuboid = () => {
  let good_couples = [];
  let couples = [];
  for (let a = 1; a < 10000; a++) {
    for (let b = 1; b < 10000; b++) {
      for (let c = 1; c < 10000; c++) {
        let e_squared = Math.pow(a, 2) + Math.pow(c, 2);
        let e = Math.sqrt(e_squared);
        if (Number.isInteger(e)) {
          let d_squared = Math.pow(a, 2) + Math.pow(b, 2);
          let d = Math.sqrt(d_squared);
          if (Number.isInteger(d)) {
            let f_squared = Math.pow(b, 2) + Math.pow(c, 2);
            let f = Math.sqrt(f_squared);
            if (Number.isInteger(f)) {
              good_couples.push({ a, b, c, d, e, f });
            }
          }
        }
      }
    }
  }
  return couples;
};


console.log(generate_good_cuboid());

2

There are 2 answers

0
Bergi On

A trivial optimisation would be to compute d (from a and b) and check for its integer-ness before looping over all c.

Further, you could avoid generating duplicates such as a=3, b=4 and a=4, b=3 by limiting your results to a < b < c, and permute your solutions if you need all of them. Then you can optimise for (let b = a; b < 10000; b++) and for (let c = b; c < 10000; c++) (although this doesn't really change complexity).

Last but not least, you could generate all known-good sets of Pythagorean triples up-front (only once), and then just search through those to find two triples (triangles) that share one side, and check whether the other two legs (catheti) form a Pythogorean triple as well. Use any of the known algorithms (generating Pythagorean triples, tree of primitive Pythagorean triples, Efficiently find all the Pythagorean triplets, best way to generate Pythagorean triples etc.) for the first part.

2
trincot On

Here is a solution that uses the Tree of primitive Pythagorian triples to generate all Pythagorian triples whose greatest element is less than your limit.

Then it creates a graph (adjacency list) where one integer has a link with another if they are the lesser two members of a Pythagorian triple.

Finally it checks if these can be combined to the desired combinations of a, b, and c:

function* pythagorianTriples(limit) {
    const ABC = [
        [[1, -2, 2], [2, -1, 2], [2, -2, 3]],
        [[1,  2, 2], [2,  1, 2], [2,  2, 3]],
        [[-1, 2, 2], [-2, 1, 2], [-2, 2, 3]]
    ]
    let triples = [[3, 4, 5]];
    while (triples.length) {
        const children = [];
        for (const triple of triples) {
            const sorted = triple.toSorted((a, b) => a - b);
            if (sorted[1] >= limit) continue; // The limit applies to a, b, not c
            // Yield multiples of this primitive pythagorian triple
            for (let i = 1; sorted[1] * i < limit; i++) {
                yield [sorted[0] * i, sorted[1] * i, sorted[2] * i];
            }
            // Get the triple's three children in the tree of Pythagorian triples:
            children.push(...ABC.map(mat =>
                mat.map(row => 
                    row[0] * triple[0] + row[1] * triple[1] + row[2] * triple[2]
                )
            ));
        }
        triples = children;
    }
}

function generateGoodCuboids(limit) {
    const pairSide = Array.from({length: limit}, (_, i) => new Set);
    const triples = [...pythagorianTriples(limit)];
    // Link pairs that belong to same Pythagorian triple; for fast lookup
    for (const [a, b] of triples) {
        pairSide[a].add(b);
        pairSide[b].add(a);
    }
    // Find valid combinations
    const solutions = [];
    for (const [a, b] of triples) {
        for (const c of pairSide[a]) {
            if (c >= b && pairSide[b].has(c)) solutions.push([a, b, c]);
        }
    }
    // For convenience, sort them:
    return solutions.sort(([a], [b]) => a - b);
}

const cuboids = generateGoodCuboids(1000);
console.log(`There are ${cuboids.length} solutions for sides < 1000:`);
for (const cuboid of cuboids) console.log(...cuboid);

// For 10 times more:
const cuboids2 = generateGoodCuboids(10000);
console.log(`There are ${cuboids2.length} solutions for sides < 10000`);

// For 10 times more:
const cuboids3 = generateGoodCuboids(100000);
console.log(`There are ${cuboids3.length} solutions for sides < 100000`);

The script finds 10 solutions (ignoring permutations) when the limit is 1000; it repeats the job for 10000 (151 solutions) and 100000 (1714 solutions).