Skip to content

Instantly share code, notes, and snippets.

@WoolDoughnut310
Created July 24, 2022 15:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WoolDoughnut310/610561a96033449e28dd54323965ece0 to your computer and use it in GitHub Desktop.
Save WoolDoughnut310/610561a96033449e28dd54323965ece0 to your computer and use it in GitHub Desktop.
const balanceEquation = (
reactants,
products
) => {
let balancingNumbers = [
new Array(reactants.length).fill(1),
new Array(products.length).fill(1),
];
let reactantAtoms = {};
let productAtoms = {};
let reactantElements = reactants.map(Object.keys);
let productElements = products.map(Object.keys);
let elements = new Set();
let reactantElementCount = {};
let productElementCount = {};
let uniqueElements = [];
const balanceCompound = (i, side, element) => {
const compound = (side === 0 ? reactants : products)[i];
const sideAtoms = side === 0 ? reactantAtoms : productAtoms;
const otherAtoms = side === 0 ? productAtoms : reactantAtoms;
let oldBalancingNumber = balancingNumbers[side][i];
let balancingNumber = oldBalancingNumber;
// The amount of atoms on the side, excluding from the current compound
const existingAtoms =
sideAtoms[element] - balancingNumber * compound[element];
balancingNumber =
(otherAtoms[element] - existingAtoms) / compound[element];
sideAtoms[element] =
existingAtoms + balancingNumber * compound[element];
// Update the amount of atoms for every other element in the compound
for (let otherElement of Object.keys(compound)) {
if (otherElement === element) continue;
const otherExistingAtoms =
sideAtoms[otherElement] -
oldBalancingNumber * compound[otherElement];
sideAtoms[otherElement] =
otherExistingAtoms + balancingNumber * compound[otherElement];
}
balancingNumbers[side][i] = balancingNumber;
// If balancing number is a decimal number
if ((parseFloat(balancingNumber) | 0) !== balancingNumber) {
// Scale to a whole number
const scale = lcm(balancingNumber, 1) / balancingNumber;
// i goes from 0 to 1, for reactants to products
for (let i = 0; i < balancingNumbers.length; i++) {
const sideBalancingNumbers = balancingNumbers[i];
// j goes up to the number of compounds from each side
for (let j = 0; j < sideBalancingNumbers.length; j++) {
balancingNumbers[i][j] *= scale;
}
}
// Can't forget to update the number of atoms
elements.forEach((element) => {
reactantAtoms[element] *= scale;
productAtoms[element] *= scale;
});
}
// Update the balancing numbers on any compounds that
// would have been affected because they contained a unique element
if (uniqueElements.length > 0) {
for (let key of Object.keys(compound)) {
if (element === key) continue;
if (!uniqueElements.includes(key)) return;
const otherElements =
side === 0 ? productElements : reactantElements;
for (let i = 0; i < otherElements.length; i++) {
const compoundElement = otherElements[i];
if (!compoundElement.includes(key)) continue;
// if side is 0 (bool false), balance on side 1 (products), 0 otherwise
balanceCompound(i, !side ? 1 : 0, key);
}
}
}
};
// Couting the amount of atoms of an element on each side
for (let reactant of reactants) {
for (let [element, value] of Object.entries(reactant)) {
elements.add(element);
if (!reactantAtoms[element]) reactantAtoms[element] = 0;
reactantAtoms[element] += value;
}
}
for (let product of products) {
for (let [element, value] of Object.entries(product)) {
if (!productAtoms[element]) productAtoms[element] = 0;
productAtoms[element] += value;
}
}
for (let compoundElements of reactantElements) {
for (let element of compoundElements) {
if (!reactantElementCount[element])
reactantElementCount[element] = 0;
reactantElementCount[element]++;
}
}
for (let compoundElements of productElements) {
for (let element of compoundElements) {
if (!productElementCount[element]) productElementCount[element] = 0;
productElementCount[element]++;
}
}
for (let i = 0; i < reactantElements.length; i++) {
const reactantCompoundElements = reactantElements[i];
for (let element of reactantCompoundElements) {
if (
!(
reactantElementCount[element] === 1 &&
productElementCount[element] === 1
)
)
continue;
uniqueElements.push(element);
for (let j = 0; j < productElements.length; j++) {
const productCompoundElements = productElements[j];
if (!productCompoundElements.includes(element)) continue;
/* Balance the compound on the side with less
atoms and a lower existing coefficient */
if (
reactants[i][element] <= products[j][element] &&
balancingNumbers[0][i] <= balancingNumbers[1][j]
) {
// We'll look at this function later
balanceCompound(i, 0, element);
} else {
balanceCompound(j, 1, element);
}
}
}
}
elements.forEach((element) => {
if (uniqueElements.includes(element)) return;
let smallestCandidate = {
side: 0,
balancingNumber: Infinity,
value: Infinity,
numElements: Infinity,
index: 0,
};
// Find the compound with the smallest number of elements
for (let i = 0; i < reactants.length; i++) {
const reactant = reactants[i];
const balancingNumber = balancingNumbers[0][i];
const numElements = reactantElements[i].length;
if (!reactantElements[i].includes(element)) continue;
if (numElements >= smallestCandidate.numElements) continue;
smallestCandidate = {
side: 0,
balancingNumber,
value: reactant[element],
numElements,
index: i,
};
}
for (let i = 0; i < products.length; i++) {
const product = products[i];
const balancingNumber = balancingNumbers[1][i];
const numElements = productElements[i].length;
if (!productElements[i].includes(element)) continue;
if (numElements >= smallestCandidate.numElements) continue;
smallestCandidate = {
side: 1,
balancingNumber,
value: product[element],
numElements,
index: i,
};
}
balanceCompound(
smallestCandidate.index,
smallestCandidate.side,
element
);
});
return balancingNumbers;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment