-2

I wanted to write this program to check type of triangle using either sides or angle inputs. I tried below code which is working perfectly as it should, as in logic and result. But I feel Its redundant and could be shorter with fewer methods.

So I want to know if this program could be shorter ?


import java.util.Scanner;
public class Test123 {
    int a;
    int b;
    int c;
    public Test123(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public void determineType() {
        if(a >= (b+c) || c >= (b+a) || b >= (a+c) ) {
            System.out.println( "Not a Triangle");
        } else if(a==b && b==c) {
            System.out.println( "Equilateral Triangle");
        } else if (((a * a) + (b * b)) == (c * c) || ((a * a) + (c * c)) == (b * b) || ((c * c) + (b * b)) == (a * a)) {
            System.out.println( "Right Triangle");
        } else if(a!=b && b!=c && c!=a) {
            System.out.println( "Scalene Triangle" );
        } else if ((a==b && b!=c ) || (a!=b && c==a) || (c==b && c!=a)) {
            System.out.println( "Isosceles Triangle");
        }
    }
    public void determineTypeA() {
        if(a+b+c!=180) {
            System.out.println( "Not a Triangle");
        } else if(a==b && b==c) {
            System.out.println( "Equilateral Triangle");
        } else if (a==90||b==90||c==90) {
            System.out.println( "Right Triangle");
        } else if(a!=b && b!=c && c!=a) {
            System.out.println( "Scalene Triangle" );
        } else if ((a==b && b!=c ) || (a!=b && c==a) || (c==b && c!=a)) {
            System.out.println( "Isosceles Triangle");
        }
    }
    
    public static void calc() {
        Scanner in = new Scanner(System.in);
        int a = in.nextInt();
        int b = in.nextInt();
        int c = in.nextInt();
        Test123 t = new Test123(a, b, c);
        t.determineType();
    }
    public static void calc2() {
        Scanner in = new Scanner(System.in);
        int a = in.nextInt();
        int b = in.nextInt();
        int c = in.nextInt();
        Test123 t = new Test123(a, b, c);
        t.determineTypeA();
    }
    public static void main(String [] args) {
        char choice;
        Scanner in = new Scanner(System.in);
        System.out.println("Enter 'a' for angle input and 's' for sides input");
        choice=in.next().charAt(0);
        switch(choice)
        {
            case 'a'
            :System.out.println("Enter the angles of Triangle");
            calc2();       
            break;
            case 's'
            :System.out.println("Enter the sides of Triangle");
            calc();
            break;
        }
    }
}

Please help.

