Skip to content

Commit

Permalink
♻️ [FIX] 테스트 시작 문제 추출시 성능 개선
Browse files Browse the repository at this point in the history
테스트 문제를 추출할 때 동기적으로 호출되는 상태를 비동기로 전환하여 실행 속도를 향상시켰습니다.
  • Loading branch information
aj4941 committed Nov 30, 2023
1 parent 2e789be commit c6c9e91
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 104 deletions.
25 changes: 22 additions & 3 deletions src/main/java/swm_nm/morandi/domain/problem/dto/BojProblem.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
Expand All @@ -12,6 +16,21 @@
public class BojProblem {
private Long testProblemId;
private Long problemId; // baekjoon problem id (바꾸면 절대 안 됨 ObjectMapper 때문에)
private Integer level;
private String levelToString;
}
private String startLevel;
private String endLevel;
public static List<BojProblem> initBojProblems(List<DifficultyRange> difficultyRanges) {
List<BojProblem> bojProblems = new ArrayList<>();
IntStream.range(0, difficultyRanges.size()).forEach(i -> {
String startLevel = difficultyRanges.get(i).getStart().getShortName();
String endLevel = difficultyRanges.get(i).getEnd().getShortName();
BojProblem bojProblem = BojProblem.builder()
.testProblemId((long) i)
.problemId(null)
.startLevel(startLevel)
.endLevel(endLevel)
.build();
bojProblems.add(bojProblem);
});
return bojProblems;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import swm_nm.morandi.global.exception.errorcode.TestErrorCode;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand All @@ -28,81 +29,54 @@
@Slf4j
public class GetProblemsService {

private final TypeProblemListRepository typeProblemListRepository;

public void getProblemsByTestType(TestType testType, List<BojProblem> bojProblems) {
List<TypeProblemList> typeProblemLists = typeProblemListRepository.findByTestType_TestTypeId(testType.getTestTypeId());
List<Problem> problems = typeProblemLists.stream().map(TypeProblemList::getProblem).collect(Collectors.toList());
private final ObjectMapper objectMapper;
public List<BojProblem> getProblemsByApi(TestType testType, String bojId) {
List<DifficultyRange> difficultyRanges = testType.getDifficultyRanges();
long index = 1;
for (DifficultyRange difficultyRange : difficultyRanges) {
int start = DifficultyLevel.getLevelByValue(difficultyRange.getStart());
int end = DifficultyLevel.getLevelByValue(difficultyRange.getEnd());
boolean flag = false;
for (Problem problem : problems) {
int problemLevel = DifficultyLevel.getLevelByValue(problem.getProblemDifficulty());
if (start <= problemLevel && problemLevel <= end) {
BojProblem bojProblem = BojProblem.builder()
.testProblemId(index++)
.problemId(problem.getBojProblemId())
.level(DifficultyLevel.getLevelByValue(problem.getProblemDifficulty()))
.levelToString(problem.getProblemDifficulty().getFullName()).build();
bojProblems.add(bojProblem);
flag = true;
break;
}
}
List<BojProblem> bojProblems = BojProblem.initBojProblems(difficultyRanges);
String apiUrl = "https://solved.ac/api/v3/search/problem";

if (!flag) {
BojProblem bojProblem = BojProblem.builder()
.testProblemId(index++)
.problemId(0L)
.build();
bojProblems.add(bojProblem);
}
}
}
List<CompletableFuture<Void>> futures = bojProblems.stream()
.map(bojProblem -> CompletableFuture.runAsync(() -> {
String start = bojProblem.getStartLevel();
String end = bojProblem.getEndLevel();
String query = getString(testType, bojId, start, end);
String URL = apiUrl + "?query=" + query + "&page=1" + "&sort=random";

public void getProblemsByApi(TestType testType, String bojId, List<BojProblem> bojProblems) {
System.out.println("testType : " + testType);
List<DifficultyRange> difficultyRanges = testType.getDifficultyRanges();
long index = 1;
for (DifficultyRange difficultyRange : difficultyRanges) {
if (bojProblems.get((int) (index - 1)).getProblemId() != 0) {
index++;
continue;
}
String start = difficultyRange.getStart().getShortName();
String end = difficultyRange.getEnd().getShortName();
String apiUrl = "https://solved.ac/api/v3/search/problem";
while (true) {
String query = getString(testType, bojId, start, end);
WebClient webClient = WebClient.builder().build();
String jsonString = webClient.get()
.uri(apiUrl + "?query=" + query + "&page=1" + "&sort=random")
.retrieve()
.bodyToMono(String.class)
.block();

ObjectMapper mapper = new ObjectMapper();
try {
JsonNode rootNode = mapper.readTree(jsonString);
JsonNode itemsArray = rootNode.get("items");
if (itemsArray != null && itemsArray.isArray() && itemsArray.size() > 0) {
if (!isKorean(itemsArray)) continue;
long prev = index;
index = getProblem(bojProblems, index, mapper, itemsArray);
if (prev == index) continue;
break;
while (true) {
WebClient webClient = WebClient.builder().build();
String jsonString = webClient.get()
.uri(URL)
.retrieve()
.bodyToMono(String.class)
.block();
try {
JsonNode jsonNode = objectMapper.readTree(jsonString);
JsonNode itemsArray = jsonNode.get("items");
if (itemsArray != null && itemsArray.isArray() && itemsArray.size() > 0) {
if (getProblem(bojProblem, itemsArray))
break;
}
} catch (JsonProcessingException e) {
throw new MorandiException(TestErrorCode.JSON_PARSE_ERROR);
}
}
} catch (JsonProcessingException e) {
log.error("JsonProcessingException : {}", e.getMessage());
throw new MorandiException(TestErrorCode.JSON_PARSE_ERROR);
}
}
}
})).collect(Collectors.toList());

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

return bojProblems;
}

private boolean getProblem(BojProblem bojProblem, JsonNode itemsArray) {
for (JsonNode jsonNode : itemsArray) {
Long problemId = jsonNode.get("problemId").asLong();
String title = jsonNode.get("titleKo").asText();
if (problemId > 28000 || !title.matches(".*[가-힣]+.*")) continue;
bojProblem.setProblemId(problemId);
return true;
}
return false;
}
private static String getString(TestType testType, String bojId, String start, String end) {
String query = testType.getTestTypeId() == 7 ?
String.format("tier:%s..%s ~solved_by:%s tag:simulation ~tag:ad_hoc ~tag:constructive ~tag:geometry" +
Expand All @@ -111,24 +85,4 @@ private static String getString(TestType testType, String bojId, String start, S
" ~tag:number_theory ~tag:simulation ~tag:math solved:200.. solved:..5000", start, end, bojId);
return query;
}

private static long getProblem(List<BojProblem> bojProblems, long index, ObjectMapper mapper, JsonNode itemsArray)
throws JsonProcessingException {
JsonNode firstProblem = itemsArray.get(0);
BojProblem apiProblem = mapper.treeToValue(firstProblem, BojProblem.class);
if (apiProblem.getProblemId() > 28000)
return index;
BojProblem bojProblem = bojProblems.get((int) (index - 1));
bojProblem.setProblemId(apiProblem.getProblemId());
bojProblem.setLevel(apiProblem.getLevel());
bojProblem.setTestProblemId(index++);
bojProblem.setLevelToString(DifficultyLevel.getValueByLevel(bojProblem.getLevel()));
return index;
}

private static boolean isKorean(JsonNode itemsArray) {
JsonNode firstItemNode = itemsArray.get(0);
String title = firstItemNode.get("titleKo").asText();
return title.matches(".*[가-힣]+.*");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,12 @@ public TestStartResponseDto getTestStartsData(Long testTypeId) {
test = addTestService.startTestByTestTypeId(testType, member);

String bojId = memberInfoService.getMemberInfo().getBojId();
List<BojProblem> bojProblems = new ArrayList<>();

// 테스트 시작시 문제 가져오기
getProblemsService.getProblemsByTestType(testType, bojProblems);
// getProblemsService.getProblemsByTestType(testType, bojProblems);

// API로 문제 가져오기
getProblemsService.getProblemsByApi(testType, bojId, bojProblems);
List<BojProblem> bojProblems = getProblemsService.getProblemsByApi(testType, bojId);

// 테스트 시작시 문제 저장
saveProblemsService.saveAttemptProblems(member, test, bojProblems);
Expand All @@ -88,10 +87,10 @@ public TestStartResponseDto getTestStartsData(Long testTypeId) {
private TestStartResponseDto getTestStartResponseDto(Tests test, List<BojProblem> bojProblems,
TempCodeDto tempCodeDto) {
List<BojProblemDto> bojProblemDtos = bojProblems.stream().map(bojProblem ->
BojProblemDto.builder()
.isSolved(false)
.bojProblemId(bojProblem.getProblemId())
.build())
BojProblemDto.builder()
.isSolved(false)
.bojProblemId(bojProblem.getProblemId())
.build())
.collect(Collectors.toList());

Integer problemCount = test.getProblemCount();
Expand Down Expand Up @@ -122,9 +121,9 @@ private TestStartResponseDto getTestStartResponseDto(Tests test) {

List<BojProblemDto> bojProblemDtos =
attemptProblems.stream().map(attemptProblem -> BojProblemDto.builder()
.isSolved(attemptProblem.getIsSolved())
.bojProblemId(attemptProblem.getProblem().getBojProblemId())
.build()).collect(Collectors.toList());
.isSolved(attemptProblem.getIsSolved())
.bojProblemId(attemptProblem.getProblem().getBojProblemId())
.build()).collect(Collectors.toList());

List<TestCodeDto> testCodeDtos = getTestCodeDtos(test);

Expand All @@ -138,4 +137,4 @@ private TestStartResponseDto getTestStartResponseDto(Tests test) {
// 테스트 시작에 대한 ResponseDto 반환
return testStartResponseDto;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package swm_nm.morandi.domain.testStart.service;

import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class GetProblemsServiceTest {

}

0 comments on commit c6c9e91

Please sign in to comment.