-1

In my application I have some List with tax rates getTaxRates(). The problem is that user can put some value which should not be possible. That means tax rates can be taken only from this list E.g. 7%, 9% and 21% and if user puts 7.5% then it should be saved 9%, if he puts 4% then 7%. The problem is the tax rate is flexible and I don't know what is the value- it can the list of 5 values e.g. 2%, 5%, 19%, 21% or 5%, 9%, 16%. How to implement this condition?

For know I have only the method which can see if the value is in range but no clue how to implement the condition checking the values which will come later from the user of my application settings.

private static boolean inRange(List<Integer> list, int min, int max) {
    return list.stream().allMatch(i -> i >= min && i <= max);
}
pcsutar
  • 1,715
  • 2
  • 9
  • 14
leccepen
  • 57
  • 1
  • 9
  • Why not just give the user a drop-down menu? If that's not possible for some odd reason, what rules are you following based on user input? Round up to the nearest acceptable value? – riddle_me_this Feb 27 '23 at 13:19
  • This must come from backend logic because there are some countries where you can put 3,4% and dropdown would be a big problem – leccepen Feb 27 '23 at 13:21
  • Yes but idea is that you have some application manager or person who can put allowed tax rates and on the other side the user who can put product with some wrong tax rate. And here I need to validate it to put only the correct tax in tax rate list. – leccepen Feb 27 '23 at 13:24
  • I don't want to have responsibility with allowed taxes and this is forwarded to some admin user who can determine allowed tax rate. This is the point of this :) It must come from backend – leccepen Feb 27 '23 at 13:29
  • Ok, but it sounds like you'd still need the rules to follow. Are you always rounding up if the input is in between tax brackets that you are given? Or are you throwing an exception? – riddle_me_this Feb 27 '23 at 13:30
  • Yes the rule is that rounding is up to 0.5% e.g. and if the user takes 3,6% then it will be taken the nearest highest rate from the list getTaxRates(). If there are 3 values like {3%,4%,17%} then the 4% is the correct one. – leccepen Feb 27 '23 at 13:36
  • 1
    Tax rates are grouped with conditional names (A, B, C, D ...) and the user chooses from them (he does not work with the real value). In the application, an administrative panel is created in which rates are allowed/disallowed and their real value is set. The conditional group (required) + the value is recorded in the history data (records in the database). – mr mcwolf Feb 27 '23 at 14:01

2 Answers2

1

There are multiple ways to approach this problem.

Here is one possible way using a List and BigDecimal to get you started:

public static BigDecimal findRate(List<BigDecimal> allowedRates, BigDecimal inputRate) {
    return allowedRates.stream()
            .min(Comparator.comparing(a -> inputRate.setScale(0, RoundingMode.HALF_UP)
                    .subtract(a).abs()))
            .orElseThrow(IllegalArgumentException::new);
}

Here is a sample Junit 5 test. You can check to see whether your particular requirements are met.

List<BigDecimal> ALLOWED_RATES = List.of(new BigDecimal("7.0"),
        new BigDecimal("9.0"),
        new BigDecimal("21.0"));

@ParameterizedTest(name = "#{index}: Testing {0}")
@DisplayName("Find the appropriate tax rate")
@CsvSource({
        "7.0, 7.0",
        "9.0, 9.0",
        "21.0, 21.0",
        "10.0, 9.0",
        "16.0, 21.0",
        "22.0, 21.0",
        "0, 7.0"
})
public void testFindTaxRate(BigDecimal input, BigDecimal expected) {
    BigDecimal actual = MyTaxRateCalculator.findRate(ALLOWED_RATES, input);
    Assertions.assertEquals(expected, actual);
}

In your production code, the method doesn't need to be static. It does make this unit test a little less verbose for this answer.

You may wish to sort, remove duplicates, or perform error-checking as needed based on your requirements on your allowed tax rate list. This method will also work fine if you use change the implementation from a List to a Set that maintains order if that works better for you. Using a Set is really nice, but it tends to require changes elsewhere in the system, and sometimes many IMO.

Finally, be careful which type you use for this.

UPDATE:

If you are looking to always choose the higher bracket given a choice between the two nearest values in your allowed list, then your code could become something like:

public static BigDecimal findRate(List<BigDecimal> allowedRates, BigDecimal inputRate) {

    BigDecimal lastBracket = allowedRates.stream().reduce((a, b) -> b).orElse(null);
    if (inputRate.compareTo(lastBracket) >= 0) {
        return lastBracket;
    }

    return allowedRates.stream()
            .filter(rate -> rate.compareTo(inputRate) >= 0)
            .findFirst()
            .orElseThrow(IllegalArgumentException::new);
}

And your test could be:

List<BigDecimal> ALLOWED_RATES = List.of(new BigDecimal("7.0"),
     new BigDecimal("9.0"),
     new BigDecimal("21.0"));

@ParameterizedTest(name = "#{index}: testing {0}")
@DisplayName("Find the appropriate tax rate")
@CsvSource({
        "7.0, 7.0",
        "9.0, 9.0",
        "21.0, 21.0",
        "10.0, 21.0",
        "16.0, 21.0",
        "22.0, 21.0",
        "0, 7.0"
})
public void testFindRate(BigDecimal input, BigDecimal expected) {
    BigDecimal actual = MyTaxRateCalculator.findRate(ALLOWED_RATES, input);
    Assertions.assertEquals(expected, actual);
}
riddle_me_this
  • 8,575
  • 10
  • 55
  • 80
  • I think you're finding the nearest allowed rate to the one the user entered, but the OP wants the next highest one. E.g. if allowed rattes = 7, 9, 21, then if the user inputs 10 the result should be 21, not 9. – k314159 Feb 27 '23 at 16:16
  • I was confused about that as well, and the reply was "Yes the rule is that rounding is up to 0.5%" so I took that to mean half-way given OP's example. – riddle_me_this Feb 27 '23 at 23:20
  • The OP is indeed unclear, but the example given as "E.g. 7%, 9% and 21% and if user puts 7.5% then it should be saved 9%" implies that it should be rounded up to the nearest allowed rate that is equal to or higher than the given rate. Otherwise, it would be 7% because that is nearer to the input 7.5%. – k314159 Feb 27 '23 at 23:24
  • Good point. Will revise – riddle_me_this Feb 27 '23 at 23:25
  • 1
    Can't `lastBracket = allowedRates.stream()...` be shortened to `lastBracket = allowedRates.get(allowedRates.size() - 1)`? – k314159 Feb 28 '23 at 08:44
  • Yes, sure. The other way just uses streams. Other choices too: https://stackoverflow.com/questions/21426843/get-last-element-of-stream-list-in-a-one-liner – riddle_me_this Feb 28 '23 at 13:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/252195/discussion-between-vphilipnyc-and-k314159). – riddle_me_this Feb 28 '23 at 13:16
0

You can use TreeSet for this kind of implementation. TreeSet provides ceiling() method. This method will return the least element in the set greater than or equal to the given element, or null if there is no such element.

Implementation:

private static Float getTaxRates(TreeSet<Float> taxRates, Float taxRate) {
    return taxRates.ceiling(taxRate);
}
pcsutar
  • 1,715
  • 2
  • 9
  • 14