public List<CombinationDto> sortByDistance(
List<Store> firstStores,
List<Store> secondStores,
List<Store> thirdStores) {
List<CombinationDto> combinationDtos = combineForDistance(firstStores, secondStores, thirdStores);
List<CombinationDto> list = combinationDtos.stream()
.sorted(Comparator.comparingDouble(CombinationDto::getTotalDistance)
.reversed()).limit(20)
.toList();
return list;
}
public List<CombinationDto> combineForDistance(
List<Store> firstStores,
List<Store> secondStores,
List<Store> thirdStores) {
DistanceCalculator distanceCalculator = new DistanceCalculator();
ConcurrentLinkedQueue<CombinationDto> combinations = new ConcurrentLinkedQueue<>();
int firstSize = firstStores.size();
int secondSize = secondStores.size();
int thirdSize = thirdStores.size();
IntStream.range(0, firstSize).parallel().forEach(i -> {
Store firstStore = firstStores.get(i);
IntStream.range(0, secondSize).parallel().forEach(j -> {
Store secondStore = secondStores.get(j);
IntStream.range(0, thirdSize).parallel().forEach(k -> {
Store thirdStore = thirdStores.get(k);
CombinationDto combinationDto = new CombinationDto(List.of(firstStore, secondStore, thirdStore));
double totalDistance = distanceCalculator.calculateDistance(combinationDto);
combinationDto.setDistance(totalDistance);
combinations.add(combinationDto);
});
});
});
return new ArrayList<>(combinations);
}
public class DistanceCalculator {
public static final double RADIUS = 6371; //지구 반지름(km)
public static final double TO_RADIAN = Math.PI / 180;
private final ConcurrentMap<String, Double> distanceCache = new ConcurrentHashMap<>();
public double calculateDistance(CombinationDto combination) {
List<Store> stores = combination.getStores();
double distance = 0;
for (int i = 0; i < stores.size() - 1; i++) {
distance += calculate(
new Coordination(stores.get(i).getLatitude(), stores.get(i).getLongitude()),
new Coordination(stores.get(i + 1).getLatitude(), stores.get(i + 1).getLongitude()));
}
return distance;
}
private double calculate(Coordination firstCoordination, Coordination secondCoordination) {
String key = firstCoordination.getLatitude() + "," + firstCoordination.getLongitude() +
"-" + secondCoordination.getLatitude() + "," + secondCoordination.getLongitude();
// 캐시에서 거리 찾기
double cahedDistance = distanceCache.getOrDefault(key, Double.NaN);
if (!Double.isNaN(cahedDistance)) {
return cahedDistance;
}
double firstLatitude = firstCoordination.getLatitude();
double firstLongitude = firstCoordination.getLongitude();
double secondLatitude = secondCoordination.getLatitude();
double secondLongitude = secondCoordination.getLongitude();
double deltaLatitude = Math.abs(firstLatitude - secondLatitude) * TO_RADIAN;
double deltaLongitude = Math.abs(firstLongitude - secondLongitude) * TO_RADIAN;
double sinDeltaLatitude = Math.sin(deltaLatitude / 2);
double sinDeltaLongitude = Math.sin(deltaLongitude / 2);
double squareRoot = Math.sqrt(
sinDeltaLatitude * sinDeltaLatitude +
Math.cos(firstLatitude * TO_RADIAN) *
Math.cos(secondLatitude * TO_RADIAN) *
sinDeltaLongitude * sinDeltaLongitude);
double distance = 2 * RADIUS * Math.asin(squareRoot);
distanceCache.put(key, distance);
return distance;
}
public void distanceCacheClear() {
distanceCache.clear();
}
}
public List<CombinationDto> sortByDistance(
List<Store> firstStores,
List<Store> secondStores,
List<Store> thirdStores) {
DistanceCalculator distanceCalculator = new DistanceCalculator();
List<CombinationDto> combinationDtos = combineForDistance(firstStores, secondStores, thirdStores);
combinationDtos.sort((a, b) -> {
double totalDistanceOfA = distanceCalculator.calculateDistance(a);
double totalDistanceOfB = distanceCalculator.calculateDistance(b);
return Double.compare(totalDistanceOfB, totalDistanceOfA);
});
distanceCalculator.distanceCacheClear();
return combinationDtos;
}
public List<CombinationDto> combineForDistance(
List<Store> firstStores,
List<Store> secondStores,
List<Store> thirdStores) {
List<CombinationDto> combinations = new ArrayList<>();
int firstSize = firstStores.size();
int secondSize = secondStores.size();
int thirdSize = thirdStores.size();
IntStream.range(0, firstSize).forEach(i -> {
Store firstStore = firstStores.get(i);
IntStream.range(0, secondSize).forEach(j -> {
Store secondStore = secondStores.get(j);
IntStream.range(0, thirdSize).forEach(k -> {
Store thirdStore = thirdStores.get(k);
combinations.add(new CombinationDto(List.of(firstStore, secondStore, thirdStore)));
});
});
});
return combinations;
}
병렬 처리를 해도 각 스레드의 연산이 가벼워서 오히려 병렬 처리의 단점이 부각됨
@Getter
@RequiredArgsConstructor
public class CombinationDto {
private final List<Store> stores;
private double totalDistance;
public void setDistance(double totalDistance) {
this.totalDistance = totalDistance;
}
}
스레드 하나의 작업이 생각보다 오래 걸리기 때문에 병렬 처리를 하면 효율성이 나타날 수 있다.
정렬을 할 때는 미리 계산해둔 총 거리만을 가져와서 sorting을 하게 한다.