-
Notifications
You must be signed in to change notification settings - Fork 0
/
ChemicalEquationBalancer4.java
360 lines (344 loc) · 16.8 KB
/
ChemicalEquationBalancer4.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package com.saptakdas.chemistry.chemicalequation.version4;
import com.saptakdas.misc.MatrixCalculator.Fraction;
import com.saptakdas.misc.MatrixCalculator.Matrix;
import com.saptakdas.misc.MatrixCalculator.MatrixError;
import java.util.*;
/**
* This OOP program can be used to solve almost any chemical equation using an improved full matrix/algebraic method.
* @author Saptak Das
*/
public class ChemicalEquationBalancer4 {
private static final Scanner sc=new Scanner(System.in);
private String originalReactantsString;
private String originalProductsString;
public final String backupReactantsString;
public final String backupProductsString;
public Hashtable<Integer, Hashtable<String, Integer>> reactants;
public Hashtable<Integer, Hashtable<String, Integer>> products;
public LinkedList<String> stringReactants;
public LinkedList<String> stringProducts;
public Matrix matrix;
public Hashtable<Integer, LinkedList<Integer>> finalSolution;
/**
* Creation of a ChemicalEquationBalancer4 object that is solved after some basic validation.
* @param args
*/
public static void main(String[] args) throws MatrixError {
ChemicalEquationBalancer4 equation=new ChemicalEquationBalancer4();
equation.solve();
}
/**
* Driver Method of Solving Algorithm. Ties up use of all classes. First, matrix is solved using gaussian elimination algorithm. Then, reduced matrix is made into algebraic equations and solved for variables in terms of last variable(n-1 x n matrix). Last, the lcm of all the denominators are multiplied to give the simplified solution.
*/
public void solve() throws MatrixError {
this.matrix.print=false;
this.matrix.gaussjordanElimination();
Fraction[] solutions = new Fraction[this.matrix.matrix[0].length];
for (int i = 0; i < this.matrix.matrix.length; i++) {
if (!this.matrix.matrix[i][this.matrix.matrix[i].length-1].equals(new Fraction(0, 1))){
solutions[i] = this.matrix.matrix[i][this.matrix.matrix[0].length-1];
}
}
solutions[this.matrix.matrix[0].length-1] = new Fraction(1, 1);
int lcm = 1;
for (Fraction f: solutions) {
lcm = Fraction.lcm(lcm, f.denominator);
}
for(int i = 0; i < solutions.length; i++){
solutions[i] = Fraction.multiply(new Fraction(lcm, 1), solutions[i]);
}
finalSolution.put(0, implementSubstitution(Arrays.copyOfRange(solutions, 0, reactants.size())));
finalSolution.put(1, implementSubstitution(Arrays.copyOfRange(solutions, reactants.size(), solutions.length)));
//Final Formatting
System.out.println("\nBalanced Equation: ");
System.out.println(formatSolution(backupReactantsString, finalSolution.get(0))+" ---> "+formatSolution(backupProductsString, finalSolution.get(1)));
}
public ChemicalEquationBalancer4() {
this(getInitialInput());
}
public static String getInitialInput(){
System.out.print("Enter either reactant side or whole equation separated by '=': ");
return sc.nextLine();
}
/**
* Initializer for ChemicalEquationBalancer4 Class. Handles, delegates, and preprocesses all user input to be ready to solve.
*/
public ChemicalEquationBalancer4(String firstInput){
if(firstInput.contains("=")){
String[] arr=firstInput.split("=");
originalReactantsString = removeUnnecessaryCharacters(arr[0].replace(" ",""));
originalProductsString = removeUnnecessaryCharacters(arr[1].replace(" ",""));
}else{
System.out.print("Enter product side: ");
String secondInput=sc.nextLine();
originalReactantsString = removeUnnecessaryCharacters(firstInput.replace(" ",""));
originalProductsString = removeUnnecessaryCharacters(secondInput.replace(" ",""));
}
this.backupReactantsString=originalReactantsString;
this.backupProductsString=originalProductsString;
//Process Input
List<Object> parseReactants=parseString(originalReactantsString);
reactants=(Hashtable<Integer, Hashtable<String, Integer>>) parseReactants.get(0);
stringReactants= (LinkedList<String>) parseReactants.get(1);
List<Object> parseProducts=parseString(originalProductsString);
products=(Hashtable<Integer, Hashtable<String, Integer>>) parseProducts.get(0);
stringProducts= (LinkedList<String>) parseProducts.get(1);
LinkedHashSet<String> reactantsElements=getElements(originalReactantsString);
LinkedHashSet<String> productsElements=getElements(originalProductsString);
if(reactantsElements.equals(productsElements)){
//Making Matrix
int[][] intMatrix = new int[reactantsElements.size()][reactants.size() + products.size()];
int currentIndex = 0;
for (String element: reactantsElements) {
int currentRowIndex = 0;
int[] intMatrixRow = new int[reactants.size() + products.size()];
for(int i=0; i<reactants.size(); i++) {
Hashtable<String, Integer> results = reactants.get(i);
intMatrixRow[currentRowIndex] = (results.getOrDefault(element, 0));
currentRowIndex += 1;
}
for(int i=0; i<products.size(); i++) {
Hashtable<String, Integer> results = products.get(i);
intMatrixRow[currentRowIndex] = ((products.size()-1==i ? 1: -1)*results.getOrDefault(element, 0));
currentRowIndex += 1;
}
intMatrix[currentIndex] = intMatrixRow;
currentIndex += 1;
}
this.matrix = new Matrix(intMatrix);
finalSolution=new Hashtable<>();
}else {
System.out.println("Error: Same el{ements need to be on both sides of the equation.");
System.exit(0);
}
}
/**
* Converts Fractions into integers for final formatting for either reactant or product side.
* @param arr Fraction Array of reactant or product coefficients
* @return LinkedList of Integers
*/
private static LinkedList<Integer> implementSubstitution(Fraction[] arr){
LinkedList<Integer> finalCoefficients=new LinkedList<>();
for (Fraction f: arr) {
finalCoefficients.addLast(f.numerator);
}
return finalCoefficients;
}
/**
* Gets all elements used on a side an equation
* @param inputString String representation of chemical equation side
* @return LinkedList of all elements on side
*/
private static LinkedHashSet<String> getElements(String inputString){
LinkedHashSet<String> elements=new LinkedHashSet<>();
String elementString="";
char character = 0;
for (int i=0; i<inputString.length(); i++){
character=inputString.charAt(i);
if (Character.isLetter(character)){
if (String.valueOf(character).toUpperCase().equals(String.valueOf(character))){
if (!elementString.equals("")){
elements.add(elementString);
elementString="";
}
}
elementString = elementString.concat(Character.toString(character));
}
else if (Character.toString(character).equals("+")){
elements.add(elementString);
elementString="";
}
}
if (!Character.toString(character).equals("")){
elements.add(elementString);
}
return elements;
}
/**
* Parses string to get Hashtable representation of a side of a chemical equation.
* @param inputString String representation of a side of a chemical equation.
* @return Hashtable representation of a chemical equation side
*/
private List<Object> parseString(String inputString){
LinkedList<String> compoundStringTable= new LinkedList<>();
Hashtable<Integer, Hashtable<String, Integer>> compoundTable= new Hashtable<>();
String storeString = "";
Integer index=0;
for (int j=0; j<inputString.length(); j++) {
if (Character.toString(inputString.charAt(j)).equals("+")) {
compoundStringTable.add(storeString);
compoundTable.put(index, parseCompound(storeString));
storeString="";
index=index+1;
}
else {
storeString=storeString.concat(Character.toString(inputString.charAt(j)));
}
}
compoundStringTable.add(storeString);
compoundTable.put(index, parseCompound(storeString));
return Arrays.asList(compoundTable, compoundStringTable);
}
/**
* Removes all characters that are not letters, numbers, parentheses(), and plus signs("+")
* @param currentString Unfiltered string
* @return Filtered String
*/
private static String removeUnnecessaryCharacters(String currentString){
return currentString.replaceAll("[^a-zA-Z0-9()+]", "");
}
/**
* String formatting of solution
* @param originalString String received from user.
* @param solutions LinkedList of solutions
* @return Formatted String
*/
private static String formatSolution(String originalString, LinkedList<Integer> solutions){
String[] arr=originalString.split("\\+");
StringBuilder s= new StringBuilder();
for (int i = 0; i < solutions.size(); i++) {
s.append(solutions.get(i));
s.append(arr[i]);
if(i<solutions.size()-1)
s.append(" + ");
}
return s.toString();
}
/**
* Parses each compound to get Hashtable of elements and quantities in compound
* @param inputString String representation of compound
* @return Hashtable representation of compound
*/
private static Hashtable<String, Integer> parseCompound(String inputString) {
Hashtable<String, Integer> dictionary = new Hashtable<>();
String symbol = "";
String numString = "";
StringBuilder paranthesesStoreString= new StringBuilder();
boolean parenthesesOn=false;
boolean parenthesesEnd=false;
String parenthesesScaler = "";
for (int i = 0; i < inputString.length(); i++) {
char character = inputString.charAt(i);
if (Character.isLetter(character)) {
//Checks that this is a letter
if (String.valueOf(character).toUpperCase().equals(String.valueOf(character))){
if(!parenthesesOn && !parenthesesEnd) {
//This is uppercase
if (!symbol.equals("")) {
//Symbol is filled and needs to be dumped
if (!dictionary.containsKey(symbol)) {
try {
dictionary.put(symbol, Integer.valueOf(numString));
} catch (NumberFormatException exception) {
dictionary.put(symbol, 1);
}
} else {
try {
dictionary.put(symbol, Integer.valueOf(numString) + dictionary.get(symbol));
} catch (NumberFormatException exception) {
dictionary.put(symbol, 1 + dictionary.get(symbol));
}
}
symbol = "";
numString = "";
}
symbol = symbol.concat(String.valueOf(character));
}else if(parenthesesOn && !parenthesesEnd){
paranthesesStoreString.append(character);
}else if(parenthesesEnd){
Hashtable<String, Integer> parenthesesParse=parseCompound(paranthesesStoreString.toString());
if(parenthesesScaler.equals(""))
parenthesesScaler="1";
for(String key: parenthesesParse.keySet()){
if (!dictionary.containsKey(key)) {
dictionary.put(key, parenthesesParse.get(key)*Integer.valueOf(parenthesesScaler));
} else {
dictionary.put(key, parenthesesParse.get(key)*Integer.valueOf(parenthesesScaler) + dictionary.get(key));
}
}
paranthesesStoreString = new StringBuilder();
parenthesesEnd=false;
parenthesesScaler="";
symbol = symbol.concat(String.valueOf(character));
}
}
else{
if(!parenthesesOn)
symbol = symbol.concat(String.valueOf(character));
else
paranthesesStoreString.append(character);
}
} else if (Character.isDigit(character)) {
//This is a number
if(!parenthesesOn && !parenthesesEnd) {
numString = numString.concat(String.valueOf(character));
}else if(parenthesesEnd && !parenthesesOn){
parenthesesScaler+=character;
}else if(parenthesesOn && !parenthesesEnd){
paranthesesStoreString.append(character);
}
}else if(character=='('){
//Start Statement
if(!parenthesesEnd) {
parenthesesOn = true;
if (!dictionary.containsKey(symbol)) {
try {
dictionary.put(symbol, Integer.valueOf(numString));
} catch (NumberFormatException exception) {
dictionary.put(symbol, 1);
}
} else {
try {
dictionary.put(symbol, Integer.valueOf(numString) + dictionary.get(symbol));
} catch (NumberFormatException exception) {
dictionary.put(symbol, 1 + dictionary.get(symbol));
}
}
symbol = "";
numString = "";
}else{
Hashtable<String, Integer> parenthesesParse=parseCompound(paranthesesStoreString.toString());
if(parenthesesScaler.equals(""))
parenthesesScaler="1";
for(String key: parenthesesParse.keySet()){
if (!dictionary.containsKey(key)) {
dictionary.put(key, parenthesesParse.get(key)*Integer.valueOf(parenthesesScaler));
} else {
dictionary.put(key, parenthesesParse.get(key)*Integer.valueOf(parenthesesScaler) + dictionary.get(key));
}
}
paranthesesStoreString = new StringBuilder();
parenthesesOn=true;
parenthesesEnd=false;
parenthesesScaler="";
}
}else if(character==')'){
//End statement
parenthesesEnd=true;
parenthesesOn=false;
}
}
if(!parenthesesEnd) {
if (numString.equals("")) {
numString = "1";
}
if (!dictionary.containsKey(symbol)) {
dictionary.put(symbol, Integer.valueOf(numString));
} else {
dictionary.put(symbol, Integer.valueOf(numString) + dictionary.get(symbol));
}
}else{
Hashtable<String, Integer> parenthesesParse=parseCompound(paranthesesStoreString.toString());
if(parenthesesScaler.equals(""))
parenthesesScaler="1";
for(String key: parenthesesParse.keySet()){
if (!dictionary.containsKey(key)) {
dictionary.put(key, parenthesesParse.get(key)*Integer.valueOf(parenthesesScaler));
} else {
dictionary.put(key, parenthesesParse.get(key)*Integer.valueOf(parenthesesScaler) + dictionary.get(key));
}
}
}
return dictionary;
}
}