  • If you are good to use stream api,you could define each ‘if’ as predicates and stream through predicates and return at first success.Lines of code might increase but will increase readability – Sagar Sep 02 '23 at 16:33
  • 2
    Hello. If your code works and you are looking for ways to *improve* it then your question is better suited on https://codereview.stackexchange.com. Anyway consider removing `calc()` method since it doesn't do anything useful. It only reads data to *local variables* `a` `b` and `c` which are then discarded since method ends. Also consider adding either more variables for angles, like `int alpha, beta, gamma;` OR some boolean flag like `boolean isStorringAngles;` to let you know if `a` `b` `c` represent angles or sides. This way you can have only one method for checking triangle type. – Pshemo Sep 02 '23 at 16:34
  • I’m voting to close this question because it belongs on Code Review Stack Exchange site. https://codereview.stackexchange.com/ – Basil Bourque Sep 02 '23 at 16:50
  • Vague title. Rewrite to summarize your specific technical issue. – Basil Bourque Sep 02 '23 at 16:51
  • Anyway I am not sure what is the goal of this code. For now it looks like you just want to print type of triangle and forget about data you used. If that is the case you don't even need instance of Test123 (triangle) to store `a` `b` `c`. Instead create two static methods to determine type of triangle and pass all required data as parameters like `public static void determineTypeBySides(int a, int b, int c){..}` and `public static void determineTypeByAngles(int a, int b, int c){..}`. This would let you get rid of `calc` and `calc1` methods, constructor and `a` `b` `c` fields. – Pshemo Sep 02 '23 at 16:52
  • Do you intend for all inputted values to be non-negative? If so, you should check this also. – Andy Turner Sep 02 '23 at 17:12

2 Answers2

0

Here is one way. I used enums for indicating triangle types and the measurement used.

Demo

List<int[]> triangles = List.of(
        new int[]{30, 40, 50}, // right isoceles
        new int[]{70, 60, 40}, // scalene
        new int[]{50, 50, 50}, // equilateral
        new int[]{70, 70, 50}, // isoceles
        new int[]{40, 40, 90}, // invalid
        new int[]{0, 50, 50}, // invalid
        new int[]{-1, 20, 30}, // invalid
        new int[]{10, 20, 30, 40}); // invalid

System.out.println("Using sides");
for (int[] triangle : triangles) {
    EnumSet<Triangle> eset = determineType(Measurement.SIDE,
            triangle[0], triangle[1], triangle[2]);
    System.out.printf("%-16s --> %s%n", Arrays.toString(triangle), eset);
}
System.out.println("\nUsing angles");

triangles = List.of(
       new int[] {30, 60, 90}, // right,scalene
       new int[]{45, 45, 90}, // right isoceles
       new int[]{60, 60, 60}, // equilateral
       new int[]{40, 60, 80}, // scalene
       new int[]{40, 40, 90}, // invalid
       new int[]{0, 50, 50}, // invalid
       new int[]{-1, 20, 30}, // invalid
       new int[]{10, 20, 30, 40}); // invalid

for (int[] triangle : triangles) {
    EnumSet<Triangle> eset = determineType(Measurement.ANGLE,
            triangle[0], triangle[1], triangle[2]);
    System.out.printf("%-16s --> %s%n", Arrays.toString(triangle), eset);
}

prints

Using sides
[30, 40, 50]     --> [SCALENE, RIGHT]
[70, 60, 40]     --> [SCALENE]
[50, 50, 50]     --> [EQUILATERAL]
[70, 70, 50]     --> [ISOCELES]
[40, 40, 90]     --> [INVALID_TRIANGLE]
[0, 50, 50]      --> [INVALID_TRIANGLE]
[-1, 20, 30]     --> [INVALID_TRIANGLE]
[10, 20, 30, 40] --> [INVALID_TRIANGLE]

Using angles
[30, 60, 90]     --> [SCALENE, RIGHT]
[45, 45, 90]     --> [RIGHT, ISOCELES]
[60, 60, 60]     --> [EQUILATERAL]
[40, 60, 80]     --> [SCALENE]
[40, 40, 90]     --> [INVALID_TRIANGLE]
[0, 50, 50]      --> [INVALID_TRIANGLE]
[-1, 20, 30]     --> [INVALID_TRIANGLE]
[10, 20, 30, 40] --> [INVALID_TRIANGLE]

Details

First, I used an enum to describe the Triangle and Measurement types.

enum Triangle {
        SCALENE, EQUILATERAL, RIGHT, ISOCELES, INVALID_TRIANGLE
}

enum Measure {ANGLE, SIDE};

Here is a validate method to ensure the angles or sides constitute a valid triangle. Some tests can be independent of measure type while others can't.

  • if any side or angle is <= 0 return false
  • if the sum of the angles is not 180, return false.
  • if the sum of the two shorter legs are less than the third, return false.
  • otherwise, true is returned and the triangle is valid.
private static boolean isValidTriangle(int[] values, Measurement measurementType) {
    if (values.length != 3 || Arrays.stream(values).anyMatch(aors -> aors <= 0)) {
        return false;
    }
    return switch (measurementType) {
        case ANGLE -> Arrays.stream(values).sum() == 180;
        case SIDE -> values[0] + values[1] > values[2];
    };
}

Now to determine the type. The first thing is to validate the triangle. I could have put the validation code in this method but wanted to reduce clutter.

For angles or sides, the rules here are as follows:

  • if the set contains a single value, two must have been duplicates so it must be equilateral.
  • if the set contains two values, one must have been a duplicate so it must be an isoceles
  • if the set contains three values then all three are different, and it must be a scalene

Then do specific tests for measurement types. And remember that a right triangle is also a scalene or isoceles triangle. In the latter case the sides are irrational so an isoceles right triangle won't occur with integral sides. But it can occur when specifying angles.

