-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
243 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 103 additions & 20 deletions
123
src/main/java/com/hobak/happinessql/domain/report/application/LocationHappinessAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,140 @@ | ||
package com.hobak.happinessql.domain.report.application; | ||
|
||
import com.hobak.happinessql.domain.record.domain.Record; | ||
import com.hobak.happinessql.domain.report.converter.ReportConverter; | ||
import com.hobak.happinessql.domain.report.dto.LocationHappinessDto; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
public class LocationHappinessAnalyzer { | ||
|
||
public static String getHappiestLocation(List<Record> records) { | ||
|
||
if (records == null || records.isEmpty()) { | ||
return null; | ||
} | ||
|
||
// 도시와 구를 기준으로 Record 그룹화 | ||
Map<String, List<Record>> locationRecordsMap = records.stream() | ||
Map<String, List<Record>> locationRecordsMap = groupRecordsByLocation(records); | ||
|
||
// 위치별 평균 행복도와 빈도 계산 | ||
Map<String, Double> locationAverageHappiness = calculateLocationAverageHappiness(locationRecordsMap); | ||
Map<String, Integer> locationFrequency = calculateLocationFrequency(locationRecordsMap); | ||
|
||
// 평균 행복도가 가장 높은 위치 찾기 | ||
double maxHappiness = Collections.max(locationAverageHappiness.values()); | ||
|
||
// 평균 행복도가 가장 높은 위치들 중 빈도가 가장 높은 위치 찾기 | ||
Optional<String> happiestLocation = findHappiestLocation(locationAverageHappiness, locationFrequency, maxHappiness); | ||
|
||
// 평균 행복도와 빈도가 같다면, 랜덤으로 선택 | ||
List<String> happiestLocations = findLocationsWithMaxHappiness(locationAverageHappiness, maxHappiness); | ||
|
||
if (happiestLocations.size() > 1) { | ||
Collections.shuffle(happiestLocations); | ||
return happiestLocations.get(0); | ||
} else { | ||
return happiestLocation.orElse(null); | ||
} | ||
} | ||
|
||
public static List<LocationHappinessDto> getLocationRankings(List<Record> records, int topCount) { | ||
List<LocationHappinessDto> locationRankings = new ArrayList<>(); | ||
if (records == null || records.isEmpty()) { | ||
// 데이터가 없는 경우에도 빈 LocationHappinessDto 객체를 topCount만큼 추가 | ||
for (int i = 0; i < topCount; i++) { | ||
locationRankings.add(ReportConverter.toLocationHappinessDto(i + 1, null)); | ||
} | ||
return locationRankings; | ||
} | ||
|
||
// 도시와 구를 기준으로 Record 그룹화 | ||
Map<String, List<Record>> locationRecordsMap = groupRecordsByLocation(records); | ||
|
||
// 위치별 평균 행복도와 빈도 계산 | ||
Map<String, Double> locationAverageHappiness = calculateLocationAverageHappiness(locationRecordsMap); | ||
Map<String, Integer> locationFrequency = calculateLocationFrequency(locationRecordsMap); | ||
|
||
// 평균 행복도와 빈도를 기준으로 위치들을 정렬, 동일한 행복도와 빈도일 경우 사전순 정렬 | ||
List<String> sortedLocations = sortLocations(locationAverageHappiness, locationFrequency); | ||
|
||
// 상위 N개의 위치 선정 | ||
for (int i = 0; i < Math.min(topCount, sortedLocations.size()); i++) { | ||
String location = sortedLocations.get(i); | ||
LocationHappinessDto locationDto = ReportConverter.toLocationHappinessDto(i + 1, location); | ||
locationRankings.add(locationDto); | ||
} | ||
|
||
// 만약 topCount보다 적게 선정된 경우, 나머지 빈 항목 추가 | ||
while (locationRankings.size() < topCount) { | ||
locationRankings.add(ReportConverter.toLocationHappinessDto(locationRankings.size() + 1, null)); | ||
} | ||
|
||
return locationRankings; | ||
} | ||
|
||
private static Map<String, List<Record>> groupRecordsByLocation(List<Record> records) { | ||
return records.stream() | ||
.filter(record -> record.getLocation() != null) | ||
.collect(Collectors.groupingBy(record -> | ||
record.getLocation().getCity() + " " + record.getLocation().getDistrict())); | ||
} | ||
|
||
// 각 위치별 평균 행복도와 빈도 계산 | ||
private static Map<String, Double> calculateLocationAverageHappiness(Map<String, List<Record>> locationRecordsMap) { | ||
Map<String, Double> locationAverageHappiness = new HashMap<>(); | ||
Map<String, Integer> locationFrequency = new HashMap<>(); | ||
|
||
locationRecordsMap.forEach((location, recordList) -> { | ||
locationAverageHappiness.put(location, recordList.stream() | ||
double averageHappiness = recordList.stream() | ||
.mapToInt(Record::getHappiness) | ||
.average() | ||
.orElse(Double.NaN)); | ||
locationFrequency.put(location, recordList.size()); | ||
.orElse(Double.NaN); | ||
locationAverageHappiness.put(location, averageHappiness); | ||
}); | ||
return locationAverageHappiness; | ||
} | ||
|
||
// 평균 행복도가 가장 높은 위치 찾기 | ||
double maxHappiness = Collections.max(locationAverageHappiness.values()); | ||
private static Map<String, Integer> calculateLocationFrequency(Map<String, List<Record>> locationRecordsMap) { | ||
Map<String, Integer> locationFrequency = new HashMap<>(); | ||
locationRecordsMap.forEach((location, recordList) -> { | ||
locationFrequency.put(location, recordList.size()); | ||
}); | ||
return locationFrequency; | ||
} | ||
|
||
// 평균 행복도가 가장 높은 위치들 중 빈도가 가장 높은 위치를 찾기 | ||
Optional<String> happiestLocation = locationAverageHappiness.entrySet().stream() | ||
private static Optional<String> findHappiestLocation(Map<String, Double> locationAverageHappiness, | ||
Map<String, Integer> locationFrequency, | ||
double maxHappiness) { | ||
return locationAverageHappiness.entrySet().stream() | ||
.filter(entry -> entry.getValue() == maxHappiness) | ||
.max(Comparator.comparing(entry -> locationFrequency.get(entry.getKey()))) | ||
.map(Map.Entry::getKey); | ||
} | ||
|
||
// 평균 행복도와 빈도가 같다면, 랜덤으로 선택 | ||
List<String> happiestLocations = locationAverageHappiness.entrySet().stream() | ||
private static List<String> findLocationsWithMaxHappiness(Map<String, Double> locationAverageHappiness, | ||
double maxHappiness) { | ||
return locationAverageHappiness.entrySet().stream() | ||
.filter(entry -> entry.getValue() == maxHappiness) | ||
.map(Map.Entry::getKey) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
if (happiestLocations.size() > 1) { | ||
Collections.shuffle(happiestLocations); | ||
return happiestLocations.get(0); | ||
} else { | ||
return happiestLocation.orElse(null); | ||
} | ||
private static List<String> sortLocations(Map<String, Double> locationAverageHappiness, | ||
Map<String, Integer> locationFrequency) { | ||
return locationAverageHappiness.entrySet().stream() | ||
.sorted((entry1, entry2) -> { | ||
double happinessDiff = entry2.getValue() - entry1.getValue(); | ||
if (happinessDiff != 0) { | ||
return Double.compare(entry2.getValue(), entry1.getValue()); | ||
} else { | ||
int frequencyDiff = locationFrequency.get(entry2.getKey()) - locationFrequency.get(entry1.getKey()); | ||
if (frequencyDiff != 0) { | ||
return Integer.compare(locationFrequency.get(entry2.getKey()), locationFrequency.get(entry1.getKey())); | ||
} else { | ||
return entry1.getKey().compareTo(entry2.getKey()); | ||
} | ||
} | ||
}) | ||
.map(Map.Entry::getKey) | ||
.toList(); | ||
} | ||
|
||
} |
42 changes: 42 additions & 0 deletions
42
...in/java/com/hobak/happinessql/domain/report/application/ReportActivityRankingService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.hobak.happinessql.domain.report.application; | ||
|
||
import com.hobak.happinessql.domain.record.domain.Record; | ||
import com.hobak.happinessql.domain.record.repository.RecordRepository; | ||
import com.hobak.happinessql.domain.report.dto.ActivityHappinessDto; | ||
import com.hobak.happinessql.domain.user.domain.User; | ||
import lombok.RequiredArgsConstructor; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.List; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ReportActivityRankingService { | ||
|
||
private final RecordRepository recordRepository; | ||
|
||
public List<ActivityHappinessDto> getTop3AllHappiestActivities(User user) { | ||
List<Record> records = recordRepository.findAllByUser(user); | ||
return ActivityHappinessAnalyzer.getActivityRankings(records, 3); | ||
} | ||
|
||
public List<ActivityHappinessDto> getTop3AnnualHappiestActivities(User user) { | ||
int currentYear = LocalDate.now().getYear(); | ||
LocalDateTime startOfYear = LocalDateTime.of(currentYear, 1, 1, 0, 0); | ||
LocalDateTime endOfYear = LocalDateTime.of(currentYear, 12, 31, 23, 59, 59); | ||
List<Record> records = recordRepository.findAllByCreatedAtBetweenAndUser(startOfYear, endOfYear, user); | ||
return ActivityHappinessAnalyzer.getActivityRankings(records, 3); | ||
} | ||
|
||
public List<ActivityHappinessDto> getTop3MonthlyHappiestActivities(User user) { | ||
LocalDate today = LocalDate.now(); | ||
LocalDate startOfMonth = today.withDayOfMonth(1); | ||
LocalDate endOfMonth = today.withDayOfMonth(today.lengthOfMonth()); | ||
LocalDateTime startOfMonthDateTime = startOfMonth.atStartOfDay(); | ||
LocalDateTime endOfMonthDateTime = endOfMonth.atTime(23, 59, 59); | ||
List<Record> records = recordRepository.findAllByCreatedAtBetweenAndUser(startOfMonthDateTime, endOfMonthDateTime, user); | ||
return ActivityHappinessAnalyzer.getActivityRankings(records, 3); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
...in/java/com/hobak/happinessql/domain/report/application/ReportLocationRankingService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.hobak.happinessql.domain.report.application; | ||
|
||
import com.hobak.happinessql.domain.record.domain.Record; | ||
import com.hobak.happinessql.domain.record.repository.RecordRepository; | ||
import com.hobak.happinessql.domain.report.dto.LocationHappinessDto; | ||
import com.hobak.happinessql.domain.user.domain.User; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ReportLocationRankingService { | ||
private final RecordRepository recordRepository; | ||
|
||
public List<LocationHappinessDto> getTop3AllHappiestLocations(User user) { | ||
List<Record> records = recordRepository.findAllByUser(user); | ||
return LocationHappinessAnalyzer.getLocationRankings(records, 3); | ||
} | ||
|
||
public List<LocationHappinessDto> getTop3AnnualHappiestLocations(User user) { | ||
int currentYear = LocalDate.now().getYear(); | ||
LocalDateTime startOfYear = LocalDateTime.of(currentYear, 1, 1, 0, 0); | ||
LocalDateTime endOfYear = LocalDateTime.of(currentYear, 12, 31, 23, 59, 59); | ||
List<Record> records = recordRepository.findAllByCreatedAtBetweenAndUser(startOfYear, endOfYear, user); | ||
return LocationHappinessAnalyzer.getLocationRankings(records, 3); | ||
} | ||
|
||
public List<LocationHappinessDto> getTop3MonthlyHappiestLocations(User user) { | ||
LocalDate today = LocalDate.now(); | ||
LocalDate startOfMonth = today.withDayOfMonth(1); | ||
LocalDate endOfMonth = today.withDayOfMonth(today.lengthOfMonth()); | ||
LocalDateTime startOfMonthDateTime = startOfMonth.atStartOfDay(); | ||
LocalDateTime endOfMonthDateTime = endOfMonth.atTime(23, 59, 59); | ||
List<Record> records = recordRepository.findAllByCreatedAtBetweenAndUser(startOfMonthDateTime, endOfMonthDateTime, user); | ||
return LocationHappinessAnalyzer.getLocationRankings(records, 3); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/main/java/com/hobak/happinessql/domain/report/dto/LocationHappinessDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.hobak.happinessql.domain.report.dto; | ||
|
||
|
||
import lombok.*; | ||
|
||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class LocationHappinessDto { | ||
private int ranking; | ||
private String location; | ||
|
||
@Builder | ||
public LocationHappinessDto(int ranking, String location) { | ||
this.ranking = ranking; | ||
this.location = location; | ||
} | ||
} |