Skip to content
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
13 changes: 13 additions & 0 deletions src/main/java/com/going/server/domain/quiz/dto/ConnectQuizDto.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
package com.going.server.domain.quiz.dto;

import com.going.server.domain.graph.dto.KnowledgeGraphDto;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Builder
@Getter
public class ConnectQuizDto {
private KnowledgeGraphDto knowledgeGraph; // 보여줄 지식 그래프
private List<ConnectQuiz> quizList;

@Builder
@Getter
public static class ConnectQuiz{
private String questionTargetId; // ? 띄울 노드 id
private List<String> shuffledOptions; // 문제 리스트
private String answer; // 정답
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.going.server.domain.quiz.generate;

import com.going.server.domain.graph.dto.EdgeDto;
import com.going.server.domain.graph.dto.KnowledgeGraphDto;
import com.going.server.domain.graph.dto.NodeDto;
import com.going.server.domain.graph.entity.Graph;
import com.going.server.domain.graph.entity.GraphEdge;
import com.going.server.domain.graph.entity.GraphNode;
import com.going.server.domain.quiz.dto.ConnectQuizDto;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class ConnectQuizGenerator implements QuizGenerator<ConnectQuizDto> {

@Override
public ConnectQuizDto generate(Graph graph) {

// 1. 지식그래프 조회
List<NodeDto> nodeDtoList = new ArrayList<>();
List<EdgeDto> edgeDtoList = new ArrayList<>();

for (GraphNode node : graph.getNodes()) {
NodeDto nodeDto = NodeDto.from(node, null);
nodeDtoList.add(nodeDto);

if (node.getEdges() != null) {
for (GraphEdge edge : node.getEdges()) {
EdgeDto edgeDto = EdgeDto.from(edge.getSource(),edge.getTarget().getNodeId().toString(),edge.getLabel());
edgeDtoList.add(edgeDto);
}
}
}

// 2. 문제 생성
Random random = new Random();
// 최종 문제 리스트
List<ConnectQuizDto.ConnectQuiz> quizList = new ArrayList<>();
// 이미 사용한 노드 Id 기록용 (중복 방지)
Set<Integer> usedNodeIds = new HashSet<>();

// 문제 3개 만들기
for (int i = 0; i < 3; i++) {
createConnectQuiz(random, nodeDtoList, quizList, usedNodeIds);
}

// 3. 반환
return ConnectQuizDto.builder()
.knowledgeGraph(KnowledgeGraphDto.of(nodeDtoList, edgeDtoList))
.quizList(quizList)
.build();
}

// connect 퀴즈 문제 생성
private static void createConnectQuiz(Random random, List<NodeDto> nodeDtoList, List<ConnectQuizDto.ConnectQuiz> quizList, Set<Integer> usedNodeIndices) {
if(usedNodeIndices.size() >= nodeDtoList.size()) {
// 모든 노드를 다 사용했으면 추가 생성 불가
return;
}

int questionTargetId;

// nodeDtoList 중 1개의 id로 랜덤 선택 (중복 방지)
do {
questionTargetId = random.nextInt(nodeDtoList.size());
} while (usedNodeIndices.contains(questionTargetId));

NodeDto targetNode = nodeDtoList.get(questionTargetId);
usedNodeIndices.add(questionTargetId); // 사용한 Id 추가

// 정답
String answer = targetNode.getLabel();

// 정답 포함 5개 보기 생성
Set<String> options = new HashSet<>();
options.add(answer); // 정답 보기 추가

while (options.size() < 5) { // 랜덤 보기 추가
int randomIndex = random.nextInt(nodeDtoList.size());
String option = nodeDtoList.get(randomIndex).getLabel();
options.add(option);
}

// 보기 리스트 랜덤 배치
List<String> shuffledOptions = new ArrayList<>(options);
Collections.shuffle(shuffledOptions);

// 문제 하나 생성
ConnectQuizDto.ConnectQuiz quiz = ConnectQuizDto.ConnectQuiz.builder()
.questionTargetId(String.valueOf(questionTargetId))
.shuffledOptions(shuffledOptions)
.answer(answer)
.build();

// 문제 리스트에 추가
quizList.add(quiz);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.going.server.domain.quiz.generate;

import com.going.server.domain.graph.entity.Graph;
import com.going.server.domain.graph.entity.GraphNode;
import com.going.server.domain.quiz.dto.ListenUpQuizDto;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class ListenUpQuizGenerator implements QuizGenerator<ListenUpQuizDto> {

@Override
public ListenUpQuizDto generate(Graph graph) {
Random random = new Random();
List<ListenUpQuizDto.ListenUpQuiz> quizzes = new ArrayList<>();
Set<String> usedSentences = new HashSet<>();
List<String> options = new ArrayList<>();

// 1. 그래프 노드에서 문장 추출
for (GraphNode node : graph.getNodes()) {
if (node.getIncludeSentence() == null || node.getIncludeSentence().isBlank()) continue;

// "." 으로 문장 나누기
String[] splitSentences = node.getIncludeSentence().split("\\.");

for (String rawSentence : splitSentences) {
String sentence = rawSentence.trim();
if (sentence.isBlank()) continue; // 공백은 스킵
if (usedSentences.contains(sentence)) continue;

String[] words = sentence.split("\\s+");
if (words.length < 5) continue; // 5단어 미만은 스킵

options.add(sentence);
}
}

// 2. 단어 수 기준 정렬 (5단어에 가까운 순서)
options.sort(Comparator.comparingInt(
s -> Math.abs(s.trim().split("\\s+").length - 5)
));

int count = 0;

for (String sentence : options) {
if (count >= 3) break;

String[] words = sentence.split("\\s+");

List<String> answer = new ArrayList<>();

if (words.length == 5) { // 5단어면 그대로
answer = Arrays.asList(words);
} else {
// 6단어 이상이면 랜덤하게 5개로 압축
int mergeCount = words.length - 5; // 합쳐야 할 횟수
List<String> wordList = new ArrayList<>(Arrays.asList(words));

for (int i = 0; i < mergeCount; i++) {
int mergeIdx = random.nextInt(wordList.size() - 1); // 마지막 단어는 제외
String merged = wordList.get(mergeIdx) + " " + wordList.get(mergeIdx + 1);
wordList.set(mergeIdx, merged);
wordList.remove(mergeIdx + 1);
}
answer = wordList;
}

if (answer.size() != 5) continue; // 안전망

List<String> shuffled = new ArrayList<>(answer);
Collections.shuffle(shuffled, random);

// 퀴즈 생성
ListenUpQuizDto.ListenUpQuiz quiz = ListenUpQuizDto.ListenUpQuiz.builder()
.answer(answer)
.shuffled(shuffled)
.description(sentence) // 이 문장 전체가 TTS로 읽힐 문장
.build();

quizzes.add(quiz);
usedSentences.add(sentence);
count++;
}

// 최종 퀴즈 DTO에 담아서 번환
return ListenUpQuizDto.builder()
.quizzes(quizzes)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.going.server.domain.quiz.generate;

import com.going.server.domain.graph.entity.Graph;
import com.going.server.domain.quiz.dto.PictureQuizDto;
import org.springframework.stereotype.Component;

@Component
public class PictureQuizGenerator implements QuizGenerator<PictureQuizDto> {

@Override
public PictureQuizDto generate(Graph graph) {
// TODO: picture 퀴즈 생성 로직
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.going.server.domain.quiz.generate;

import com.going.server.domain.graph.entity.Graph;

public interface QuizGenerator<T> {
T generate(Graph graph);
}
129 changes: 18 additions & 111 deletions src/main/java/com/going/server/domain/quiz/service/QuizServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.going.server.domain.quiz.service;

import com.going.server.domain.graph.dto.EdgeDto;
import com.going.server.domain.graph.dto.KnowledgeGraphDto;
import com.going.server.domain.graph.dto.NodeDto;
import com.going.server.domain.graph.entity.Graph;
import com.going.server.domain.graph.entity.GraphEdge;
import com.going.server.domain.graph.entity.GraphNode;
import com.going.server.domain.graph.exception.GraphNotFoundException;
import com.going.server.domain.graph.repository.GraphRepository;
import com.going.server.domain.quiz.dto.ConnectQuizDto;
import com.going.server.domain.quiz.dto.ListenUpQuizDto;
import com.going.server.domain.quiz.dto.PictureQuizDto;
import com.going.server.domain.quiz.dto.QuizCreateResponseDto;
import com.going.server.domain.quiz.generate.ConnectQuizGenerator;
import com.going.server.domain.quiz.generate.ListenUpQuizGenerator;
import com.going.server.domain.quiz.generate.PictureQuizGenerator;
import com.going.server.domain.quiz.generate.QuizGenerator;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

Expand All @@ -17,124 +24,24 @@
@AllArgsConstructor
public class QuizServiceImpl implements QuizService{
private final GraphRepository graphRepository;
private final ListenUpQuizGenerator listenUpQuizGenerator;
private final ConnectQuizGenerator connectQuizGenerator;
private final PictureQuizGenerator pictureQuizGenerator;

@Override
public QuizCreateResponseDto quizCreate(String graphIdStr, String mode) {
Long graphId = Long.valueOf(graphIdStr);

// 404 : 지식그래프 찾을 수 없음
Graph graph = graphRepository.findById(graphId)
.orElseThrow(GraphNotFoundException::new);
Graph graph = graphRepository.getByGraph(graphId);

Object quizDto = null;

switch (mode) {
case "listenUp":
quizDto = listenUpQuizCreate(graph);
break;
case "connect":
quizDto = connectQuizCreate(graph);
break;
case "picture":
quizDto = pictureQuizCreate(graph);
break;
default:
// TODO : 퀴즈 모드 관련 예외처리 필요
}
Object quizDto = switch (mode) {
case "listenUp" -> listenUpQuizGenerator.generate(graph);
case "connect" -> connectQuizGenerator.generate(graph);
case "picture" -> pictureQuizGenerator.generate(graph);
default -> throw new IllegalArgumentException("지원하지 않는 모드입니다: " + mode);
};

return new QuizCreateResponseDto<>(graphIdStr, mode, quizDto);
}

// listenUp 퀴즈 생성 메서드
private ListenUpQuizDto listenUpQuizCreate(Graph graph) {
Random random = new Random();
List<ListenUpQuizDto.ListenUpQuiz> quizzes = new ArrayList<>();
Set<String> usedSentences = new HashSet<>();
List<String> candidates = new ArrayList<>();

// 1. 그래프 노드에서 문장 추출
for (GraphNode node : graph.getNodes()) {
if (node.getIncludeSentence() == null || node.getIncludeSentence().isBlank()) continue;

// "." 으로 문장 나누기
String[] splitSentences = node.getIncludeSentence().split("\\.");

for (String rawSentence : splitSentences) {
String sentence = rawSentence.trim();
if (sentence.isBlank()) continue; // 공백은 스킵
if (usedSentences.contains(sentence)) continue;

String[] words = sentence.split("\\s+");
if (words.length < 5) continue; // 5단어 미만은 스킵

candidates.add(sentence);
}
}

// 2. 단어 수 기준 정렬 (5단어에 가까운 순서)
candidates.sort(Comparator.comparingInt(
s -> Math.abs(s.trim().split("\\s+").length - 5)
));

int count = 0;

for (String sentence : candidates) {
if (count >= 3) break;

String[] words = sentence.split("\\s+");

List<String> answer = new ArrayList<>();

if (words.length == 5) { // 5단어면 그대로
answer = Arrays.asList(words);
} else {
// 6단어 이상이면 랜덤하게 5개로 압축
int mergeCount = words.length - 5; // 합쳐야 할 횟수
List<String> wordList = new ArrayList<>(Arrays.asList(words));

for (int i = 0; i < mergeCount; i++) {
int mergeIdx = random.nextInt(wordList.size() - 1); // 마지막 단어는 제외
String merged = wordList.get(mergeIdx) + " " + wordList.get(mergeIdx + 1);
wordList.set(mergeIdx, merged);
wordList.remove(mergeIdx + 1);
}
answer = wordList;
}

if (answer.size() != 5) continue; // 안전망

List<String> shuffled = new ArrayList<>(answer);
Collections.shuffle(shuffled, random);

// 퀴즈 생성
ListenUpQuizDto.ListenUpQuiz quiz = ListenUpQuizDto.ListenUpQuiz.builder()
.answer(answer)
.shuffled(shuffled)
.description(sentence) // 이 문장 전체가 TTS로 읽힐 문장
.build();

quizzes.add(quiz);
usedSentences.add(sentence);
count++;
}

// 최종 퀴즈 DTO에 담아서 번환
return ListenUpQuizDto.builder()
.quizzes(quizzes)
.build();
}

// connect 퀴즈 생성 메서드
private ConnectQuizDto connectQuizCreate(Graph graph) {
// TODO : connect 퀴즈 생성 로직 작성
return ConnectQuizDto.builder()
.build();
}

// picture 퀴즈 생성 메서드
private PictureQuizDto pictureQuizCreate(Graph graph) {
// TODO : picture 퀴즈 생성 로직 작성
return PictureQuizDto.builder()
.build();
}
}