Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ [FIX] 테스트 시작 문제 추출시 성능 개선 #584

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {

}