I am doing DIY renovations on my house. I am currently rebuilding the overhang and have reached the point where I need to figure out where I want the spotlights. I have a few requirements:

  • The spots must be spaced out evenly.
  • The spots cannot be placed at the position of a beam as I don't want to carve out the wood that carries the roof.
  • I want around 2 meters between the spots.
  • I am open on a difference between distance between the first spot and the edge of the roof and the last spot and its corresponding edge.

How do we solve this? I know many people who would sit and puzzle together a solution, but that's not where I have the most patience.

So I went outside and measured all distances between the beams and wrote a program.

const beamWidth = 11.5;

const minDistance = 120.0;
const maxDistance = 350.0;

const lampDiameter = 7.4;

const beamDistances = [ 40.0, 73.0, 87.0, 80.0, 80.0, 82.0, 87.0, 87.0, 82.0, 82.0, 64.0, 61.0, 69.0, 82.0, 85.0, 38.0 ];

const totalDistance = beamDistances.reduce(
  (prev, curr) => prev + curr,
  beamWidth * (beamDistances.length - 1)
);

const isBetweenBeams = offset => {
  let beamStart = 0;
  for (let beamId = 0; beamId < beamDistances.length; beamId++) {
    const beamEnd = beamStart + beamDistances[beamId];
    if (beamStart <= offset && offset <= beamEnd - lampDiameter) {
      return true;
    }
    beamStart = beamEnd + beamWidth + beamDistances[beamId];
  }
  return false;
};

const solutions = [];

for (let startOffset = 0; startOffset < minDistance; startOffset += 0.5) {
  for (let distance = minDistance; distance < maxDistance; distance += 0.5) {
    // Iterate pos from startOffset to totalDistance with distance increments
    succ = true;
    const currTry = [];
    for (
      let pos = startOffset;
      pos <= totalDistance - lampDiameter;
      pos += distance
    ) {
      // We add half a diameter so we get the center of the hole and not the edge
      currTry.push(pos + lampDiameter / 2.0);
      // Check if it is between joists
      if (!isBetweenBeams(pos)) {
        succ = false;
        break;
      }
    }
    if (succ) {
      const numberOfLamps = currTry.length;
      solutions.push({
        distance,
        startOffset,
        endOffset:
          totalDistance -
          (startOffset + (numberOfLamps - 1) * distance) -
          lampDiameter,
        numberOfLamps,
        lamps: currTry
      });
    }
  }
}

// We sort by the solutions where start and end offsets are most similar
solutions.sort(
  (a, b) =>
    Math.abs(a.startOffset - a.endOffset) -
    Math.abs(b.startOffset - b.endOffset)
);
for (let i = 0; i < 25; i++) {
  console.log(JSON.stringify(solutions[i]));
}

It creates all viable solutions with even spacing and then sorts the results by the most even first lamp to edge distance and last lamp to edge distance.

These are the first three solutions I got:

{"distance":182.5,"startOffset":119.5,"endOffset":129.6,"numberOfLamps":7,"lamps":[123.2,305.7,488.2,670.7,853.2,1035.7,1218.2]}
{"distance":183,"startOffset":117.5,"endOffset":128.6,"numberOfLamps":7,"lamps":[121.2,304.2,487.2,670.2,853.2,1036.2,1219.2]}
{"distance":182.5,"startOffset":119,"endOffset":130.1,"numberOfLamps":7,"lamps":[122.7,305.2,487.7,670.2,852.7,1035.2,1217.7]}

Looking at the first solution, it looks like I will have 7 lamps, one every 182.5cm and the edge distances are 119.5 and 129.6. I am gonna use that solution.

That's how programmers build houses.