I’ve been doing UIL practice to grind definitely not because I was forced to for state/regional.
Recently I came across last year’s (2022) state packet, and I wanted to share my solutions to one of the problems.
Anyway’s let’s just get into it.
Problem statement
The problem statement is as follows:
Input consists of a single integer T, followed by T test cases.
For each test case, there will be an integer N, followed by N lines of input.
The N following lines define a system of linear equations in N variables.
All coefficients and the final constant are integers.
We need to solve the system of linear equations, and output the solution.
While the problem seems pretty straightforward, there’s a few caveats:
First of all, take a look at this example input:
3
1x+1y+1z=6
0x+2y+5z=-4
2x+5y-1z=27
We’re given the system of linear equations as pure strings, and it’s our job to parse every coefficient, variable name, and constant.
Thankfully, the input also tells us:
Variables are guarenteed to be a single lowercase letter from a - z.
Variables stay in the same order for all equations per test case.
Variables are not allowed to be the same and are not allowed to be duplicated within the same test case.`
Another caveat is the output. We have to output in the format var1=NUM1,var2=NUM2..., where NUMi is the solution for the variable vari rounded to 3 decimal places.
But first let’s worry about parsing the actual equations
Parsing input
A neat trick we can do is to use regex to parse the input. As we know the variables are lowercase letters, we can single them out like this:
First, we split on every character that isn’t a lowercase letter. This gives us an array of strings, where each string is either a variable or an empty string.
We can then filter out the empty strings with the cool removeIf method, and we’re left with just the variables.
For example:
"0x+2y+5z=-4" -> ["", "x", "", "y", "", "z", ""]
Filtering out s -> s.equals("") gives
["x", "y", "z"]
Now that we have the variables, let’s split on every character that IS a lowercase letter, as well as = to seperate the coefficients and constant.
We’ll store them in double[] for now because we know our final answer eventually becomes a float anyway, but we can use Integer.parseInt because we know
every coefficient and constant is an integer.
Here’s an example of what this does:
"0x+2y+5z=-4" -> ["0", "+2", "+5", "", "-4"]
Filtering out s -> s.equals("") gives
["0", "+2", "+5", "-4"]
Finally casting to int gives
left = [0, 2, 5]
ret = -4
Now that we have every piece of data extracted, let’s see how we can actually solve the system of linear equations.
Linear equations and linear algebra
Let’s first take a step back and consider the set of linear equations purely mathematically.
x+y+z2y+5z2x+5y−z=6=−4=27
We can represent this system of linear equations as a product of a matrix and a vector:
10212515−1xyz=6−427
This is because if we expand out the matrix multiplication, we get our original system of linear equations.
However, we can also reverse this multiplication to solve for the vector of variables.
10212515−1−16−427=xyz
But how do we go about implementing this in Java?
Implementation (Java boilerplate ahead ⚠️)
Since we know the size of the matrix is either 2x2 or 3x3, we can just hardcode the matrix inverse for each case.
I followed the algorithms shown here, and implemented them in Java.
First let’s start with the base boilerplate code:
Let’s also define a method for transposing a matrix (i.e. swapping rows and columns). It’s used for inverting a 3x3 matrix.
Now let’s define a method for calculating the determinant. Again, we hardcode both sizes seperately, and also introduce a helper function for 3x3 matrices.
Thankfully, the _subdet function from earlier can also help us calculate each of these smaller determinants.
This gives us final code which looks like:
Actually not too bad!
All together, we also need a final method to calculate the product of a matrix and a vector. This is so that we can multiply our inverted
matrix by the vector of constants to finally get the solution for each variable.
Putting it all together
So overall, our final process looks like this:
for each test case:
read in n
coefs = double[n][n]
constants = double[n]
vars = string[n]
for each equation:
read in equation
coef, cnst, vars = parseInputs(equation, n)
coefs[i] = coef
constants[i] = cnst
vars = vars # we override each time, but it doesn't matter because vars stay in same order and place each equation
mat = Matrix(coefs)
inv = mat.inverse()
solution = inv.multiply_vector(constants)
print out formatted solution
This should work regardless of 2 or 3 variables, because we have different code for each case.
My final code for this problem looks like this:
I do have this small tidbit before printing
This is because sometimes the float multiplication isn’t entirely accurate and might cause the printf to output something like -0.000, which this code fixes.
Conclusion
There are also a few other ways to solve this problem, including Gaussian Elimination, but I chose to do it this way because it’s very generalizable and you don’t have to worry
about weird row operations. I hope this writeup helped you understand this method and hopefully you’ll be able to use it in the future.
If you have any questions or suggestions, feel free to DM me on discord. Thanks for reading!