Last active
April 18, 2020 01:32
-
-
Save mrboli/f720644718ccf3d4697ca72f77bc362d to your computer and use it in GitHub Desktop.
Number of Islands [Interesting Idea - WIP] Update all pointers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let toggleLog = false; | |
toggleLog = true; | |
let log = false; | |
// log = true; | |
const parents = {}; | |
let numKeys = 0; | |
class UnionSet { | |
// End result: At each cell, check adjacent, if it's visited, set parent to it's parent | |
constructor (grid) { | |
this.nodes = {}; | |
// this.tree = {}; | |
this.grid = grid; | |
this.rows = grid.length; | |
this.cols = grid[0].length; | |
this.islands = 0; | |
grid.forEach((gridRow, row) => { | |
gridRow.forEach((elem, col) => { | |
// Only "add" if it is a valid element | |
if (toggleLog && row === 3 && col === 1) log = true; | |
if (log && elem === "1") console.log('-', row, ',', col); | |
if (elem === "1") this.add(row, col); | |
// if (elem === "1") console.log('after adding', JSON.stringify(this.nodes)) | |
}); | |
}); | |
} | |
get numIslands () { | |
return this.islands; | |
} | |
get length () { | |
return Object.keys(this.nodes).length; | |
} | |
add (row, col) { | |
const key = this.key(row, col); | |
const node = {}; | |
this.nodes[key] = node; | |
this.islands++; // Increment this by default, decrement when joining | |
// TODO [optimize]: Generalize this with below | |
// Initialize the parent pointer scheme on first node | |
// Don't check adjacents | |
if (this.length === 1) { | |
// TODO modify {node : key} to just key, will not have to change it explicitly | |
const parent = { pointer: { node: key } }; // Initially, it should point to itself | |
node.parent = parent; | |
parents[key] = true; | |
numKeys++; | |
return true; | |
} | |
const adjRows = [1, 0, -1, 0]; | |
const adjCols = [0, 1, 0, -1]; | |
let joined = false; | |
if (log) this.logParentKeys(); | |
for (let i = 0; i < 4; i++) { | |
const adjRow = adjRows[i] + row; | |
const adjCol = adjCols[i] + col; | |
if (adjRow >= 0 && adjRow < this.rows && adjCol >= 0 && adjCol < this.cols) { | |
const adjKey = this.key(adjRow, adjCol); | |
const adjNode = this.nodes[adjKey]; | |
if (log && adjNode) console.log(' ! adjExists', adjKey, adjNode.parent); | |
if (adjNode) joined = this.join(adjNode, node); | |
if (joined) { | |
joined = false; | |
numKeys--; | |
this.logParentKeys(); | |
console.log('after removing keys', numKeys); | |
console.log('node', node, key); | |
console.log('adjNode', adjNode, adjKey); | |
if (log) this.logParentKeys(); | |
} | |
if (log && adjNode) console.log(' == join result', node.parent, adjNode.parent); | |
// No adjNode means it hasn't been visited yet, or was 0 | |
} | |
} | |
// If there were no joins | |
if (!node.parent) { | |
const parent = { pointer: { node: key } }; // Initially, it should point to itself | |
parent[key] = true; | |
node.parent = parent; | |
numKeys++; | |
parents[key] = true; | |
return true; | |
} | |
} | |
logParentKeys () { | |
console.log('logging parent keys'); | |
const keys = Object.values(this.nodes).map(node => { | |
return node.parent && node.parent.pointer.node || null; | |
}).filter(val => val != null); | |
console.log((new Set(keys)).size, new Set(keys)); | |
} | |
// lNode will always exist, since this function isn't entered without it | |
// rNode will at least be a new node | |
join (lNode, rNode) { | |
if (log) console.log('joining', lNode); | |
if (log) console.log(' + with', rNode); | |
// Assume left node will always have a pointer since it's previously visited | |
// Set all child parentPointers to the same one as the parent | |
// If it's a new node, just attach to adjacent | |
if (!rNode.parent) { | |
// If we did this for non-new nodes, all the children nodes' | |
// parents wouldn't get updated, so we can only do this for new ones | |
// Re-assigning the parent, would detatch the refernece. | |
rNode.parent = lNode.parent; | |
if (log) console.log(' -- new, numislands decreased', this.islands); | |
this.islands--; // Joining, so decrease islands | |
return false; | |
} | |
// If they share the same parent, do nothing | |
if (log && rNode.parent.pointer.node === lNode.parent.pointer.node) console.log(' << already share same parent'); | |
if (rNode.parent.pointer === lNode.parent.pointer) return false; | |
if (log) console.log(' -- merging', this.islands); | |
console.log('removing key', rNode.parent.pointer); | |
// - Always attach right to left | |
// - Update the parent pointer of right to point to same node | |
// - The reason we don't assign parent directly because the parent | |
// is a reference to an object, which is shared by it's children. | |
// - If we update the pointer of that parent object, since all the | |
// children share the same object, the pointer will now be equal | |
// for all children when we modify the parent's parent pointer. | |
rNode.parent.pointer = lNode.parent.pointer; | |
this.islands--; // Joining, since pointers are different | |
return true; | |
} | |
key (row, col) { | |
return `${row}-${col}`; | |
} | |
} | |
var numIslands = function(grid) { | |
if (grid.length === 0 || grid[0].length === 0) return 0; | |
let unionSet = new UnionSet(grid); | |
return unionSet.numIslands; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DJS { | |
constructor (grid) { | |
this.rows = grid.length; | |
this.cols = grid[0].length; | |
this.values = []; | |
this.count = 0; | |
this.parents = []; | |
this.ranks = []; | |
grid.forEach((rowVal, row) => { | |
this.values.push([]); | |
rowVal.forEach((value, col) => { | |
let element = { value, rank: 0, parent: undefined }; | |
if (value === '1') { | |
element.parent = element; | |
this.count++; | |
} | |
this.values[row].push(element) | |
}); | |
}); | |
} | |
find (element) { | |
if (element.parent !== element) | |
element.parent = this.find(element.parent); | |
return element.parent; // do this if it's a "leader", aka it's own parent | |
} | |
union (firstNode, secondNode) { | |
// Assume non flattened values | |
let first = this.find(firstNode); | |
let second = this.find(secondNode); | |
// This return check is purely for keeping track of count to reduce another loop at the end | |
if (first === second) return; | |
this.count--; | |
if (first.rank < second.rank) { | |
first.parent = second; | |
} else if (first.rank > second.rank) { | |
second.parent = first; | |
} else { | |
first.parent = second; | |
second.rank++; | |
} | |
} | |
isValidIndex (row, col) { | |
if (row < 0 || col < 0 || row >= this.rows || col >= this.cols) return undefined; | |
return this.values[row][col]; | |
} | |
findIslands () { | |
this.values.forEach((rowVal, row) => { | |
rowVal.forEach((element, col) => { | |
// can "optimize by keeping visited" | |
if (element.value === '1') { | |
let up = this.isValidIndex(row - 1, col); | |
if (up && up.value === '1') this.union(element, up); | |
let down = this.isValidIndex(row + 1, col); | |
if (down && down.value === '1') this.union(element, down); | |
let left = this.isValidIndex(row, col -1); | |
if (left && left.value === '1') this.union(element, left); | |
let right = this.isValidIndex(row, col + 1); | |
if (right && right.value === '1') this.union(element, right); | |
} | |
}) | |
}); | |
return this.count; | |
} | |
} | |
/** | |
* @param {character[][]} grid | |
* @return {number} | |
*/ | |
var numIslands = function(grid) { | |
if (!grid || !grid[0]) return []; | |
let djs = new DJS(grid); | |
return djs.findIslands(); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UnionSet { | |
// End result: At each cell, check adjacent, if it's visited, set parent to it's parent | |
constructor (grid) { | |
this.nodes = {}; | |
this.grid = grid; | |
this.rows = grid.length; | |
this.cols = grid[0].length; | |
this.islands = 0; | |
grid.forEach((gridRow, row) => { | |
gridRow.forEach((elem, col) => { | |
// Only "add" if it is a valid element | |
if (elem === "1") this.add(row, col); | |
}); | |
}); | |
} | |
get numIslands () { | |
return this.islands; | |
} | |
add (row, col) { | |
const key = this.key(row, col); | |
const node = {}; | |
node.parent = node; | |
this.nodes[key] = node; | |
this.islands++; // Increment this by default, decrement when joining | |
const adjRows = [1, 0, -1, 0]; | |
const adjCols = [0, 1, 0, -1]; | |
for (let i = 0; i < 4; i++) { | |
const adjRow = adjRows[i] + row; | |
const adjCol = adjCols[i] + col; | |
if (adjRow >= 0 && adjRow < this.rows && adjCol >= 0 && adjCol < this.cols) { | |
const adjKey = this.key(adjRow, adjCol); | |
const adjNode = this.nodes[adjKey]; | |
if (adjNode) this.join(adjNode, node); | |
} | |
} | |
} | |
parent (node) { | |
const isRoot = node.parent === node; | |
if (isRoot) return node; | |
node.parent = this.parent(node.parent); | |
return node.parent; | |
} | |
join (fNode, rNode) { | |
let lParent = this.parent(fNode); | |
let rParent = this.parent(rNode); | |
if (lParent === rParent) return null; | |
rParent.parent = lParent; | |
this.islands--; | |
} | |
key (row, col) { | |
return `${row}-${col}`; | |
} | |
} | |
var numIslands = function(grid) { | |
if (grid.length === 0 || grid[0].length === 0) return 0; | |
let unionSet = new UnionSet(grid); | |
return unionSet.numIslands; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment