I have a List<BigDecimal>
collection which contains (for the sake of simplicity) BigDecimal
prices. I would like to process the collection and get:
- All of the highest prices.
- All of the lowest prices.
My initial thoughts are to approach this using look-behind in order to decide if the numbers are moving in an up or down trend. When the trend changes - determine which of the previous numbers are "highest" or "lowest" prices and then add them to the respectful List<BigDecimal> lowestPrices
and List<BigDecimal highestPrices
collections. For example, the first 3 dots are in an up-trend, but the 4th changes the trend to a down-trend. So can now determine the min/max
of the numbers before the change (0,1,2) and get the prices.
I am not entirely sure if this isn't a naive approach so I was wondering if there would be the best approach to solving this issue in java? Maybe a library that can already do this? (probably better not to re-invent the wheel)
CodePudding user response:
You are looking for local maxima (/minima).
Just look at whether the current point is greater (/less) than the point preceding and following it:
For a local maximum:
list.get(i) > list.get(i - 1) && list.get(i) > list.get(i 1)
For a local minimum:
list.get(i) < list.get(i - 1) && list.get(i) < list.get(i 1)
Pseudocode:
for (int i = 1; i < list.size()-1; i) {
if (local maximum) {
// Add to list of local maxima
} else if (local minimum) {
// Add to list of local minima
}
}
and handle the two endpoints as you desire.
(You can also do this in ways that are more efficient for non-random access lists, e.g. LinkedList
, using (List)Iterators; but the principle is the same).
CodePudding user response:
I decided to try implementing this, although I'm sure my implementation could be improved. The idea is just as you say, to keep track of the trend and record a local minimum or local maximum whenever the trend changes. There are two additional details to consider: first, initially we are not trending up or down, but the first value is either a minimum or maximum, so we have a third possibility for the trend, in addition to increasing or decreasing: inchoate; second, after the end of the loop we have to add the last item as either a minimum or maximum, depending on the direction the trend was going when we finished. Note that it will never add null if the list of prices is empty, because in that case, the trend would never have changed from inchoate.
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
public class Partition {
public static void main(String[] args) {
List<String> values = List.of("10.99", "15.99", "19.99", "12.99", "24.99",
"21.99", "17.99", "11.99", "22.99", "29.99", "35.99", "27.99", "20.99");
List<BigDecimal> prices = values.stream().map(BigDecimal::new).toList();
Map<Extrema, List<BigDecimal>> part = new Partition().partitionExtrema(prices);
System.out.format("Minima: %s%n", part.get(Extrema.MINIMA));
System.out.format("Maxima: %s%n", part.get(Extrema.MAXIMA));
}
public Map<Extrema, List<BigDecimal>> partitionExtrema(List<BigDecimal> prices) {
Trend trend = Trend.INCHOATE; // intially we don't know if we're going up or down
List<BigDecimal> maxima = new ArrayList<>();
List<BigDecimal> minima = new ArrayList<>();
BigDecimal previous = null;
for (BigDecimal current : prices) {
int direction = previous == null ? 0 : current.compareTo(previous);
if (direction > 0) {
if (trend != Trend.DECREASING) {
minima.add(previous); // switching from decreasing to increasing
}
trend = Trend.INCREASING;
}
if (direction < 0) {
if (trend != Trend.INCREASING) {
maxima.add(previous); // switching from increasing to decreasing
}
trend = Trend.DECREASING;
}
previous = current;
}
if (trend == trend.INCREASING) {
maxima.add(previous);
} else if (trend == trend.DECREASING) {
minima.add(previous);
}
return Map.of(Extrema.MINIMA, minima, Extrema.MAXIMA, maxima);
}
}
public enum Trend {
INCREASING,
DECREASING,
INCHOATE
}
public enum Extrema {
MAXIMA,
MINIMA
}