i have written an algorithm that finds each line of a hexagon in a huge object-array structure.
the array consits of about 80.000 - 100.000 elements (line coordinates from start to end).
A hexagon cosists of 6 lines points. So the array has the information of about 15.000 hexagons.
The structure of the object (UNSORTED!!!) looks like this:
const stamps = [
  { 
    vertices: [
      {x: 114.5116411118, y: 342.9815785601},
      {x: 115.6663416502, y: 344.9815785601}
    ]
  },
  {
    vertices: [
      {x: 115.6663416502, y: 340.9815785601},
      {x: 114.5116411118, y: 342.9815785601}
    ]
  },
  {
    vertices: [
      {x: 122.6663416502, y: 364.9815785601},
      {x: 147.9757427269, y: 314.9815785601},
    ]
  },
  {
    vertices: [
      {x: 117.9757427269, y: 340.9815785601},
      {x: 115.6663416502, y: 340.9815785601},
    ]
  },
  {
    vertices: [
      {x: 119.1304432653, y: 342.9815785601},
      {x: 117.9757427269, y: 340.9815785601},
    ]
  },
  {
    vertices: [
      {x: 117.9757427269, y: 344.9815785601},
      {x: 119.1304432653, y: 342.9815785601},
    ]
  },
  {
    vertices: [
      {x: 115.6663416502, y: 344.9815785601},
      {x: 117.9757427269, y: 344.9815785601},
    ]
  },
];
To find each line hexagon, my idea was that there has to be 2 elements that have to same coordinate. If this is the case, i'm jumping to the index of this element and repeat that process untill i have all 6 lines of the hexagon.
It works like this, but its really, really slow. For an array with 80.000 elements its about 3 minutes.
The algorithm:
function findHexPolyPoints() {
  const hexCoordinates = [];
  let activeArrayPos = 0;
  let position = 0;
  while (1) {
    let foundPair = false;
    if (stamps.length < 6) break;
    for (let k = 0; k < stamps.length; ++k) {
      if (k === position) continue;
      if (stamps[position].vertices[0].x === stamps[k].vertices[1].x && stamps[position].vertices[0].y === stamps[k].vertices[1].y) {
        if (hexCoordinates[activeArrayPos]) {
          hexCoordinates[activeArrayPos].push(stamps[k].vertices[0].x, stamps[k].vertices[0].y);
        } else {
          hexCoordinates.push([stamps[position].vertices[0].x, stamps[position].vertices[0].y, stamps[k].vertices[0].x, stamps[k].vertices[0].y]);
        }
        foundPair = true;
      } else if (stamps[position].vertices[1].x === stamps[k].vertices[0].x && stamps[position].vertices[1].y === stamps[k].vertices[0].y) {
        if (hexCoordinates[activeArrayPos]) {
          hexCoordinates[activeArrayPos].push(stamps[k].vertices[1].x, stamps[k].vertices[1].y);
        } else {
          hexCoordinates.push([stamps[position].vertices[1].x, stamps[position].vertices[1].y, stamps[k].vertices[1].x, stamps[k].vertices[1].y]);
        }
        foundPair = true;
      }
      if (foundPair) {
        stamps.splice(position, 1);
        if (k > position) {
          position = k - 1;
        } else {
          position = k;
        }
        if (hexCoordinates[activeArrayPos].length < 12) break;
      }
      if (hexCoordinates[activeArrayPos] && hexCoordinates[activeArrayPos].length === 12) {
        if (k > position) stamps.splice(k - 1, 1);
        else stamps.splice(k, 1);
        activeArrayPos += 1;
        position = 0;
        break;
      }
      if (k === stamps.length - 1) {
        stamps.splice(position, 1);
        break;
      }
    }
  }
  sortHexagons(hexCoordinates);
}
Is there any way to speed up my algorithm? I have read that a simple for loop is still faster that some js sort functions like .map .filter or similar.
The following O(n) algorithm assumes
=== exactly equal)(if the points 1. and 2. are not true, the algo would need to be worked more, to try all possibilities (in overt[x_y] array, see below) in order to avoid non-hexagon or overlapping coords, depending on the expectancy to find overlapping hexagons, or orphans, the complexity could go beyond O(n))
Using the concept of map (get an object value from a key), which is considered O(1).
In order to conveniently use the vertices coordinates, we can concatenate x and y into one string
x:123.456, y:789.321
gives
x_y = "123.456_789.321"
Let's create 3 variables avert = [], overt = {}, hexas = []
avert is an array of all vertices, avert[index] is an array(2) of the x_y vertices coordinatesovert is an object that, for each x_y coordinates gives an array of indexes in avert (the size should not become more than 2, as assumed above (and there is no >2 check))hexas is an array of array(6), the list of hexagons found (each coordinate is formatted x_y)In the first forEach, avert and overt are created.
The next forEach processes all avert vertices [x_y1, x_y2]
hexa, starting from the next (after the first)origin (first)Initialization
let avert = [], overt = {}, hexas = [];
stamps.forEach(function(e, i){
    let xy1 = e['vertices'][0]['x']+'_'+e['vertices'][0]['y'];
    let xy2 = e['vertices'][1]['x']+'_'+e['vertices'][1]['y'];
    // overt[XY] (array) should have two elements at most (no overlapping),
    // one if orphan
    if ( ! overt[xy1]) overt[xy1] = [];
    overt[xy1].push( i );
    if ( ! overt[xy2]) overt[xy2] = [];
    overt[xy2].push( i );
    avert.push([ xy1, xy2 ]);
});
Processing
avert.forEach(function (e){
    let j,coord = e[0];   // first coords x_y
    let origin = coord;
    let hexa = [];
    let lastindex = -1;   // avoid going back!
    // try to find 5 connected vertices + origin
    for(j=0 ; j<6 ; j++) {
        let o = overt[coord];
        if ( o === undefined || o.length < 2 ) {
           break; // not found(already processed), or orphan!
        }
        let index = o[0] == lastindex ? o[1] : o[0];  // no turn back!
        lastindex = index;
        coord = avert[index][0] === coord ? avert[index][1] : avert[index][0];
        hexa.push(coord);
    }
    if (j >= 6) { // found all vertices
        // check that the last coord is the starting point
        if (hexa[5] === origin) { // got it
             hexas.push( hexa );
             hexa.forEach(function(h){ // delete used vertices
                delete overt[h];
             });
        }
    }
});
All hexagons should be in hexas
You can avoid the nested loop over all data by using a hash map. Key the individual vertices with a hash, for instance their JSON representation, and store the corresponding x, y coordinate together with a list of neigbor objects.
Once you have that, it is easy to walk through that graph and identify the hexagons.
Runnable snippet using the sample data you provided:
function findHexPolyPoints(stamps) {
    // Create graph
    let map = new Map;
    for (let {vertices} of stamps) {
        // Get unique identifier for each vertex (its JSON notation)
        let keys = vertices.map(JSON.stringify);
        // Create "nodes" for each vertex, keyed by their key
        let nodes = keys.map(function (key, i) {
            let {x, y} = vertices[i];
            let node = map.get(key);
            if (!node) map.set(key, node = { key, x, y, neighbors: [] });
            return node;
        });
        // Link these two nodes in both directions
        nodes[0].neighbors.push(nodes[1]);
        nodes[1].neighbors.push(nodes[0]);
    }
    
    // Walk through the graph to detect and collect hexagons
    let hexagons = [];
    for (let [key, vertex] of map) {
        let hexagon = [];
        while (vertex && hexagon.push(vertex) < 6) {
            vertex = vertex.neighbors.find(neighbor => !hexagon.includes(neighbor));
        }
        // Remove collected nodes so they don't get visited a second time
        for (let {key} of hexagon) map.delete(key);
        // Verify that they form indeed a hexagon:
        if (vertex && vertex.neighbors.includes(hexagon[0])) {
            // Simplify the hexagon to only coordinates (12 coordinates)
            hexagons.push(hexagon.flatMap(({x, y}) => [x, y]));
        }
    }
    return hexagons;
}
// Demo. Just replace `stamps` with your actual data.
const stamps = [{vertices: [{x: 114.5116411118, y: 342.9815785601},{x: 115.6663416502, y: 344.9815785601}]},{vertices: [{x: 115.6663416502, y: 340.9815785601},{x: 114.5116411118, y: 342.9815785601}]},{vertices: [{x: 122.6663416502, y: 364.9815785601},{x: 147.9757427269, y: 314.9815785601},]},{vertices: [{x: 117.9757427269, y: 340.9815785601},{x: 115.6663416502, y: 340.9815785601},]},{vertices: [{x: 119.1304432653, y: 342.9815785601},{x: 117.9757427269, y: 340.9815785601},]},{vertices: [{x: 117.9757427269, y: 344.9815785601},{x: 119.1304432653, y: 342.9815785601},]},{vertices: [{x: 115.6663416502, y: 344.9815785601},{x: 117.9757427269, y: 344.9815785601},]},];
let hexagons = findHexPolyPoints(stamps);
console.log(hexagons);
It is true that plain old for loops are somewhat faster than .map, .forEach, .reduce, .find and the likes, but here I kept using them, as the main speed up is really coming from using a hash map.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With