  • if the measurement type is ANGLE and the set contains 90 then it must be a right triangle
  • if the measurement type is SIDE and the Pythagorean theorem holds it must be a right triangle.
public static EnumSet<Triangle> determineType(Measurement measurementType,
        int... values) {
    
    Set<Integer> set =new HashSet<>();
    for (int i : values) {
        set.add(i);
    }
   
    if (measurementType.equals(Measurement.SIDE)) {
        Arrays.sort(values); // required for side validation
    }
    if (!isValidTriangle(values, measurementType)) {
        return EnumSet.of(Triangle.INVALID_TRIANGLE);
    }
    EnumSet<Triangle> eset = EnumSet.noneOf(Triangle.class);
    
    switch (set.size()) {
        case 1 -> eset.add(Triangle.EQUILATERAL);
        case 2 -> eset.add(Triangle.ISOCELES);
        case 3 -> eset.add(Triangle.SCALENE);
    }

    switch (measurementType) {
        case ANGLE -> {
            if (Arrays.stream(values).anyMatch(a->a == 90)) {
                eset.add(Triangle.RIGHT);
            }
        }
        case SIDE -> {
            if (values[0] * values[0] + values[1] * values[1] == values[2]
                    * values[2]) {
                eset.add(Triangle.RIGHT);
            }
        }
    }

    return eset;
}

To prompt for input you can do the following:

  • enter the measurement type (side or angle)
  • then enter the values corresponding to that type.
Scanner input = new Scanner(System.in);
String type = "";
do {
    System.out.printf("Enter angle or side: ");
    type = input.nextLine().toLowerCase();
} while (!type.equals("side") && !type.equals("angle"));
Measurement measurementType = type.equals("side") ? Measurement.SIDE :
                   Measurement.ANGLE;

int[] values = new int[3];
for (int i = 0; i <= 2; i++) {
    System.out.printf("Enter %s %d: ", type, i+1);
    values[i] = input.nextInt();
}

To invoke the method and print the results do:


EnumSet<Triangle> eset = determineType(MeasurementType,
            values[0], values[1], values[2]);
System.out.printf("%-16s --> %s%n", Arrays.toString(values), eset);
WJS
  • 36,363
  • 4
  • 24
  • 39
-5

So, I assume that you've identified that there is duplicated code here:

Scanner in = new Scanner(System.in);
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
Test123 t = new Test123(a, b, c);

So, what you could do is extract this code from calc() and calc2() , into an new method:

public static Test123 getTest123FromUserInput(){
    Scanner in = new Scanner(System.in);
    int a = in.nextInt();
    int b = in.nextInt();
    int c = in.nextInt();
    return new Test123(a, b, c);
}

And call it from calc() and calc2():

public static void calc() {
    Test123 t = getTest123FromUserInput();
    t.determineType();
}

public static void calc2() {
    Test123 t = getTest123FromUserInput();
    t.determineTypeA();
}

You could also utilize method chaining, since you call determineType() on the Test123 object, and getTest123FromUserInput() returns a Test123 object. So it can be reduced.

From:

Test123 t = getTest123FromUserInput();
t.determineType();

To:

getTest123FromUserInput().determineType();

Of course, you could reduce it further, by getting rid of the calc() and calc2() by moving them to the switch-case:

switch(choice)
        {
            case 'a':
                System.out.println("Enter the angles of Triangle");
                getTest123FromUserInput().determineTypeA();
                break;
            case 's':
                System.out.println("Enter the sides of Triangle");
                getTest123FromUserInput().determineType();
                break;
        }

But this may be reducing it too much, making it more difficult to expand upon later. But it is "shorter".

Chrimle
  • 490
  • 1
  • 4
  • 10
  • 2
    Why does this answer encourage creation of multiple `Scanner` instances based on `System.in`? This is not only unnecessary, [it carries a significant risk](https://stackoverflow.com/a/65503544/). If you're giving an answer that is supposed to improve the OP's code, especially the code of a newbie to Java, then best to actually improve the code. – Hovercraft Full Of Eels Sep 02 '23 at 17:57
  • There are multiple issues with the original code. That is not what they were requested to be addressed. I think for a "newbie", the use of method chaining and how to refactor duplicate code, is significantly more important than having too many `Scanner`s... – Chrimle Sep 02 '23 at 22:31