diff --git a/.gitignore b/.gitignore index e9574a0..ba43b7a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,7 @@ out/ .vscode/ .env -*secret.yaml \ No newline at end of file +*secret.yaml + +### Secret Config ### +src/main/resources/application-secret.yml diff --git a/build.gradle b/build.gradle index 9394c53..8484c3d 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-docker-compose' diff --git a/compose.yaml b/compose.yaml index 4d2047e..49ee895 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,4 +7,9 @@ services: - 'MYSQL_ROOT_PASSWORD=verysecret' - 'MYSQL_USER=myuser' ports: - - '3306' + - '33006:3306' + + redis: + image: 'redis:latest' + ports: + - '6379:6379' \ No newline at end of file diff --git a/k8s/app.yaml b/k8s/app.yaml index f28a1aa..5dc63b5 100644 --- a/k8s/app.yaml +++ b/k8s/app.yaml @@ -27,6 +27,10 @@ spec: env: - name: SPRING_PROFILES_ACTIVE value: "prod" + - name: SPRING_REDIS_HOST + value: "redis.redis.svc.cluster.local" + - name: SPRING_REDIS_PORT + value: "6379" envFrom: - secretRef: name: db-secret diff --git a/k8s/redis.yaml b/k8s/redis.yaml new file mode 100644 index 0000000..d9096d6 --- /dev/null +++ b/k8s/redis.yaml @@ -0,0 +1,84 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: redis +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-config + namespace: redis +data: + redis.conf: | + bind 0.0.0.0 + port 6379 + protected-mode yes + appendonly yes + appendfsync everysec + maxmemory 512mb + maxmemory-policy volatile-lru +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-data + namespace: redis +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + storageClassName: local-path +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:7.2 + command: ["redis-server", "/usr/local/etc/redis/redis.conf"] + ports: + - name: redis + containerPort: 6379 + volumeMounts: + - name: redis-config + mountPath: /usr/local/etc/redis + - name: redis-data + mountPath: /data + volumes: + - name: redis-config + configMap: + name: redis-config + items: + - key: redis.conf + path: redis.conf + - name: redis-data + persistentVolumeClaim: + claimName: redis-data +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: redis +spec: + type: ClusterIP + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 + protocol: TCP diff --git a/src/main/java/com/example/skillboost/SkillBoostApplication.java b/src/main/java/com/example/skillboost/SkillBoostApplication.java index 63875ab..d65240e 100644 --- a/src/main/java/com/example/skillboost/SkillBoostApplication.java +++ b/src/main/java/com/example/skillboost/SkillBoostApplication.java @@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; + @SpringBootApplication public class SkillBoostApplication { @@ -10,4 +11,4 @@ public static void main(String[] args) { SpringApplication.run(SkillBoostApplication.class, args); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codereview/controller/CodeReviewController.java b/src/main/java/com/example/skillboost/codereview/controller/CodeReviewController.java new file mode 100644 index 0000000..0c01fe6 --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/controller/CodeReviewController.java @@ -0,0 +1,26 @@ +// src/main/java/com/example/skillboost/codereview/controller/CodeReviewController.java +package com.example.skillboost.codereview.controller; + +import com.example.skillboost.codereview.dto.CodeReviewRequest; +import com.example.skillboost.codereview.dto.CodeReviewResponse; +import com.example.skillboost.codereview.service.CodeReviewService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +@CrossOrigin(origins = "http://localhost:3000") +@RestController +@RequestMapping("/api/review") +@RequiredArgsConstructor +public class CodeReviewController { + + private final CodeReviewService codeReviewService; + + @PostMapping( + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE + ) + public CodeReviewResponse review(@RequestBody CodeReviewRequest request) { + return codeReviewService.review(request); + } +} diff --git a/src/main/java/com/example/skillboost/codereview/dto/CodeReviewRequest.java b/src/main/java/com/example/skillboost/codereview/dto/CodeReviewRequest.java new file mode 100644 index 0000000..e413c1d --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/dto/CodeReviewRequest.java @@ -0,0 +1,59 @@ +// src/main/java/com/example/skillboost/codereview/dto/CodeReviewRequest.java +package com.example.skillboost.codereview.dto; + +public class CodeReviewRequest { + + private String code; + private String comment; + + // ๐Ÿ”น ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๋ฆฌ๋ทฐ์šฉ ํ•„๋“œ + private String repoUrl; // ์˜ˆ: https://github.com/Junseung-Ock/java-calculator-7 + private String branch; // ๊ธฐ๋ณธ๊ฐ’: main + + public CodeReviewRequest() { + } + + public CodeReviewRequest(String code, String comment) { + this.code = code; + this.comment = comment; + } + + public CodeReviewRequest(String code, String comment, String repoUrl, String branch) { + this.code = code; + this.comment = comment; + this.repoUrl = repoUrl; + this.branch = branch; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getRepoUrl() { + return repoUrl; + } + + public void setRepoUrl(String repoUrl) { + this.repoUrl = repoUrl; + } + + public String getBranch() { + return branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } +} diff --git a/src/main/java/com/example/skillboost/codereview/dto/CodeReviewResponse.java b/src/main/java/com/example/skillboost/codereview/dto/CodeReviewResponse.java new file mode 100644 index 0000000..6fb48fd --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/dto/CodeReviewResponse.java @@ -0,0 +1,23 @@ +package com.example.skillboost.codereview.dto; + +import java.util.ArrayList; +import java.util.List; + +public class CodeReviewResponse { + + private String review; + private List questions = new ArrayList<>(); + + public CodeReviewResponse() {} + + public CodeReviewResponse(String review, List questions) { + this.review = review; + this.questions = questions; + } + + public String getReview() { return review; } + public void setReview(String review) { this.review = review; } + + public List getQuestions() { return questions; } + public void setQuestions(List questions) { this.questions = questions; } +} diff --git a/src/main/java/com/example/skillboost/codereview/github/GithubController.java b/src/main/java/com/example/skillboost/codereview/github/GithubController.java new file mode 100644 index 0000000..0ccb95e --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/github/GithubController.java @@ -0,0 +1,4 @@ +package com.example.skillboost.codereview.github; + +public class GithubController { +} diff --git a/src/main/java/com/example/skillboost/codereview/github/GithubFile.java b/src/main/java/com/example/skillboost/codereview/github/GithubFile.java new file mode 100644 index 0000000..fa2e5b7 --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/github/GithubFile.java @@ -0,0 +1,18 @@ +// src/main/java/com/example/skillboost/codereview/github/GithubFile.java +package com.example.skillboost.codereview.github; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class GithubFile { + + private String path; + private String content; + + public GithubFile(String path, String content) { + this.path = path; + this.content = content; + } +} diff --git a/src/main/java/com/example/skillboost/codereview/github/GithubService.java b/src/main/java/com/example/skillboost/codereview/github/GithubService.java new file mode 100644 index 0000000..0b81c55 --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/github/GithubService.java @@ -0,0 +1,114 @@ +// src/main/java/com/example/skillboost/codereview/github/GithubService.java +package com.example.skillboost.codereview.github; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.*; + +@Slf4j +@Service +public class GithubService { + + private final RestTemplate restTemplate = new RestTemplate(); + + @Value("${github.token:}") + private String githubToken; + + private static final List TEXT_EXTENSIONS = List.of( + ".java", ".kt", ".xml", ".json", ".yml", ".yaml", + ".md", ".gradle", ".gitignore", ".txt", ".properties", ".csv" + ); + + public List fetchRepoCode(String repoUrl, String branch) { + if (repoUrl == null || !repoUrl.contains("github.com/")) { + throw new IllegalArgumentException("์ž˜๋ชป๋œ GitHub URL ํ˜•์‹์ž…๋‹ˆ๋‹ค."); + } + + try { + String[] parts = repoUrl.replace("https://github.com/", "") + .replace("http://github.com/", "") + .split("/"); + if (parts.length < 2) throw new IllegalArgumentException("์ž˜๋ชป๋œ GitHub URL ํ˜•์‹์ž…๋‹ˆ๋‹ค."); + + String owner = parts[0]; + String repo = parts[1]; + + String treeUrl = String.format( + "https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1", + owner, repo, branch + ); + + log.info("[GithubService] tree ํ˜ธ์ถœ: {}", treeUrl); + + HttpHeaders headers = new HttpHeaders(); + if (githubToken != null && !githubToken.isEmpty()) { + headers.setBearerAuth(githubToken); + } + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity resp = restTemplate.exchange( + treeUrl, HttpMethod.GET, entity, Map.class + ); + + Map body = resp.getBody(); + if (body == null || !body.containsKey("tree")) { + return Collections.emptyList(); + } + + List> tree = (List>) body.get("tree"); + List files = new ArrayList<>(); + + for (Map file : tree) { + if (!"blob".equals(file.get("type"))) continue; + + String path = (String) file.get("path"); + if (!isTextFile(path)) continue; + + String rawUrl = String.format( + "https://raw.githubusercontent.com/%s/%s/%s/%s", + owner, repo, branch, path + ); + + String content = fetchFileContent(rawUrl); + files.add(new GithubFile(path, content)); + } + + log.info("[GithubService] {} ๊ฐœ ํŒŒ์ผ ๋กœ๋“œ ์™„๋ฃŒ", files.size()); + return files; + + } catch (Exception e) { + log.error("[GithubService] ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ๋กœ๋“œ ์‹คํŒจ: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + private String fetchFileContent(String rawUrl) { + try { + HttpHeaders headers = new HttpHeaders(); + if (githubToken != null && !githubToken.isEmpty()) { + headers.setBearerAuth(githubToken); + } + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity resp = restTemplate.exchange( + rawUrl, HttpMethod.GET, entity, String.class + ); + return resp.getBody() != null ? resp.getBody() : ""; + } catch (Exception e) { + log.warn("[GithubService] ํŒŒ์ผ ์ฝ๊ธฐ ์‹คํŒจ: {} ({})", rawUrl, e.getMessage()); + return ""; + } + } + + private boolean isTextFile(String path) { + String lower = path.toLowerCase(); + for (String ext : TEXT_EXTENSIONS) { + if (lower.endsWith(ext)) return true; + } + return false; + } +} diff --git a/src/main/java/com/example/skillboost/codereview/llm/GeminiCodeReviewClient.java b/src/main/java/com/example/skillboost/codereview/llm/GeminiCodeReviewClient.java new file mode 100644 index 0000000..a3e8145 --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/llm/GeminiCodeReviewClient.java @@ -0,0 +1,245 @@ +// src/main/java/com/example/skillboost/codereview/llm/GeminiCodeReviewClient.java +package com.example.skillboost.codereview.llm; + +import com.example.skillboost.codereview.dto.CodeReviewResponse; +import com.example.skillboost.codereview.github.GithubFile; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.*; + +@Component +public class GeminiCodeReviewClient { + + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + private final String apiKey; + private final String model; + + public GeminiCodeReviewClient( + @Value("${gemini.api.key}") String apiKey, + @Value("${gemini.model}") String model + ) { + this.apiKey = apiKey; + this.model = model; + this.restTemplate = new RestTemplate(); + this.objectMapper = new ObjectMapper(); + } + + // ๐Ÿ”น ์ฝ”๋“œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ์กด ๋ชจ๋“œ (ํ˜ธํ™˜์šฉ) + public CodeReviewResponse requestReview(String code, String comment) { + return requestReview(code, comment, null); + } + + // ๐Ÿ”น ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ์ปจํ…์ŠคํŠธ๊นŒ์ง€ ํ•จ๊ป˜ ๋„˜๊ธฐ๋Š” ํ™•์žฅ ๋ฒ„์ „ + public CodeReviewResponse requestReview(String code, String comment, List repoContext) { + try { + String url = "https://generativelanguage.googleapis.com/v1beta/models/" + + model + ":generateContent?key=" + apiKey; + + String prompt = buildPrompt(code, comment, repoContext); + + Map textPart = new HashMap<>(); + textPart.put("text", prompt); + + Map content = new HashMap<>(); + content.put("parts", Collections.singletonList(textPart)); + + Map requestBody = new HashMap<>(); + requestBody.put("contents", Collections.singletonList(content)); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); + String body = response.getBody(); + + if (!response.getStatusCode().is2xxSuccessful() || body == null) { + CodeReviewResponse fallback = new CodeReviewResponse(); + fallback.setReview("AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์š”์ฒญ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒํƒœ์ฝ”๋“œ: " + response.getStatusCode()); + fallback.setQuestions(Collections.emptyList()); + return fallback; + } + + return parseGeminiResponse(body); + + } catch (Exception e) { + CodeReviewResponse fallback = new CodeReviewResponse(); + fallback.setReview("AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: " + e.getMessage()); + fallback.setQuestions(Collections.emptyList()); + return fallback; + } + } + + /** + * ์ฝ”๋“œ + (์„ ํƒ) GitHub ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ์ปจํ…์ŠคํŠธ(README, ํŒŒ์ผ๊ตฌ์กฐ, ์ผ๋ถ€ ์ฝ”๋“œ)๋ฅผ ํฌํ•จํ•œ ํ”„๋กฌํ”„ํŠธ + */ + private String buildPrompt(String code, String comment, List repoContext) { + String userRequirement = (comment != null && !comment.trim().isEmpty()) + ? comment.trim() + : "ํŠน๋ณ„ํ•œ ์ถ”๊ฐ€ ์š”๊ตฌ์‚ฌํ•ญ์€ ์—†์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ๋งŒ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋ฆฌ๋ทฐํ•ด์ค˜."; + + StringBuilder sb = new StringBuilder(); + + // 1) ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ์ „์ฒด ๋งฅ๋ฝ + if (repoContext != null && !repoContext.isEmpty()) { + sb.append("์ด ์ฝ”๋“œ๋Š” GitHub ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ์ „์ฒด ๋งฅ๋ฝ ์•ˆ์— ์žˆ๋Š” ์ผ๋ถ€ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.\n") + .append("๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ์˜ README์™€ ํŒŒ์ผ ๊ตฌ์กฐ, ์ฃผ์š” ์ฝ”๋“œ ํŒŒ์ผ์„ ์ฐธ๊ณ ํ•ด์„œ '์š”๊ตฌ์‚ฌํ•ญ์„ ๋งŒ์กฑํ•˜๋Š”์ง€'์™€ '์•„ํ‚คํ…์ฒ˜ ์ ์ ˆ์„ฑ'๊นŒ์ง€ ํ•จ๊ป˜ ๋ฆฌ๋ทฐํ•ด ์ฃผ์„ธ์š”.\n\n"); + + // README ์ฐพ๊ธฐ + GithubFile readme = repoContext.stream() + .filter(f -> f.getPath().equalsIgnoreCase("README.md") + || f.getPath().toLowerCase().endsWith("/readme.md")) + .findFirst() + .orElse(null); + + if (readme != null && readme.getContent() != null) { + String readmeContent = readme.getContent(); + if (readmeContent.length() > 2000) { + readmeContent = readmeContent.substring(0, 2000) + "\n... (์ƒ๋žต)"; + } + + sb.append("=== README (์š”๊ตฌ์‚ฌํ•ญ ๊ธฐ์ค€) ===\n"); + sb.append(readmeContent).append("\n\n"); + } + + // ํŒŒ์ผ ๋ชฉ๋ก (์ตœ๋Œ€ 40๊ฐœ) + sb.append("=== ํ”„๋กœ์ ํŠธ ํŒŒ์ผ ๊ตฌ์กฐ (์ผ๋ถ€) ===\n"); + repoContext.stream() + .limit(40) + .forEach(f -> sb.append("- ").append(f.getPath()).append("\n")); + if (repoContext.size() > 40) { + sb.append("... ์™ธ ").append(repoContext.size() - 40).append("๊ฐœ ํŒŒ์ผ ๋” ์žˆ์Œ\n"); + } + sb.append("\n"); + + // ์ฃผ์š” ์ฝ”๋“œ ์ƒ˜ํ”Œ (java ์œ„์ฃผ ์ตœ๋Œ€ 5๊ฐœ) + sb.append("=== ์ฃผ์š” ์ฝ”๋“œ ์ƒ˜ํ”Œ (์ผ๋ถ€) ===\n"); + repoContext.stream() + .filter(f -> f.getPath().endsWith(".java")) + .limit(5) + .forEach(f -> { + sb.append("#### ").append(f.getPath()).append("\n"); + String c = f.getContent(); + if (c != null && c.length() > 1200) { + c = c.substring(0, 1200) + "\n... (์ƒ๋žต)"; + } + sb.append(c == null ? "" : c).append("\n\n"); + }); + + sb.append("์œ„ ์ •๋ณด๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ, ์•„๋ž˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ฝ”๋“œ๊ฐ€ ์ด ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ/README ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ์ž˜ ๋งž๋Š”์ง€ ๊ฒ€ํ† ํ•ด ์ฃผ์„ธ์š”.\n\n"); + } + + // 2) ์—ฌ๊ธฐ๋ถ€ํ„ฐ๋Š” JSON ํ˜•์‹ / ์ถœ๋ ฅ ๊ทœ์น™ ์•ˆ๋‚ด (๊ธฐ์กด ๋กœ์ง ์œ ์ง€) + sb.append(""" + ๋„ˆ๋Š” ์ˆ™๋ จ๋œ ์‹œ๋‹ˆ์–ด ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž์ด์ž ์ฝ”๋“œ ๋ฆฌ๋ทฐ์–ด์•ผ. + ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด์„œ ๋ฐ˜๋“œ์‹œ **JSON ํ˜•์‹ ํ•˜๋‚˜๋งŒ** ์ถœ๋ ฅํ•ด. + + โš ๏ธ ๋ชจ๋“  ์ถœ๋ ฅ์€ ๋ฐ˜๋“œ์‹œ ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ด. + ๋งˆํฌ๋‹ค์šด ๊ธˆ์ง€(**, ```, # ๋“ฑ) + JSON ์™ธ ํ…์ŠคํŠธ ์ถœ๋ ฅ ๊ธˆ์ง€. + + ๐Ÿ”’ ์ถœ๋ ฅ ํ˜•์‹ ๊ทœ์น™ + - review ํ•ญ๋ชฉ์€: + - ๋ชจ๋“  ์ค„์„ 'โ–ก ' ๋กœ ์‹œ์ž‘ + - ํ•œ ์ค„์€ ํ•ต์‹ฌ ํ•œ ๋ฌธ์žฅ + - ํ•ญ๋ชฉ ์‚ฌ์ด์—๋Š” ๋นˆ ์ค„(\\n\\n) ์žˆ์–ด์•ผ ํ•จ + + - questions ํ•ญ๋ชฉ์€: + - ๋ฐฐ์—ด ํ˜•ํƒœ + - ๊ฐ ์งˆ๋ฌธ์€ ํ•œ๊ตญ์–ด ํ•œ ๋ฌธ์žฅ + - ๋ฒˆํ˜ธ(1. 2.)๋Š” ๋„ฃ์ง€ ๋ง ๊ฒƒ + + JSON ์˜ˆ์‹œ: + + { + "review": "โ–ก ํ•ต์‹ฌ ํ”ผ๋“œ๋ฐฑ์ž…๋‹ˆ๋‹ค.\\n\\nโ–ก ๋˜ ๋‹ค๋ฅธ ํ•ต์‹ฌ ํ”ผ๋“œ๋ฐฑ์ž…๋‹ˆ๋‹ค.", + "questions": [ + "์ด ์ฝ”๋“œ์—์„œ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์€ ๋ฌด์—‡์ธ๊ฐ€์š”?", + "์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด ์–ด๋–ค ์ผ€์ด์Šค๋ฅผ ๊ณ ๋ คํ•˜๊ฒ ์Šต๋‹ˆ๊นŒ?" + ] + } + + ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ ์š”๊ตฌ์‚ฌํ•ญ: + """).append("\n") + .append(userRequirement).append("\n\n") + .append("๋ฆฌ๋ทฐํ•  ์ฝ”๋“œ:\n") + .append(code); + + return sb.toString(); + } + + private CodeReviewResponse parseGeminiResponse(String body) throws Exception { + JsonNode root = objectMapper.readTree(body); + + JsonNode candidates = root.path("candidates"); + if (!candidates.isArray() || candidates.isEmpty()) { + CodeReviewResponse resp = new CodeReviewResponse(); + resp.setReview("AI ์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค."); + resp.setQuestions(Collections.emptyList()); + return resp; + } + + JsonNode textNode = candidates.get(0) + .path("content") + .path("parts") + .get(0) + .path("text"); + + String rawText = textNode.asText(""); + if (rawText.isEmpty()) { + CodeReviewResponse resp = new CodeReviewResponse(); + resp.setReview("AI ์‘๋‹ต ํ…์ŠคํŠธ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."); + resp.setQuestions(Collections.emptyList()); + return resp; + } + + String cleaned = stripCodeFence(rawText); + + try { + JsonNode json = objectMapper.readTree(cleaned); + + String review = json.path("review").asText(""); + if (review.isEmpty()) review = cleaned; + + List questions = new ArrayList<>(); + JsonNode qNode = json.path("questions"); + if (qNode.isArray()) { + for (JsonNode q : qNode) questions.add(q.asText()); + } + + CodeReviewResponse resp = new CodeReviewResponse(); + resp.setReview(review); + resp.setQuestions(questions); + return resp; + + } catch (Exception e) { + CodeReviewResponse resp = new CodeReviewResponse(); + resp.setReview(cleaned); + resp.setQuestions(Collections.emptyList()); + return resp; + } + } + + private String stripCodeFence(String text) { + if (text == null) return ""; + String trimmed = text.trim(); + + if (!trimmed.startsWith("```")) return trimmed; + + int firstNewline = trimmed.indexOf('\n'); + int lastFence = trimmed.lastIndexOf("```"); + + if (firstNewline != -1 && lastFence != -1 && lastFence > firstNewline) { + return trimmed.substring(firstNewline + 1, lastFence).trim(); + } + + return trimmed; + } +} diff --git a/src/main/java/com/example/skillboost/codereview/service/CodeReviewService.java b/src/main/java/com/example/skillboost/codereview/service/CodeReviewService.java new file mode 100644 index 0000000..c8eb152 --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/service/CodeReviewService.java @@ -0,0 +1,9 @@ +package com.example.skillboost.codereview.service; + +import com.example.skillboost.codereview.dto.CodeReviewRequest; +import com.example.skillboost.codereview.dto.CodeReviewResponse; + +public interface CodeReviewService { + + CodeReviewResponse review(CodeReviewRequest request); +} diff --git a/src/main/java/com/example/skillboost/codereview/service/CodeReviewServiceImpl.java b/src/main/java/com/example/skillboost/codereview/service/CodeReviewServiceImpl.java new file mode 100644 index 0000000..1edc828 --- /dev/null +++ b/src/main/java/com/example/skillboost/codereview/service/CodeReviewServiceImpl.java @@ -0,0 +1,44 @@ +// src/main/java/com/example/skillboost/codereview/service/CodeReviewServiceImpl.java +package com.example.skillboost.codereview.service; + +import com.example.skillboost.codereview.dto.CodeReviewRequest; +import com.example.skillboost.codereview.dto.CodeReviewResponse; +import com.example.skillboost.codereview.github.GithubFile; +import com.example.skillboost.codereview.github.GithubService; +import com.example.skillboost.codereview.llm.GeminiCodeReviewClient; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CodeReviewServiceImpl implements CodeReviewService { + + private final GeminiCodeReviewClient geminiCodeReviewClient; + private final GithubService githubService; + + @Override + public CodeReviewResponse review(CodeReviewRequest request) { + if (request == null || !StringUtils.hasText(request.getCode())) { + throw new IllegalArgumentException("์ฝ”๋“œ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค."); + } + + String code = request.getCode(); + String comment = request.getComment(); + String repoUrl = request.getRepoUrl(); + String branch = StringUtils.hasText(request.getBranch()) ? request.getBranch() : "main"; + + List repoContext = Collections.emptyList(); + + // ๐Ÿ”น repoUrl ์ด ์žˆ์œผ๋ฉด GitHub ๋ ˆํฌ ์ „์ฒด ์ฝ์–ด์˜ค๊ธฐ + if (StringUtils.hasText(repoUrl)) { + repoContext = githubService.fetchRepoCode(repoUrl, branch); + } + + // ๐Ÿ”น ์ฝ”๋“œ + (์žˆ๋‹ค๋ฉด) ๋ ˆํฌ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ Gemini์— ๋ฆฌ๋ทฐ ์š”์ฒญ + return geminiCodeReviewClient.requestReview(code, comment, repoContext); + } +} diff --git a/src/main/java/com/example/skillboost/codingtest/controller/CodingTestController.java b/src/main/java/com/example/skillboost/codingtest/controller/CodingTestController.java new file mode 100644 index 0000000..fdaaa0d --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/controller/CodingTestController.java @@ -0,0 +1,54 @@ +package com.example.skillboost.codingtest.controller; + +import com.example.skillboost.codingtest.domain.CodingProblem; +import com.example.skillboost.codingtest.domain.Difficulty; +import com.example.skillboost.codingtest.repository.CodingProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Random; + +@RestController +@RequestMapping("/api/coding/problems") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +public class CodingTestController { + + private final CodingProblemRepository problemRepository; + + @GetMapping("/random") + public ResponseEntity getRandomProblem(@RequestParam(required = false) String difficulty) { + List problems; + + // 1. ํ”„๋ก ํŠธ์—์„œ ๋‚œ์ด๋„๋ฅผ ์„ ํƒํ–ˆ๋Š”์ง€ ํ™•์ธ + if (difficulty != null && !difficulty.isEmpty()) { + try { + // "EASY" -> Difficulty.EASY ๋ณ€ํ™˜ + Difficulty diff = Difficulty.valueOf(difficulty.toUpperCase()); + // ํ•ด๋‹น ๋‚œ์ด๋„ ๋ฌธ์ œ๋“ค๋งŒ DB์—์„œ ๊ฐ€์ ธ์˜ด (์˜ˆ: 5๊ฐœ) + problems = problemRepository.findAllByDifficulty(diff); + } catch (IllegalArgumentException e) { + // ์ด์ƒํ•œ ๋‚œ์ด๋„๊ฐ€ ์˜ค๋ฉด ๊ทธ๋ƒฅ ์ „์ฒด ๋ฌธ์ œ ๊ฐ€์ ธ์˜ด + problems = problemRepository.findAll(); + } + } else { + // ๋‚œ์ด๋„ ์„ ํƒ ์•ˆ ํ–ˆ์œผ๋ฉด ์ „์ฒด ๋ฌธ์ œ(15๊ฐœ) ๊ฐ€์ ธ์˜ด + problems = problemRepository.findAll(); + } + + // 2. ๋ฌธ์ œ๊ฐ€ ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด 404 ์—๋Ÿฌ + if (problems.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + // 3. ๋ชฉ๋ก ์ค‘์—์„œ ๋žœ๋ค์œผ๋กœ ํ•˜๋‚˜ ๋ฝ‘๊ธฐ (ํ•ต์‹ฌ ๋กœ์ง) + Random random = new Random(); + int randomIndex = random.nextInt(problems.size()); // 0 ~ (๊ฐœ์ˆ˜-1) ์‚ฌ์ด ๋žœ๋ค ์ˆซ์ž + CodingProblem randomProblem = problems.get(randomIndex); + + // 4. ๋ฝ‘ํžŒ ๋ฌธ์ œ ๋ฐ˜ํ™˜ + return ResponseEntity.ok(randomProblem); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codingtest/controller/SubmissionController.java b/src/main/java/com/example/skillboost/codingtest/controller/SubmissionController.java new file mode 100644 index 0000000..d16b355 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/controller/SubmissionController.java @@ -0,0 +1,42 @@ +package com.example.skillboost.codingtest.controller; + +import com.example.skillboost.codingtest.dto.SubmissionRequestDto; +import com.example.skillboost.codingtest.dto.SubmissionResultDto; +import com.example.skillboost.codingtest.service.GradingService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/api/coding") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +public class SubmissionController { + + private final GradingService gradingService; + + /** + * ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ ์ œ์ถœ + ์ฑ„์  + * POST /api/coding/submissions + */ + @PostMapping("/submissions") + public ResponseEntity submit(@RequestBody SubmissionRequestDto request) { + log.info("์ฝ”๋”ฉํ…Œ์ŠคํŠธ ์ œ์ถœ ์š”์ฒญ: problemId={}, language={}, userId={}", + request.getProblemId(), request.getLanguage(), request.getUserId()); + + if (request.getCode() == null || request.getCode().isBlank()) { + return ResponseEntity.badRequest().body( + SubmissionResultDto.builder() + .status("ERROR") + .score(0) + .message("์ฝ”๋“œ๊ฐ€ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.") + .build() + ); + } + + SubmissionResultDto result = gradingService.grade(request); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/example/skillboost/codingtest/domain/CodingProblem.java b/src/main/java/com/example/skillboost/codingtest/domain/CodingProblem.java new file mode 100644 index 0000000..a5a7bce --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/domain/CodingProblem.java @@ -0,0 +1,34 @@ +package com.example.skillboost.codingtest.domain; + +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CodingProblem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + + @Column(columnDefinition = "TEXT") // ๊ธด ๋ฌธ์ œ ์„ค๋ช… ์ €์žฅ์šฉ + private String description; + + @Enumerated(EnumType.STRING) + private Difficulty difficulty; + + // ์˜ˆ: "array,implementation" + private String tags; + + @OneToMany(mappedBy = "problem", cascade = CascadeType.ALL, orphanRemoval = true) + private List testCases = new ArrayList<>(); +} diff --git a/src/main/java/com/example/skillboost/codingtest/domain/CodingSubmission.java b/src/main/java/com/example/skillboost/codingtest/domain/CodingSubmission.java new file mode 100644 index 0000000..7a07fef --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/domain/CodingSubmission.java @@ -0,0 +1,70 @@ +package com.example.skillboost.codingtest.domain; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "coding_submission") +public class CodingSubmission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // ๋ฌธ์ œ ID + @Column(nullable = false) + private Long problemId; + + // ์œ ์ € ID + @Column(nullable = false) + private Long userId; + + // ์‚ฌ์šฉ ์–ธ์–ด (python / java / cpp ...) + @Column(length = 20) + private String language; + + // ์ œ์ถœ ์ฝ”๋“œ + @Lob + @Column(nullable = false) + private String sourceCode; + + // "AC", "WA", "PARTIAL", "ERROR" ๋“ฑ + @Column(length = 20) + private String status; + + // 0 ~ 100 ์  + private Integer score; + + // ํ†ต๊ณผ/์ „์ฒด ํ…Œ์ŠคํŠธ ์ˆ˜ + private Integer passedCount; + private Integer totalCount; + + // ๊ฐ„๋‹จ ๋ฉ”์‹œ์ง€ + @Column(length = 255) + private String message; + + // ๐Ÿ”น AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ (TEXT) + @Lob + private String aiFeedback; + + // ๐Ÿ”ฅ ์˜ˆ์ƒ ๋ฉด์ ‘ ์งˆ๋ฌธ (JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ) + @Lob + private String interviewQuestionsJson; + + // ์ƒ์„ฑ ์‹œ๊ฐ + private LocalDateTime createdAt; + + @PrePersist + public void onCreate() { + if (createdAt == null) { + createdAt = LocalDateTime.now(); + } + } +} diff --git a/src/main/java/com/example/skillboost/codingtest/domain/CodingTestCase.java b/src/main/java/com/example/skillboost/codingtest/domain/CodingTestCase.java new file mode 100644 index 0000000..e81bb41 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/domain/CodingTestCase.java @@ -0,0 +1,30 @@ +package com.example.skillboost.codingtest.domain; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CodingTestCase { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // โ˜… ์—ฌ๊ธฐ ํ•„๋“œ ์ด๋ฆ„์ด CodingProblem์˜ mappedBy("problem") ์™€ ๊ฐ™์•„์•ผ ํ•จ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "problem_id") + private CodingProblem problem; + + @Column(columnDefinition = "TEXT") + private String inputData; + + @Column(columnDefinition = "TEXT") + private String expectedOutput; + + private boolean sample; // ์˜ˆ์ œ์šฉ ํ…Œ์ŠคํŠธ์ผ€์ด์Šค์ธ์ง€ ์—ฌ๋ถ€ +} diff --git a/src/main/java/com/example/skillboost/codingtest/domain/Difficulty.java b/src/main/java/com/example/skillboost/codingtest/domain/Difficulty.java new file mode 100644 index 0000000..9110aeb --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/domain/Difficulty.java @@ -0,0 +1,7 @@ +package com.example.skillboost.codingtest.domain; + +public enum Difficulty { + EASY, + MEDIUM, + HARD +} diff --git a/src/main/java/com/example/skillboost/codingtest/dto/ProblemDetailDto.java b/src/main/java/com/example/skillboost/codingtest/dto/ProblemDetailDto.java new file mode 100644 index 0000000..d00ecb9 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/dto/ProblemDetailDto.java @@ -0,0 +1,23 @@ +package com.example.skillboost.codingtest.dto; + +import lombok.Builder; +import lombok.Data; +import java.util.List; + +@Data +@Builder +public class ProblemDetailDto { + private Long id; + private String title; + private String description; + private String difficulty; + private String tags; + private List samples; + + @Data + @Builder + public static class SampleCase { + private String inputData; + private String expectedOutput; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codingtest/dto/ProblemSummaryDto.java b/src/main/java/com/example/skillboost/codingtest/dto/ProblemSummaryDto.java new file mode 100644 index 0000000..6445c1e --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/dto/ProblemSummaryDto.java @@ -0,0 +1,13 @@ +package com.example.skillboost.codingtest.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ProblemSummaryDto { + private Long id; + private String title; + private String difficulty; + private String tags; +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codingtest/dto/SubmissionRequestDto.java b/src/main/java/com/example/skillboost/codingtest/dto/SubmissionRequestDto.java new file mode 100644 index 0000000..aa43aaa --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/dto/SubmissionRequestDto.java @@ -0,0 +1,26 @@ +package com.example.skillboost.codingtest.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * ํ”„๋ก ํŠธ์—์„œ /api/coding/submissions ๋กœ ๋ณด๋‚ด๋Š” ์š”์ฒญ DTO + */ +@Data +@NoArgsConstructor +public class SubmissionRequestDto { + + // ๋ฌธ์ œ ID + private Long problemId; + + // ํ”„๋ก ํŠธ JSON ํ‚ค: "sourceCode" -> ์—ฌ๊ธฐ๋กœ ๋งคํ•‘ + @JsonProperty("sourceCode") + private String code; + + // ์‚ฌ์šฉ ์–ธ์–ด (python / java / cpp ...) + private String language; + + // ์œ ์ € ID (์—†์œผ๋ฉด 1๋กœ ๊ธฐ๋ณธ๊ฐ’ ์ค„ ์ˆ˜๋„ ์žˆ์Œ) + private Long userId; +} diff --git a/src/main/java/com/example/skillboost/codingtest/dto/SubmissionResultDto.java b/src/main/java/com/example/skillboost/codingtest/dto/SubmissionResultDto.java new file mode 100644 index 0000000..119af77 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/dto/SubmissionResultDto.java @@ -0,0 +1,38 @@ +package com.example.skillboost.codingtest.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SubmissionResultDto { + + private Long submissionId; + + // "AC"(์ •๋‹ต), "WA"(์˜ค๋‹ต) ๋“ฑ + private String status; + + // 0 ~ 100์  + private Integer score; + + // ํ†ต๊ณผํ•œ ํ…Œ์ŠคํŠธ์ผ€์ด์Šค ์ˆ˜ (์—†์œผ๋ฉด null ๊ฐ€๋Šฅ) + private Integer passedCount; + + // ์ „์ฒด ํ…Œ์ŠคํŠธ์ผ€์ด์Šค ์ˆ˜ (์—†์œผ๋ฉด null ๊ฐ€๋Šฅ) + private Integer totalCount; + + // "์ •๋‹ต์ž…๋‹ˆ๋‹ค! ๐ŸŽ‰" ๊ฐ™์€ ๊ฐ„๋‹จ ๋ฉ”์‹œ์ง€ + private String message; + + // ๐Ÿ”น AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ + private String aiFeedback; + + // ๐Ÿ”ฅ ์˜ˆ์ƒ ๋ฉด์ ‘ ์งˆ๋ฌธ ๋ฆฌ์ŠคํŠธ (ํ”„๋ก ํŠธ์—์„œ 1. 2. 3. ์œผ๋กœ ๋ฟŒ๋ ค์คŒ) + private List interviewQuestions; +} diff --git a/src/main/java/com/example/skillboost/codingtest/init/CodingTestDataInitializer.java b/src/main/java/com/example/skillboost/codingtest/init/CodingTestDataInitializer.java new file mode 100644 index 0000000..255149b --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/init/CodingTestDataInitializer.java @@ -0,0 +1,1189 @@ +package com.example.skillboost.codingtest.init; + +import com.example.skillboost.codingtest.domain.CodingProblem; +import com.example.skillboost.codingtest.domain.Difficulty; +import com.example.skillboost.codingtest.repository.CodingProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CodingTestDataInitializer implements CommandLineRunner { + + private final CodingProblemRepository problemRepository; + + @Override + public void run(String... args) { + // ========================= + // EASY ๋ฌธ์ œ๋“ค + // ========================= + createExamSupervisorProblem(); // ์‹œํ—˜ ๊ฐ๋… + createZoacDistancingProblem(); // ZOAC ๊ฑฐ๋ฆฌ๋‘๊ธฐ + createDjmaxRankingProblem(); // DJMAX ๋žญํ‚น + createMinHeapProblem(); // ์ตœ์†Œ ํž™ + createTriangleProblem(); // ์‚ผ๊ฐํ˜• ๋ถ„๋ฅ˜ + createMakeOneProblem(); // 1๋กœ ๋งŒ๋“ค๊ธฐ + createNumberCardProblem(); // ์ˆซ์ž ์นด๋“œ + + // ========================= + // MEDIUM ๋ฌธ์ œ๋“ค + // ========================= + createSnakeGameProblem(); // Dummy (๋ฑ€ ๊ฒŒ์ž„) + createDiceSimulationProblem(); // ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ + createTargetDistanceProblem(); // ๋ชฉํ‘œ์ง€์  ๊ฑฐ๋ฆฌ + createDfsBfsProblem(); // DFS์™€ BFS + createTripPlanningProblem(); // ์—ฌํ–‰ ๊ฐ€์ž + createChristmasGiftProblem(); // ํฌ๋ฆฌ์Šค๋งˆ์Šค ์„ ๋ฌผ + createCardBuyingProblem(); // ์นด๋“œ ๊ตฌ๋งคํ•˜๊ธฐ + createFireEscapeProblem(); // ๋ถˆ! + + // ========================= + // HARD ๋ฌธ์ œ๋“ค + // ========================= + createMarbleEscapeProblem(); // ๊ตฌ์Šฌ ํƒˆ์ถœ + createSharkCopyMagicProblem(); // ๋งˆ๋ฒ•์‚ฌ ์ƒ์–ด์™€ ๋ณต์ œ + createSimilarWordsProblem(); // ๋น„์Šทํ•œ ๋‹จ์–ด + createJewelThiefProblem(); // ๋ณด์„ ๋„๋‘‘ + createMarsExplorationProblem(); // ํ™”์„ฑ ํƒ์‚ฌ + createLectureTourProblem(); // ์ˆœํšŒ๊ฐ•์—ฐ + createLectureRoomAssignmentProblem(); // ๊ฐ•์˜์‹ค ๋ฐฐ์ • + createPopulationMovementProblem(); // ์ธ๊ตฌ ์ด๋™ + createPrisonBreakProblem(); // ํƒˆ์˜ฅ + } + + // ========================= + // EASY ๋ฌธ์ œ๋“ค + // ========================= + + // 1. ์‹œํ—˜ ๊ฐ๋… + private void createExamSupervisorProblem() { + if (problemRepository.existsByTitle("์‹œํ—˜ ๊ฐ๋…")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์ด N๊ฐœ์˜ ์‹œํ—˜์žฅ์ด ์žˆ๊ณ , ๊ฐ๊ฐ์˜ ์‹œํ—˜์žฅ๋งˆ๋‹ค ์‘์‹œ์ž๋“ค์ด ์žˆ๋‹ค. i๋ฒˆ ์‹œํ—˜์žฅ์— ์žˆ๋Š” ์‘์‹œ์ž์˜ ์ˆ˜๋Š” Ai๋ช…์ด๋‹ค. + + ๊ฐ๋…๊ด€์€ ์ด๊ฐ๋…๊ด€๊ณผ ๋ถ€๊ฐ๋…๊ด€์œผ๋กœ ๋‘ ์ข…๋ฅ˜๊ฐ€ ์žˆ๋‹ค. + ์ด๊ฐ๋…๊ด€์€ ํ•œ ์‹œํ—˜์žฅ์—์„œ ๊ฐ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ์‘์‹œ์ž์˜ ์ˆ˜๊ฐ€ B๋ช…์ด๊ณ , + ๋ถ€๊ฐ๋…๊ด€์€ ํ•œ ์‹œํ—˜์žฅ์—์„œ ๊ฐ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ์‘์‹œ์ž์˜ ์ˆ˜๊ฐ€ C๋ช…์ด๋‹ค. + + ๊ฐ๊ฐ์˜ ์‹œํ—˜์žฅ์— ์ด๊ฐ๋…๊ด€์€ ์˜ค์ง 1๋ช…๋งŒ ์žˆ์–ด์•ผ ํ•˜๊ณ , + ๋ถ€๊ฐ๋…๊ด€์€ ์—ฌ๋Ÿฌ ๋ช… ์žˆ์–ด๋„ ๋œ๋‹ค. + + ๊ฐ ์‹œํ—˜์žฅ๋งˆ๋‹ค ์‘์‹œ์ƒ๋“ค์„ ๋ชจ๋‘ ๊ฐ์‹œํ•ด์•ผ ํ•œ๋‹ค. + ์ด๋•Œ, ํ•„์š”ํ•œ ๊ฐ๋…๊ด€ ์ˆ˜์˜ ์ตœ์†Ÿ๊ฐ’์„ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์‹œํ—˜์žฅ์˜ ๊ฐœ์ˆ˜ N(1 โ‰ค N โ‰ค 1,000,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‘˜์งธ ์ค„์—๋Š” ๊ฐ ์‹œํ—˜์žฅ์— ์žˆ๋Š” ์‘์‹œ์ž์˜ ์ˆ˜ Ai (1 โ‰ค Ai โ‰ค 1,000,000)๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ์…‹์งธ ์ค„์—๋Š” B์™€ C๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค B, C โ‰ค 1,000,000) + + + [์ถœ๋ ฅ] + + ๊ฐ ์‹œํ—˜์žฅ๋งˆ๋‹ค ์‘์‹œ์ƒ์„ ๋ชจ๋‘ ๊ฐ๋…ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ฐ๋…๊ด€์˜ ์ตœ์†Œ ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์‹œํ—˜ ๊ฐ๋…") + .difficulty(Difficulty.EASY) + .description(description) + .tags("math,greedy") + .build(); + + problemRepository.save(problem); + } + + // 2. ZOAC ๊ฑฐ๋ฆฌ๋‘๊ธฐ + private void createZoacDistancingProblem() { + if (problemRepository.existsByTitle("ZOAC ๊ฑฐ๋ฆฌ๋‘๊ธฐ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + 2021๋…„ 12์›”, ๋„ค ๋ฒˆ์งธ๋กœ ๊ฐœ์ตœ๋œ ZOAC์˜ ์˜คํ”„๋‹์„ ๋งก์€ ์„ฑ์šฐ๋Š” + ์˜คํ”„๋ผ์ธ ๋Œ€ํšŒ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ๊ฐ•์˜์‹ค์„ ์˜ˆ์•ฝํ•˜๋ ค๊ณ  ํ•œ๋‹ค. + + ๊ฐ•์˜์‹ค์—์„œ ๋Œ€ํšŒ๋ฅผ ์น˜๋ฅด๋ ค๋ฉด ๊ฑฐ๋ฆฌ๋‘๊ธฐ ์ˆ˜์น™์„ ์ง€์ผœ์•ผ ํ•œ๋‹ค! + + ํ•œ ๋ช…์”ฉ ์•‰์„ ์ˆ˜ ์žˆ๋Š” ํ…Œ์ด๋ธ”์ด ํ–‰๋งˆ๋‹ค W๊ฐœ์”ฉ Hํ–‰์— ๊ฑธ์ณ ์žˆ์„ ๋•Œ, + ๋ชจ๋“  ์ฐธ๊ฐ€์ž๋Š” ์„ธ๋กœ๋กœ N์นธ ๋˜๋Š” ๊ฐ€๋กœ๋กœ M์นธ ์ด์ƒ ๋น„์šฐ๊ณ  ์•‰์•„์•ผ ํ•œ๋‹ค. + ์ฆ‰, ๋‹ค๋ฅธ ๋ชจ๋“  ์ฐธ๊ฐ€์ž์™€ ์„ธ๋กœ์ค„ ๋ฒˆํ˜ธ์˜ ์ฐจ๊ฐ€ N๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ + ๊ฐ€๋กœ์ค„ ๋ฒˆํ˜ธ์˜ ์ฐจ๊ฐ€ M๋ณด๋‹ค ํฐ ๊ณณ์—๋งŒ ์•‰์„ ์ˆ˜ ์žˆ๋‹ค. + + ๋…ผ๋ฌธ๊ณผ ๊ณผ์ œ์— ์‹œ๋‹ฌ๋ฆฌ๋Š” ์„ฑ์šฐ๋ฅผ ์œ„ํ•ด + ๊ฐ•์˜์‹ค์ด ๊ฑฐ๋ฆฌ๋‘๊ธฐ ์ˆ˜์น™์„ ์ง€ํ‚ค๋ฉด์„œ + ์ตœ๋Œ€ ๋ช‡ ๋ช…์„ ์ˆ˜์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌํ•ด๋ณด์ž. + + + [์ž…๋ ฅ] + + H, W, N, M์ด ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„๋˜์–ด ์ฃผ์–ด์ง„๋‹ค. + (0 < H, W, N, M โ‰ค 50,000) + + + [์ถœ๋ ฅ] + + ๊ฐ•์˜์‹ค์ด ์ˆ˜์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์ธ์› ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("ZOAC ๊ฑฐ๋ฆฌ๋‘๊ธฐ") + .difficulty(Difficulty.EASY) + .description(description) + .tags("math,implementation") + .build(); + + problemRepository.save(problem); + } + + // 3. DJMAX ๋žญํ‚น + private void createDjmaxRankingProblem() { + if (problemRepository.existsByTitle("DJMAX ๋žญํ‚น")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ํƒœ์ˆ˜๊ฐ€ ์ฆ๊ฒจํ•˜๋Š” ๋””์ œ์ด๋งฅ์Šค ๊ฒŒ์ž„์€ ๊ฐ๊ฐ์˜ ๋…ธ๋ž˜๋งˆ๋‹ค ๋žญํ‚น ๋ฆฌ์ŠคํŠธ๊ฐ€ ์žˆ๋‹ค. + ์ด๊ฒƒ์€ ๋งค๋ฒˆ ๊ฒŒ์ž„ํ•  ๋•Œ๋งˆ๋‹ค ์–ป๋Š” ์ ์ˆ˜๊ฐ€ ๋น„์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. + + ์ด ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์˜ ๋“ฑ์ˆ˜๋Š” ๋ณดํ†ต ์œ„์—์„œ๋ถ€ํ„ฐ ๋ช‡ ๋ฒˆ์งธ ์žˆ๋Š” ์ ์ˆ˜์ธ์ง€๋กœ ๊ฒฐ์ •ํ•œ๋‹ค. + ํ•˜์ง€๋งŒ, ๊ฐ™์€ ์ ์ˆ˜๊ฐ€ ์žˆ์„ ๋•Œ๋Š” ๊ทธ๋Ÿฌํ•œ ์ ์ˆ˜์˜ ๋“ฑ์ˆ˜ ์ค‘์— ๊ฐ€์žฅ ์ž‘์€ ๋“ฑ์ˆ˜๊ฐ€ ๋œ๋‹ค. + + ์˜ˆ๋ฅผ ๋“ค์–ด ๋žญํ‚น ๋ฆฌ์ŠคํŠธ๊ฐ€ 100, 90, 90, 80์ผ ๋•Œ ๊ฐ๊ฐ์˜ ๋“ฑ์ˆ˜๋Š” 1, 2, 2, 4๋“ฑ์ด ๋œ๋‹ค. + + ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์— ์˜ฌ๋ผ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์ ์ˆ˜์˜ ๊ฐœ์ˆ˜ P๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๊ทธ๋ฆฌ๊ณ  ๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ์ ์ˆ˜ N๊ฐœ๊ฐ€ ๋น„์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ฃผ์–ด์ง€๊ณ , + ํƒœ์ˆ˜์˜ ์ƒˆ๋กœ์šด ์ ์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ์ด๋•Œ, ํƒœ์ˆ˜์˜ ์ƒˆ๋กœ์šด ์ ์ˆ˜๊ฐ€ ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์—์„œ ๋ช‡ ๋“ฑ ํ•˜๋Š”์ง€ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + ๋งŒ์•ฝ ์ ์ˆ˜๊ฐ€ ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์— ์˜ฌ๋ผ๊ฐˆ ์ˆ˜ ์—†์„ ์ •๋„๋กœ ๋‚ฎ๋‹ค๋ฉด -1์„ ์ถœ๋ ฅํ•œ๋‹ค. + + ๋งŒ์•ฝ, ๋žญํ‚น ๋ฆฌ์ŠคํŠธ๊ฐ€ ๊ฝ‰ ์ฐจ์žˆ์„ ๋•Œ, + ์ƒˆ ์ ์ˆ˜๊ฐ€ ์ด์ „ ์ ์ˆ˜๋ณด๋‹ค ๋” ์ข‹์„ ๋•Œ๋งŒ ์ ์ˆ˜๊ฐ€ ๋ฐ”๋€๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— N, ํƒœ์ˆ˜์˜ ์ƒˆ๋กœ์šด ์ ์ˆ˜, ๊ทธ๋ฆฌ๊ณ  P๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + P๋Š” 10๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™๊ณ , 50๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€ ์ •์ˆ˜, + N์€ 0๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™๊ณ , P๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€ ์ •์ˆ˜์ด๋‹ค. + ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์ ์ˆ˜๋Š” 2,000,000,000๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€ ์ž์—ฐ์ˆ˜ ๋˜๋Š” 0์ด๋‹ค. + + ๋‘˜์งธ ์ค„์—๋Š” ํ˜„์žฌ ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ์ ์ˆ˜๊ฐ€ ๋น„์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ฃผ์–ด์ง„๋‹ค. + ๋‘˜์งธ ์ค„์€ N์ด 0๋ณด๋‹ค ํฐ ๊ฒฝ์šฐ์—๋งŒ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ์งธ ์ค„์— ํƒœ์ˆ˜์˜ ์ ์ˆ˜๊ฐ€ ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์—์„œ ์ฐจ์ง€ํ•˜๋Š” ๋“ฑ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + ๋žญํ‚น ๋ฆฌ์ŠคํŠธ์— ์˜ฌ๋ผ๊ฐˆ ์ˆ˜ ์—†์œผ๋ฉด -1์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("DJMAX ๋žญํ‚น") + .difficulty(Difficulty.EASY) + .description(description) + .tags("implementation,sorting") + .build(); + + problemRepository.save(problem); + } + + // 4. ์ตœ์†Œ ํž™ + private void createMinHeapProblem() { + if (problemRepository.existsByTitle("์ตœ์†Œ ํž™")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ๋„๋ฆฌ ์ž˜ ์•Œ๋ ค์ง„ ์ž๋ฃŒ๊ตฌ์กฐ ์ค‘ ์ตœ์†Œ ํž™์ด ์žˆ๋‹ค. + ์ตœ์†Œ ํž™์„ ์ด์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ฐ์‚ฐ์„ ์ง€์›ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + 1. ๋ฐฐ์—ด์— ์ž์—ฐ์ˆ˜ x๋ฅผ ๋„ฃ๋Š”๋‹ค. + 2. ๋ฐฐ์—ด์—์„œ ๊ฐ€์žฅ ์ž‘์€ ๊ฐ’์„ ์ถœ๋ ฅํ•˜๊ณ , ๊ทธ ๊ฐ’์„ ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐํ•œ๋‹ค. + + ํ”„๋กœ๊ทธ๋žจ์€ ์ฒ˜์Œ์— ๋น„์–ด์žˆ๋Š” ๋ฐฐ์—ด์—์„œ ์‹œ์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์—ฐ์‚ฐ์˜ ๊ฐœ์ˆ˜ N(1 โ‰ค N โ‰ค 100,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ N๊ฐœ์˜ ์ค„์—๋Š” ์—ฐ์‚ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ •์ˆ˜ x๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๋งŒ์•ฝ x๊ฐ€ ์ž์—ฐ์ˆ˜๋ผ๋ฉด ๋ฐฐ์—ด์— x๋ผ๋Š” ๊ฐ’์„ ๋„ฃ๋Š”(์ถ”๊ฐ€ํ•˜๋Š”) ์—ฐ์‚ฐ์ด๊ณ , + x๊ฐ€ 0์ด๋ผ๋ฉด ๋ฐฐ์—ด์—์„œ ๊ฐ€์žฅ ์ž‘์€ ๊ฐ’์„ ์ถœ๋ ฅํ•˜๊ณ  ๊ทธ ๊ฐ’์„ ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐํ•˜๋Š” ๊ฒฝ์šฐ์ด๋‹ค. + x๋Š” 2^31๋ณด๋‹ค ์ž‘์€ ์ž์—ฐ์ˆ˜ ๋˜๋Š” 0์ด๊ณ , ์Œ์˜ ์ •์ˆ˜๋Š” ์ž…๋ ฅ์œผ๋กœ ์ฃผ์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. + + + [์ถœ๋ ฅ] + + ์ž…๋ ฅ์—์„œ 0์ด ์ฃผ์–ด์ง„ ํšŸ์ˆ˜๋งŒํผ ๋‹ต์„ ์ถœ๋ ฅํ•œ๋‹ค. + ๋งŒ์•ฝ ๋ฐฐ์—ด์ด ๋น„์–ด ์žˆ๋Š” ๊ฒฝ์šฐ์ธ๋ฐ ๊ฐ€์žฅ ์ž‘์€ ๊ฐ’์„ ์ถœ๋ ฅํ•˜๋ผ๊ณ  ํ•œ ๊ฒฝ์šฐ์—๋Š” 0์„ ์ถœ๋ ฅํ•˜๋ฉด ๋œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์ตœ์†Œ ํž™") + .difficulty(Difficulty.EASY) + .description(description) + .tags("datastructure,heap") + .build(); + + problemRepository.save(problem); + } + + // 5. ์‚ผ๊ฐํ˜• ๋ถ„๋ฅ˜ + private void createTriangleProblem() { + if (problemRepository.existsByTitle("์‚ผ๊ฐํ˜• ๋ถ„๋ฅ˜")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์‚ผ๊ฐํ˜•์˜ ์„ธ ๋ณ€์˜ ๊ธธ์ด๊ฐ€ ์ฃผ์–ด์งˆ ๋•Œ ๋ณ€์˜ ๊ธธ์ด์— ๋”ฐ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•œ๋‹ค. + Equilateral : ์„ธ ๋ณ€์˜ ๊ธธ์ด๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ ๊ฒฝ์šฐ + Isosceles : ๋‘ ๋ณ€์˜ ๊ธธ์ด๋งŒ ๊ฐ™์€ ๊ฒฝ์šฐ + Scalene : ์„ธ ๋ณ€์˜ ๊ธธ์ด๊ฐ€ ๋ชจ๋‘ ๋‹ค๋ฅธ ๊ฒฝ์šฐ + + ๋‹จ ์ฃผ์–ด์ง„ ์„ธ ๋ณ€์˜ ๊ธธ์ด๊ฐ€ ์‚ผ๊ฐํ˜•์˜ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” "Invalid" ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + ์˜ˆ๋ฅผ ๋“ค์–ด 6, 3, 2๊ฐ€ ์ด ๊ฒฝ์šฐ์— ํ•ด๋‹นํ•œ๋‹ค. + ๊ฐ€์žฅ ๊ธด ๋ณ€์˜ ๊ธธ์ด๋ณด๋‹ค ๋‚˜๋จธ์ง€ ๋‘ ๋ณ€์˜ ๊ธธ์ด์˜ ํ•ฉ์ด ๊ธธ์ง€ ์•Š์œผ๋ฉด ์‚ผ๊ฐํ˜•์˜ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•œ๋‹ค. + + ์„ธ ๋ณ€์˜ ๊ธธ์ด๊ฐ€ ์ฃผ์–ด์งˆ ๋•Œ ์œ„ ์ •์˜์— ๋”ฐ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ๊ฐ ์ค„์—๋Š” 1,000์„ ๋„˜์ง€ ์•Š๋Š” ์–‘์˜ ์ •์ˆ˜ 3๊ฐœ๊ฐ€ ์ž…๋ ฅ๋œ๋‹ค. + ๋งˆ์ง€๋ง‰ ์ค„์€ 0 0 0์ด๋ฉฐ ์ด ์ค„์€ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค. + + + [์ถœ๋ ฅ] + + ๊ฐ ์ž…๋ ฅ์— ๋Œ€ํ•ด Equilateral, Isosceles, Scalene, Invalid ์ค‘ ํ•˜๋‚˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์‚ผ๊ฐํ˜• ๋ถ„๋ฅ˜") + .difficulty(Difficulty.EASY) + .description(description) + .tags("math,implementation,geometry") + .build(); + + problemRepository.save(problem); + } + + // 6. 1๋กœ ๋งŒ๋“ค๊ธฐ + private void createMakeOneProblem() { + if (problemRepository.existsByTitle("1๋กœ ๋งŒ๋“ค๊ธฐ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์ •์ˆ˜ X์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์—ฐ์‚ฐ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ธ ๊ฐ€์ง€ ์ด๋‹ค. + + 1. X๊ฐ€ 3์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋–จ์–ด์ง€๋ฉด, 3์œผ๋กœ ๋‚˜๋ˆˆ๋‹ค. + 2. X๊ฐ€ 2๋กœ ๋‚˜๋ˆ„์–ด ๋–จ์–ด์ง€๋ฉด, 2๋กœ ๋‚˜๋ˆˆ๋‹ค. + 3. 1์„ ๋บ€๋‹ค. + + ์ •์ˆ˜ N์ด ์ฃผ์–ด์กŒ์„ ๋•Œ, ์œ„์™€ ๊ฐ™์€ ์—ฐ์‚ฐ ์„ธ ๊ฐœ๋ฅผ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•ด์„œ 1์„ ๋งŒ๋“ค๋ ค๊ณ  ํ•œ๋‹ค. + ์—ฐ์‚ฐ์„ ์‚ฌ์šฉํ•˜๋Š” ํšŸ์ˆ˜์˜ ์ตœ์†Ÿ๊ฐ’์„ ์ถœ๋ ฅํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— 1๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™๊ณ , 10^6๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€ ์ •์ˆ˜ N์ด ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์—ฐ์‚ฐ์„ ํ•˜๋Š” ํšŸ์ˆ˜์˜ ์ตœ์†Ÿ๊ฐ’์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("1๋กœ ๋งŒ๋“ค๊ธฐ") + .difficulty(Difficulty.EASY) + .description(description) + .tags("dp") + .build(); + + problemRepository.save(problem); + } + + // 7. ์ˆซ์ž ์นด๋“œ + private void createNumberCardProblem() { + if (problemRepository.existsByTitle("์ˆซ์ž ์นด๋“œ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์ˆซ์ž ์นด๋“œ๋Š” ์ •์ˆ˜ ํ•˜๋‚˜๊ฐ€ ์ ํ˜€์ ธ ์žˆ๋Š” ์นด๋“œ์ด๋‹ค. ์ƒ๊ทผ์ด๋Š” ์ˆซ์ž ์นด๋“œ N๊ฐœ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. + ์ •์ˆ˜ M๊ฐœ๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ, ์ด ์ˆ˜๊ฐ€ ์ ํ˜€์žˆ๋Š” ์ˆซ์ž ์นด๋“œ๋ฅผ ์ƒ๊ทผ์ด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ์•„๋‹Œ์ง€๋ฅผ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์ƒ๊ทผ์ด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ˆซ์ž ์นด๋“œ์˜ ๊ฐœ์ˆ˜ N(1 โ‰ค N โ‰ค 500,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‘˜์งธ ์ค„์—๋Š” ์ˆซ์ž ์นด๋“œ์— ์ ํ˜€์žˆ๋Š” ์ •์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ์ˆซ์ž ์นด๋“œ์— ์ ํ˜€์žˆ๋Š” ์ˆ˜๋Š” -10,000,000๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™๊ณ , 10,000,000๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™๋‹ค. + ๋‘ ์ˆซ์ž ์นด๋“œ์— ๊ฐ™์€ ์ˆ˜๊ฐ€ ์ ํ˜€์žˆ๋Š” ๊ฒฝ์šฐ๋Š” ์—†๋‹ค. + + ์…‹์งธ ์ค„์—๋Š” M(1 โ‰ค M โ‰ค 500,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋„ท์งธ ์ค„์—๋Š” ์ƒ๊ทผ์ด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ˆซ์ž ์นด๋“œ์ธ์ง€ ์•„๋‹Œ์ง€๋ฅผ ๊ตฌํ•ด์•ผ ํ•  M๊ฐœ์˜ ์ •์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์ž…๋ ฅ์œผ๋กœ ์ฃผ์–ด์ง„ M๊ฐœ์˜ ์ˆ˜์— ๋Œ€ํ•ด์„œ, + ๊ฐ ์ˆ˜๊ฐ€ ์ ํžŒ ์ˆซ์ž ์นด๋“œ๋ฅผ ์ƒ๊ทผ์ด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด 1์„, ์•„๋‹ˆ๋ฉด 0์„ ๊ณต๋ฐฑ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์ˆซ์ž ์นด๋“œ") + .difficulty(Difficulty.EASY) + .description(description) + .tags("binary_search,set,implementation") + .build(); + + problemRepository.save(problem); + } + + // ========================= + // MEDIUM ๋ฌธ์ œ๋“ค + // ========================= + + // 8. Dummy (๋ฑ€ ๊ฒŒ์ž„) + private void createSnakeGameProblem() { + if (problemRepository.existsByTitle("Dummy (๋ฑ€ ๊ฒŒ์ž„)")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + 'Dummy' ๋ผ๋Š” ๋„์Šค๊ฒŒ์ž„์ด ์žˆ๋‹ค. ์ด ๊ฒŒ์ž„์—๋Š” ๋ฑ€์ด ๋‚˜์™€์„œ ๊ธฐ์–ด๋‹ค๋‹ˆ๋Š”๋ฐ, + ์‚ฌ๊ณผ๋ฅผ ๋จน์œผ๋ฉด ๋ฑ€ ๊ธธ์ด๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค. + ๋ฑ€์ด ์ด๋ฆฌ์ €๋ฆฌ ๊ธฐ์–ด๋‹ค๋‹ˆ๋‹ค๊ฐ€ ๋ฒฝ ๋˜๋Š” ์ž๊ธฐ์ž์‹ ์˜ ๋ชธ๊ณผ ๋ถ€๋”ชํžˆ๋ฉด ๊ฒŒ์ž„์ด ๋๋‚œ๋‹ค. + + ๊ฒŒ์ž„์€ NxN ์ •์‚ฌ๊ฐ ๋ณด๋“œ ์œ„์—์„œ ์ง„ํ–‰๋˜๊ณ , ๋ช‡๋ช‡ ์นธ์—๋Š” ์‚ฌ๊ณผ๊ฐ€ ๋†“์—ฌ์ ธ ์žˆ๋‹ค. + ๋ณด๋“œ์˜ ์ƒํ•˜์ขŒ์šฐ ๋์—๋Š” ๋ฒฝ์ด ์žˆ๋‹ค. + ๊ฒŒ์ž„์ด ์‹œ์ž‘ํ•  ๋•Œ ๋ฑ€์€ ๋งจ ์œ„ ๋งจ ์ขŒ์ธก์— ์œ„์น˜ํ•˜๊ณ  ๋ฑ€์˜ ๊ธธ์ด๋Š” 1์ด๋‹ค. + ๋ฑ€์€ ์ฒ˜์Œ์— ์˜ค๋ฅธ์ชฝ์„ ํ–ฅํ•œ๋‹ค. + + ๋ฑ€์€ ๋งค ์ดˆ๋งˆ๋‹ค ์ด๋™์„ ํ•˜๋Š”๋ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ทœ์น™์„ ๋”ฐ๋ฅธ๋‹ค. + + 1. ๋จผ์ € ๋ฑ€์€ ๋ชธ๊ธธ์ด๋ฅผ ๋Š˜๋ ค ๋จธ๋ฆฌ๋ฅผ ๋‹ค์Œ ์นธ์— ์œ„์น˜์‹œํ‚จ๋‹ค. + 2. ๋งŒ์•ฝ ๋ฒฝ์ด๋‚˜ ์ž๊ธฐ์ž์‹ ์˜ ๋ชธ๊ณผ ๋ถ€๋”ชํžˆ๋ฉด ๊ฒŒ์ž„์ด ๋๋‚œ๋‹ค. + 3. ๋งŒ์•ฝ ์ด๋™ํ•œ ์นธ์— ์‚ฌ๊ณผ๊ฐ€ ์žˆ๋‹ค๋ฉด, ๊ทธ ์นธ์— ์žˆ๋˜ ์‚ฌ๊ณผ๊ฐ€ ์—†์–ด์ง€๊ณ  ๊ผฌ๋ฆฌ๋Š” ์›€์ง์ด์ง€ ์•Š๋Š”๋‹ค. + 4. ๋งŒ์•ฝ ์ด๋™ํ•œ ์นธ์— ์‚ฌ๊ณผ๊ฐ€ ์—†๋‹ค๋ฉด, ๋ชธ๊ธธ์ด๋ฅผ ์ค„์—ฌ์„œ ๊ผฌ๋ฆฌ๊ฐ€ ์œ„์น˜ํ•œ ์นธ์„ ๋น„์›Œ์ค€๋‹ค. ์ฆ‰, ๋ชธ๊ธธ์ด๋Š” ๋ณ€ํ•˜์ง€ ์•Š๋Š”๋‹ค. + + ์‚ฌ๊ณผ์˜ ์œ„์น˜์™€ ๋ฑ€์˜ ์ด๋™๊ฒฝ๋กœ๊ฐ€ ์ฃผ์–ด์งˆ ๋•Œ + ์ด ๊ฒŒ์ž„์ด ๋ช‡ ์ดˆ์— ๋๋‚˜๋Š”์ง€ ๊ณ„์‚ฐํ•˜๋ผ. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ๋ณด๋“œ์˜ ํฌ๊ธฐ N์ด ์ฃผ์–ด์ง„๋‹ค. (2 โ‰ค N โ‰ค 100) + ๋‹ค์Œ ์ค„์— ์‚ฌ๊ณผ์˜ ๊ฐœ์ˆ˜ K๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (0 โ‰ค K โ‰ค 100) + + ๋‹ค์Œ K๊ฐœ์˜ ์ค„์—๋Š” ์‚ฌ๊ณผ์˜ ์œ„์น˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ์ฒซ ๋ฒˆ์งธ ์ •์ˆ˜๋Š” ํ–‰, ๋‘ ๋ฒˆ์งธ ์ •์ˆ˜๋Š” ์—ด ์œ„์น˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค. + ์‚ฌ๊ณผ์˜ ์œ„์น˜๋Š” ๋ชจ๋‘ ๋‹ค๋ฅด๋ฉฐ, ๋งจ ์œ„ ๋งจ ์ขŒ์ธก (1ํ–‰ 1์—ด)์—๋Š” ์‚ฌ๊ณผ๊ฐ€ ์—†๋‹ค. + + ๋‹ค์Œ ์ค„์—๋Š” ๋ฑ€์˜ ๋ฐฉํ–ฅ ๋ณ€ํ™˜ ํšŸ์ˆ˜ L์ด ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค L โ‰ค 100) + + ๋‹ค์Œ L๊ฐœ์˜ ์ค„์—๋Š” ๋ฑ€์˜ ๋ฐฉํ–ฅ ๋ณ€ํ™˜ ์ •๋ณด๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ์ •์ˆ˜ X์™€ ๋ฌธ์ž C๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์œผ๋ฉฐ, + ๊ฒŒ์ž„ ์‹œ์ž‘ ์‹œ๊ฐ„์œผ๋กœ๋ถ€ํ„ฐ X์ดˆ๊ฐ€ ๋๋‚œ ๋’ค์— + ์™ผ์ชฝ(C๊ฐ€ 'L') ๋˜๋Š” ์˜ค๋ฅธ์ชฝ(C๊ฐ€ 'D')์œผ๋กœ 90๋„ ๋ฐฉํ–ฅ์„ ํšŒ์ „์‹œํ‚จ๋‹ค๋Š” ๋œป์ด๋‹ค. + X๋Š” 10,000 ์ดํ•˜์˜ ์–‘์˜ ์ •์ˆ˜์ด๋ฉฐ, ๋ฐฉํ–ฅ ์ „ํ™˜ ์ •๋ณด๋Š” X๊ฐ€ ์ฆ๊ฐ€ํ•˜๋Š” ์ˆœ์œผ๋กœ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ์งธ ์ค„์— ๊ฒŒ์ž„์ด ๋ช‡ ์ดˆ์— ๋๋‚˜๋Š”์ง€ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("Dummy (๋ฑ€ ๊ฒŒ์ž„)") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("simulation,implementation,queue") + .build(); + + problemRepository.save(problem); + } + + // 9. ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ + private void createDiceSimulationProblem() { + if (problemRepository.existsByTitle("์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ํฌ๊ธฐ๊ฐ€ Nร—M์ธ ์ง€๋„๊ฐ€ ์กด์žฌํ•œ๋‹ค. ์ง€๋„์˜ ์˜ค๋ฅธ์ชฝ์€ ๋™์ชฝ, ์œ„์ชฝ์€ ๋ถ์ชฝ์ด๋‹ค. + ์ด ์ง€๋„์˜ ์œ„์— ์ฃผ์‚ฌ์œ„๊ฐ€ ํ•˜๋‚˜ ๋†“์—ฌ์ ธ ์žˆ์œผ๋ฉฐ, ์ฃผ์‚ฌ์œ„์˜ ์ „๊ฐœ๋„๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. + ์ง€๋„์˜ ์ขŒํ‘œ๋Š” (r, c)๋กœ ๋‚˜ํƒ€๋‚ด๋ฉฐ, r๋Š” ๋ถ์ชฝ์œผ๋กœ๋ถ€ํ„ฐ ๋–จ์–ด์ง„ ์นธ์˜ ๊ฐœ์ˆ˜, + c๋Š” ์„œ์ชฝ์œผ๋กœ๋ถ€ํ„ฐ ๋–จ์–ด์ง„ ์นธ์˜ ๊ฐœ์ˆ˜์ด๋‹ค. + + 2 + 4 1 3 + 5 + 6 + + ์ฃผ์‚ฌ์œ„๋Š” ์ง€๋„ ์œ„์— ์œ— ๋ฉด์ด 1์ด๊ณ , ๋™์ชฝ์„ ๋ฐ”๋ผ๋ณด๋Š” ๋ฐฉํ–ฅ์ด 3์ธ ์ƒํƒœ๋กœ ๋†“์—ฌ์ ธ ์žˆ์œผ๋ฉฐ, + ๋†“์—ฌ์ ธ ์žˆ๋Š” ๊ณณ์˜ ์ขŒํ‘œ๋Š” (x, y)์ด๋‹ค. + ๊ฐ€์žฅ ์ฒ˜์Œ์— ์ฃผ์‚ฌ์œ„์—๋Š” ๋ชจ๋“  ๋ฉด์— 0์ด ์ ํ˜€์ ธ ์žˆ๋‹ค. + + ์ง€๋„์˜ ๊ฐ ์นธ์—๋Š” ์ •์ˆ˜๊ฐ€ ํ•˜๋‚˜์”ฉ ์“ฐ์—ฌ์ ธ ์žˆ๋‹ค. + ์ฃผ์‚ฌ์œ„๋ฅผ ๊ตด๋ ธ์„ ๋•Œ, ์ด๋™ํ•œ ์นธ์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๊ฐ€ 0์ด๋ฉด, + ์ฃผ์‚ฌ์œ„์˜ ๋ฐ”๋‹ฅ๋ฉด์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๊ฐ€ ์นธ์— ๋ณต์‚ฌ๋œ๋‹ค. + 0์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋Š” ์นธ์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๊ฐ€ ์ฃผ์‚ฌ์œ„์˜ ๋ฐ”๋‹ฅ๋ฉด์œผ๋กœ ๋ณต์‚ฌ๋˜๋ฉฐ, + ์นธ์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๋Š” 0์ด ๋œ๋‹ค. + + ์ฃผ์‚ฌ์œ„๋ฅผ ๋†“์€ ๊ณณ์˜ ์ขŒํ‘œ์™€ ์ด๋™์‹œํ‚ค๋Š” ๋ช…๋ น์ด ์ฃผ์–ด์กŒ์„ ๋•Œ, + ์ฃผ์‚ฌ์œ„๊ฐ€ ์ด๋™ํ–ˆ์„ ๋•Œ๋งˆ๋‹ค ์ƒ๋‹จ์— ์“ฐ์—ฌ ์žˆ๋Š” ๊ฐ’์„ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + ์ฃผ์‚ฌ์œ„๋Š” ์ง€๋„์˜ ๋ฐ”๊นฅ์œผ๋กœ ์ด๋™์‹œํ‚ฌ ์ˆ˜ ์—†๋‹ค. + ๋งŒ์•ฝ ๋ฐ”๊นฅ์œผ๋กœ ์ด๋™์‹œํ‚ค๋ ค๊ณ  ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ํ•ด๋‹น ๋ช…๋ น์„ ๋ฌด์‹œํ•ด์•ผ ํ•˜๋ฉฐ, + ์ถœ๋ ฅ๋„ ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์ง€๋„์˜ ์„ธ๋กœ ํฌ๊ธฐ N, ๊ฐ€๋กœ ํฌ๊ธฐ M (1 โ‰ค N, M โ‰ค 20), + ์ฃผ์‚ฌ์œ„๋ฅผ ๋†“์€ ๊ณณ์˜ ์ขŒํ‘œ x, y(0 โ‰ค x โ‰ค N-1, 0 โ‰ค y โ‰ค M-1), + ๊ทธ๋ฆฌ๊ณ  ๋ช…๋ น์˜ ๊ฐœ์ˆ˜ K (1 โ‰ค K โ‰ค 1,000)๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + ๋‘˜์งธ ์ค„๋ถ€ํ„ฐ N๊ฐœ์˜ ์ค„์— ์ง€๋„์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๊ฐ€ ๋ถ์ชฝ๋ถ€ํ„ฐ ๋‚จ์ชฝ์œผ๋กœ, + ๊ฐ ์ค„์€ ์„œ์ชฝ๋ถ€ํ„ฐ ๋™์ชฝ ์ˆœ์„œ๋Œ€๋กœ ์ฃผ์–ด์ง„๋‹ค. + ์ฃผ์‚ฌ์œ„๋ฅผ ๋†“์€ ์นธ์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๋Š” ํ•ญ์ƒ 0์ด๋‹ค. + ์ง€๋„์˜ ๊ฐ ์นธ์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๋Š” 10 ๋ฏธ๋งŒ์˜ ์ž์—ฐ์ˆ˜ ๋˜๋Š” 0์ด๋‹ค. + + ๋งˆ์ง€๋ง‰ ์ค„์—๋Š” ์ด๋™ํ•˜๋Š” ๋ช…๋ น์ด ์ˆœ์„œ๋Œ€๋กœ ์ฃผ์–ด์ง„๋‹ค. + ๋™์ชฝ์€ 1, ์„œ์ชฝ์€ 2, ๋ถ์ชฝ์€ 3, ๋‚จ์ชฝ์€ 4๋กœ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ์ฃผ์‚ฌ์œ„์˜ ์œ— ๋ฉด์— ์“ฐ์—ฌ ์žˆ๋Š” ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + ๋งŒ์•ฝ ๋ฐ”๊นฅ์œผ๋กœ ์ด๋™์‹œํ‚ค๋ ค๊ณ  ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ํ•ด๋‹น ๋ช…๋ น์„ ๋ฌด์‹œํ•ด์•ผ ํ•˜๋ฉฐ, + ์ถœ๋ ฅ๋„ ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("simulation,implementation") + .build(); + + problemRepository.save(problem); + } + + // 10. ๋ชฉํ‘œ์ง€์  ๊ฑฐ๋ฆฌ + private void createTargetDistanceProblem() { + if (problemRepository.existsByTitle("๋ชฉํ‘œ์ง€์  ๊ฑฐ๋ฆฌ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์ง€๋„๊ฐ€ ์ฃผ์–ด์ง€๋ฉด ๋ชจ๋“  ์ง€์ ์— ๋Œ€ํ•ด์„œ ๋ชฉํ‘œ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ๋ฅผ ๊ตฌํ•˜์—ฌ๋ผ. + ๋ฌธ์ œ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์˜ค์ง ๊ฐ€๋กœ์™€ ์„ธ๋กœ๋กœ๋งŒ ์›€์ง์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•˜์ž. + + + [์ž…๋ ฅ] + + ์ง€๋„์˜ ํฌ๊ธฐ n๊ณผ m์ด ์ฃผ์–ด์ง„๋‹ค. n์€ ์„ธ๋กœ์˜ ํฌ๊ธฐ, m์€ ๊ฐ€๋กœ์˜ ํฌ๊ธฐ๋‹ค. (2 โ‰ค n โ‰ค 1000, 2 โ‰ค m โ‰ค 1000) + ๋‹ค์Œ n๊ฐœ์˜ ์ค„์— m๊ฐœ์˜ ์ˆซ์ž๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + 0์€ ๊ฐˆ ์ˆ˜ ์—†๋Š” ๋•…์ด๊ณ  1์€ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๋•…, 2๋Š” ๋ชฉํ‘œ์ง€์ ์ด๋‹ค. ์ž…๋ ฅ์—์„œ 2๋Š” ๋‹จ ํ•œ ๊ฐœ์ด๋‹ค. + + + [์ถœ๋ ฅ] + + ๊ฐ ์ง€์ ์—์„œ ๋ชฉํ‘œ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + ์›๋ž˜ ๊ฐˆ ์ˆ˜ ์—†๋Š” ๋•…์ธ ์œ„์น˜๋Š” 0์„ ์ถœ๋ ฅํ•˜๊ณ , + ์›๋ž˜ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๋•…์ธ ๋ถ€๋ถ„ ์ค‘์—์„œ ๋„๋‹ฌํ•  ์ˆ˜ ์—†๋Š” ์œ„์น˜๋Š” -1์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๋ชฉํ‘œ์ง€์  ๊ฑฐ๋ฆฌ") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("bfs,graph") + .build(); + + problemRepository.save(problem); + } + + // 11. DFS์™€ BFS + private void createDfsBfsProblem() { + if (problemRepository.existsByTitle("DFS์™€ BFS")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ๊ทธ๋ž˜ํ”„๋ฅผ DFS๋กœ ํƒ์ƒ‰ํ•œ ๊ฒฐ๊ณผ์™€ BFS๋กœ ํƒ์ƒ‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + ๋‹จ, ๋ฐฉ๋ฌธํ•  ์ˆ˜ ์žˆ๋Š” ์ •์ ์ด ์—ฌ๋Ÿฌ ๊ฐœ์ธ ๊ฒฝ์šฐ์—๋Š” ์ •์  ๋ฒˆํ˜ธ๊ฐ€ ์ž‘์€ ๊ฒƒ์„ ๋จผ์ € ๋ฐฉ๋ฌธํ•˜๊ณ , + ๋” ์ด์ƒ ๋ฐฉ๋ฌธํ•  ์ˆ˜ ์žˆ๋Š” ์ ์ด ์—†๋Š” ๊ฒฝ์šฐ ์ข…๋ฃŒํ•œ๋‹ค. + ์ •์  ๋ฒˆํ˜ธ๋Š” 1๋ฒˆ๋ถ€ํ„ฐ N๋ฒˆ๊นŒ์ง€์ด๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์ •์ ์˜ ๊ฐœ์ˆ˜ N(1 โ‰ค N โ‰ค 1,000), ๊ฐ„์„ ์˜ ๊ฐœ์ˆ˜ M(1 โ‰ค M โ‰ค 10,000), ํƒ์ƒ‰์„ ์‹œ์ž‘ํ•  ์ •์ ์˜ ๋ฒˆํ˜ธ V๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ M๊ฐœ์˜ ์ค„์—๋Š” ๊ฐ„์„ ์ด ์—ฐ๊ฒฐํ•˜๋Š” ๋‘ ์ •์ ์˜ ๋ฒˆํ˜ธ๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ์–ด๋–ค ๋‘ ์ •์  ์‚ฌ์ด์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฐ„์„ ์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ์ž…๋ ฅ์œผ๋กœ ์ฃผ์–ด์ง€๋Š” ๊ฐ„์„ ์€ ์–‘๋ฐฉํ–ฅ์ด๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ์งธ ์ค„์— DFS๋ฅผ ์ˆ˜ํ–‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ, ๊ทธ ๋‹ค์Œ ์ค„์—๋Š” BFS๋ฅผ ์ˆ˜ํ–‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + V๋ถ€ํ„ฐ ๋ฐฉ๋ฌธ๋œ ์ ์„ ์ˆœ์„œ๋Œ€๋กœ ์ถœ๋ ฅํ•˜๋ฉด ๋œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("DFS์™€ BFS") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("graph,dfs,bfs") + .build(); + + problemRepository.save(problem); + } + + // 12. ์—ฌํ–‰ ๊ฐ€์ž + private void createTripPlanningProblem() { + if (problemRepository.existsByTitle("์—ฌํ–‰ ๊ฐ€์ž")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ๋™ํ˜์ด๋Š” ์นœ๊ตฌ๋“ค๊ณผ ํ•จ๊ป˜ ์—ฌํ–‰์„ ๊ฐ€๋ ค๊ณ  ํ•œ๋‹ค. + ํ•œ๊ตญ์—๋Š” ๋„์‹œ๊ฐ€ N๊ฐœ ์žˆ๊ณ  ์ž„์˜์˜ ๋‘ ๋„์‹œ ์‚ฌ์ด์— ๊ธธ์ด ์žˆ์„ ์ˆ˜๋„, ์—†์„ ์ˆ˜๋„ ์žˆ๋‹ค. + + ๋™ํ˜์ด์˜ ์—ฌํ–‰ ์ผ์ •์ด ์ฃผ์–ด์กŒ์„ ๋•Œ, ์ด ์—ฌํ–‰ ๊ฒฝ๋กœ๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฒƒ์ธ์ง€ ์•Œ์•„๋ณด์ž. + ๋ฌผ๋ก  ์ค‘๊ฐ„์— ๋‹ค๋ฅธ ๋„์‹œ๋ฅผ ๊ฒฝ์œ ํ•ด์„œ ์—ฌํ–‰์„ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. + + ๋„์‹œ๋“ค์˜ ๊ฐœ์ˆ˜์™€ ๋„์‹œ๋“ค ๊ฐ„์˜ ์—ฐ๊ฒฐ ์—ฌ๋ถ€๊ฐ€ ์ฃผ์–ด์ ธ ์žˆ๊ณ , + ๋™ํ˜์ด์˜ ์—ฌํ–‰ ๊ณ„ํš์— ์†ํ•œ ๋„์‹œ๋“ค์ด ์ˆœ์„œ๋Œ€๋กœ ์ฃผ์–ด์กŒ์„ ๋•Œ ๊ฐ€๋Šฅํ•œ์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋ณ„ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + ๊ฐ™์€ ๋„์‹œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐฉ๋ฌธํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ ์ค„์— ๋„์‹œ์˜ ์ˆ˜ N์ด ์ฃผ์–ด์ง„๋‹ค. (N โ‰ค 200) + ๋‘˜์งธ ์ค„์— ์—ฌํ–‰ ๊ณ„ํš์— ์†ํ•œ ๋„์‹œ๋“ค์˜ ์ˆ˜ M์ด ์ฃผ์–ด์ง„๋‹ค. (M โ‰ค 1000) + + ๋‹ค์Œ N๊ฐœ์˜ ์ค„์—๋Š” N๊ฐœ์˜ ์ •์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + i๋ฒˆ์งธ ์ค„์˜ j๋ฒˆ์งธ ์ˆ˜๋Š” i๋ฒˆ ๋„์‹œ์™€ j๋ฒˆ ๋„์‹œ์˜ ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ์˜๋ฏธํ•œ๋‹ค. + 1์ด๋ฉด ์—ฐ๊ฒฐ๋œ ๊ฒƒ์ด๊ณ  0์ด๋ฉด ์—ฐ๊ฒฐ์ด ๋˜์ง€ ์•Š์€ ๊ฒƒ์ด๋‹ค. + + ๋งˆ์ง€๋ง‰ ์ค„์—๋Š” ์—ฌํ–‰ ๊ณ„ํš์ด ์ฃผ์–ด์ง„๋‹ค. + ๋„์‹œ์˜ ๋ฒˆํ˜ธ๋Š” 1๋ถ€ํ„ฐ N๊นŒ์ง€ ์ฐจ๋ก€๋Œ€๋กœ ๋งค๊ฒจ์ ธ ์žˆ๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ ์ค„์— ๊ฐ€๋Šฅํ•˜๋ฉด YES, ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด NO๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์—ฌํ–‰ ๊ฐ€์ž") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("graph,union_find,bfs") + .build(); + + problemRepository.save(problem); + } + + // 13. ํฌ๋ฆฌ์Šค๋งˆ์Šค ์„ ๋ฌผ + private void createChristmasGiftProblem() { + if (problemRepository.existsByTitle("ํฌ๋ฆฌ์Šค๋งˆ์Šค ์„ ๋ฌผ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ํฌ๋ฆฌ์Šค๋งˆ์Šค์—๋Š” ์‚ฐํƒ€๊ฐ€ ์ฐฉํ•œ ์•„์ด๋“ค์—๊ฒŒ ์„ ๋ฌผ์„ ๋‚˜๋ˆ ์ค€๋‹ค. + ์˜ฌํ•ด๋„ ์‚ฐํƒ€๋Š” ์„ ๋ฌผ์„ ๋‚˜๋ˆ ์ฃผ๊ธฐ ์œ„ํ•ด ์ „ ์„ธ๊ณ„๋ฅผ ๋Œ์•„๋‹ค๋‹ˆ๋ฉฐ ์ฐฉํ•œ ์•„์ด๋“ค์—๊ฒŒ ์„ ๋ฌผ์„ ๋‚˜๋ˆ ์ค„ ๊ฒƒ์ด๋‹ค. + ํ•˜์ง€๋งŒ ์‚ฐํƒ€์˜ ์ฐ๋งค๋Š” ๊ทธ๋ ‡๊ฒŒ ํฌ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, + ์„ธ๊ณ„ ๊ณณ๊ณณ์— ๊ฑฐ์ ๋“ค์„ ์„ธ์›Œ ๊ทธ ๊ณณ์„ ๋ฐฉ๋ฌธํ•˜๋ฉฐ ์„ ๋ฌผ์„ ์ถฉ์ „ํ•ด ๋‚˜๊ฐˆ ๊ฒƒ์ด๋‹ค. + + ๋˜ํ•œ, ์ฐฉํ•œ ์•„์ด๋“ค์„ ๋งŒ๋‚  ๋•Œ๋งˆ๋‹ค ์ž์‹ ์ด ๋“ค๊ณ  ์žˆ๋Š” ๊ฐ€์žฅ ๊ฐ€์น˜๊ฐ€ ํฐ ์„ ๋ฌผ ํ•˜๋‚˜๋ฅผ ์„ ๋ฌผํ•ด ์ค„ ๊ฒƒ์ด๋‹ค. + + ์ด์ œ ์‚ฐํƒ€๊ฐ€ ์„ ๋ฌผ์„ ๋‚˜๋ˆ ์ค„ ๊ฒƒ์ด๋‹ค. + ์ฐจ๋ก€๋Œ€๋กœ ๋ฐฉ๋ฌธํ•œ ์•„์ด๋“ค๊ณผ ๊ฑฐ์ ์ง€์˜ ์ •๋ณด๋“ค์ด ์ฃผ์–ด์กŒ์„ ๋•Œ, + ์•„์ด๋“ค์ด ๋ฐ›์€ ์„ ๋ฌผ๋“ค์˜ ๊ฐ€์น˜๋ฅผ ์ถœ๋ ฅํ•˜์‹œ์˜ค. + ๋งŒ์•ฝ ์•„์ด๋“ค์—๊ฒŒ ์ค„ ์„ ๋ฌผ์ด ์—†๋‹ค๋ฉด -1์„ ์ถœ๋ ฅํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ ๋ฒˆ์งธ ์ค„์—๋Š” ์•„์ด๋“ค๊ณผ ๊ฑฐ์ ์ง€๋ฅผ ๋ฐฉ๋ฌธํ•œ ํšŸ์ˆ˜ n์ด ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค n โ‰ค 5,000) + + ๋‹ค์Œ n์ค„์—๋Š” ๋จผ์ € ์ •์ˆ˜ a๊ฐ€ ์ฃผ์–ด์ง€๊ณ , ๊ทธ ๋‹ค์Œ a๊ฐœ์˜ ์ˆซ์ž๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + a > 0 ์ด๋ผ๋ฉด ๊ฑฐ์ ์ง€์—์„œ a๊ฐœ์˜ ์„ ๋ฌผ์„ ์ถฉ์ „ํ•˜๋Š” ๊ฒƒ์ด๊ณ , + ์ด ์ˆซ์ž๋“ค์ด ์„ ๋ฌผ์˜ ๊ฐ€์น˜์ด๋‹ค. + ๋งŒ์•ฝ a๊ฐ€ 0์ด๋ผ๋ฉด ๊ฑฐ์ ์ง€๊ฐ€ ์•„๋‹Œ ์•„์ด๋“ค์„ ๋งŒ๋‚œ ๊ฒƒ์ด๋‹ค. + + ์„ ๋ฌผ์˜ ๊ฐ€์น˜๋Š” 100,000๋ณด๋‹ค ์ž‘์€ ์–‘์˜ ์ •์ˆ˜์ด๋‹ค. (1 โ‰ค a โ‰ค 100) + + + [์ถœ๋ ฅ] + + a๊ฐ€ 0์ผ ๋•Œ๋งˆ๋‹ค, ์•„์ด๋“ค์—๊ฒŒ ์ค€ ์„ ๋ฌผ์˜ ๊ฐ€์น˜๋ฅผ ์ถœ๋ ฅํ•˜์‹œ์˜ค. + ๋งŒ์•ฝ ์ค„ ์„ ๋ฌผ์ด ์—†๋‹ค๋ฉด -1์„ ์ถœ๋ ฅํ•˜๋ผ. + ์ ์–ด๋„ ํ•˜๋‚˜์˜ ์ถœ๋ ฅ์ด ์žˆ์Œ์„ ๋ณด์žฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("ํฌ๋ฆฌ์Šค๋งˆ์Šค ์„ ๋ฌผ") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("datastructure,heap,priority_queue") + .build(); + + problemRepository.save(problem); + } + + // 14. ์นด๋“œ ๊ตฌ๋งคํ•˜๊ธฐ + private void createCardBuyingProblem() { + if (problemRepository.existsByTitle("์นด๋“œ ๊ตฌ๋งคํ•˜๊ธฐ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์š”์ฆ˜ ๋ฏผ๊ทœ๋„ค ๋™๋„ค์—์„œ๋Š” ์Šคํƒ€ํŠธ๋งํฌ์—์„œ ๋งŒ๋“  PS์นด๋“œ๋ฅผ ๋ชจ์œผ๋Š” ๊ฒƒ์ด ์œ ํ–‰์ด๋‹ค. + + PS์นด๋“œ๋Š” PS(Problem Solving) ๋ถ„์•ผ์—์„œ ์œ ๋ช…ํ•œ ์‚ฌ๋žŒ๋“ค์˜ ์•„์ด๋””์™€ ์–ผ๊ตด์ด ์ ํ˜€์žˆ๋Š” ์นด๋“œ์ด๋‹ค. + ๊ฐ๊ฐ์˜ ์นด๋“œ์—๋Š” ๋“ฑ๊ธ‰์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ƒ‰์ด ์น ํ•ด์ ธ ์žˆ๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด 8๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. + + ์ „์„ค์นด๋“œ, ๋ ˆ๋“œ์นด๋“œ, ์˜ค๋ Œ์ง€์นด๋“œ, ํผํ”Œ์นด๋“œ, + ๋ธ”๋ฃจ์นด๋“œ, ์ฒญ๋ก์นด๋“œ, ๊ทธ๋ฆฐ์นด๋“œ, ๊ทธ๋ ˆ์ด์นด๋“œ + + ์นด๋“œ๋Š” ์นด๋“œํŒฉ์˜ ํ˜•ํƒœ๋กœ๋งŒ ๊ตฌ๋งคํ•  ์ˆ˜ ์žˆ๊ณ , + ์นด๋“œํŒฉ์˜ ์ข…๋ฅ˜๋Š” ์นด๋“œ 1๊ฐœ๊ฐ€ ํฌํ•จ๋œ ์นด๋“œํŒฉ, ์นด๋“œ 2๊ฐœ๊ฐ€ ํฌํ•จ๋œ ์นด๋“œํŒฉ, ... + ์นด๋“œ N๊ฐœ๊ฐ€ ํฌํ•จ๋œ ์นด๋“œํŒฉ๊ณผ ๊ฐ™์ด ์ด N๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•œ๋‹ค. + + ๋ฏผ๊ทœ๋Š” ์นด๋“œ์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ ์€ ํŒฉ์ด๋”๋ผ๋„ ๊ฐ€๊ฒฉ์ด ๋น„์‹ธ๋ฉด ๋†’์€ ๋“ฑ๊ธ‰์˜ ์นด๋“œ๊ฐ€ ๋งŽ์ด ๋“ค์–ด์žˆ์„ ๊ฒƒ์ด๋ผ๋Š” ๋ฏธ์‹ ์„ ๋ฏฟ๊ณ  ์žˆ๋‹ค. + ๋”ฐ๋ผ์„œ, ๋ฏผ๊ทœ๋Š” ๋ˆ์„ ์ตœ๋Œ€ํ•œ ๋งŽ์ด ์ง€๋ถˆํ•ด์„œ ์นด๋“œ N๊ฐœ๋ฅผ ๊ตฌ๋งคํ•˜๋ ค๊ณ  ํ•œ๋‹ค. + + ์นด๋“œ๊ฐ€ i๊ฐœ ํฌํ•จ๋œ ์นด๋“œํŒฉ์˜ ๊ฐ€๊ฒฉ์€ Pi์›์ด๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ๋ฏผ๊ทœ๊ฐ€ ๊ตฌ๋งคํ•˜๋ ค๊ณ  ํ•˜๋Š” ์นด๋“œ์˜ ๊ฐœ์ˆ˜ N์ด ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค N โ‰ค 1,000) + ๋‘˜์งธ ์ค„์—๋Š” P1๋ถ€ํ„ฐ PN๊นŒ์ง€ ์นด๋“œํŒฉ์˜ ๊ฐ€๊ฒฉ Pi๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค Pi โ‰ค 10,000) + + + [์ถœ๋ ฅ] + + ๋ฏผ๊ทœ๊ฐ€ ์นด๋“œ N๊ฐœ๋ฅผ ๊ฐ–๊ธฐ ์œ„ํ•ด ์ง€๋ถˆํ•ด์•ผ ํ•˜๋Š” ๊ธˆ์•ก์˜ ์ตœ๋Œ“๊ฐ’์„ ์ถœ๋ ฅํ•œ๋‹ค. + ๊ตฌ๋งคํ•œ ์นด๋“œํŒฉ์— ํฌํ•จ๋œ ์นด๋“œ ๊ฐœ์ˆ˜์˜ ํ•ฉ์€ ์ •ํ™•ํžˆ N๊ณผ ๊ฐ™์•„์•ผ ํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์นด๋“œ ๊ตฌ๋งคํ•˜๊ธฐ") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("dp,knapsack") + .build(); + + problemRepository.save(problem); + } + + // 15. ๋ถˆ! + private void createFireEscapeProblem() { + if (problemRepository.existsByTitle("๋ถˆ!")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์ƒ๊ทผ์ด๋Š” ๋นˆ ๊ณต๊ฐ„๊ณผ ๋ฒฝ์œผ๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฑด๋ฌผ์— ๊ฐ‡ํ˜€์žˆ๋‹ค. + ๊ฑด๋ฌผ์˜ ์ผ๋ถ€์—๋Š” ๋ถˆ์ด ๋‚ฌ๊ณ , ์ƒ๊ทผ์ด๋Š” ์ถœ๊ตฌ๋ฅผ ํ–ฅํ•ด ๋›ฐ๊ณ  ์žˆ๋‹ค. + + ๋งค ์ดˆ๋งˆ๋‹ค, ๋ถˆ์€ ๋™์„œ๋‚จ๋ถ ๋ฐฉํ–ฅ์œผ๋กœ ์ธ์ ‘ํ•œ ๋นˆ ๊ณต๊ฐ„์œผ๋กœ ํผ์ ธ๋‚˜๊ฐ„๋‹ค. + ๋ฒฝ์—๋Š” ๋ถˆ์ด ๋ถ™์ง€ ์•Š๋Š”๋‹ค. + ์ƒ๊ทผ์ด๋Š” ๋™์„œ๋‚จ๋ถ ์ธ์ ‘ํ•œ ์นธ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, 1์ดˆ๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค. + ์ƒ๊ทผ์ด๋Š” ๋ฒฝ์„ ํ†ต๊ณผํ•  ์ˆ˜ ์—†๊ณ , + ๋ถˆ์ด ์˜ฎ๊ฒจ์ง„ ์นธ ๋˜๋Š” ์ด์ œ ๋ถˆ์ด ๋ถ™์œผ๋ ค๋Š” ์นธ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์—†๋‹ค. + ์ƒ๊ทผ์ด๊ฐ€ ์žˆ๋Š” ์นธ์— ๋ถˆ์ด ์˜ฎ๊ฒจ์˜ด๊ณผ ๋™์‹œ์— ๋‹ค๋ฅธ ์นธ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋‹ค. + + ๋นŒ๋”ฉ์˜ ์ง€๋„๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ, + ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ๋นŒ๋”ฉ์„ ํƒˆ์ถœํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (์ตœ๋Œ€ 100๊ฐœ) + + ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์ฒซ์งธ ์ค„์—๋Š” ๋นŒ๋”ฉ ์ง€๋„์˜ ๋„ˆ๋น„ w์™€ ๋†’์ด h๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค w, h โ‰ค 1000) + ๋‹ค์Œ h๊ฐœ ์ค„์—๋Š” w๊ฐœ์˜ ๋ฌธ์ž๋กœ ๋นŒ๋”ฉ์˜ ์ง€๋„๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + '.' : ๋นˆ ๊ณต๊ฐ„ + '#' : ๋ฒฝ + '@' : ์ƒ๊ทผ์ด์˜ ์‹œ์ž‘ ์œ„์น˜ + '*' : ๋ถˆ + + + [์ถœ๋ ฅ] + + ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋งˆ๋‹ค ๋นŒ๋”ฉ์„ ํƒˆ์ถœํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ๋น ๋ฅธ ์‹œ๊ฐ„์„ ์ถœ๋ ฅํ•œ๋‹ค. + ๋นŒ๋”ฉ์„ ํƒˆ์ถœํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” "IMPOSSIBLE"์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๋ถˆ!") + .difficulty(Difficulty.MEDIUM) + .description(description) + .tags("bfs,graph,multi_source_bfs") + .build(); + + problemRepository.save(problem); + } + + // ========================= + // HARD ๋ฌธ์ œ๋“ค + // ========================= + + // 16. ๊ตฌ์Šฌ ํƒˆ์ถœ (์ด๋ฏธ ๊ธฐ์กด์— ์žˆ๋˜ ๋ฌธ์ œ) + private void createMarbleEscapeProblem() { + if (problemRepository.existsByTitle("๊ตฌ์Šฌ ํƒˆ์ถœ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์Šคํƒ€ํŠธ๋งํฌ์—์„œ ํŒ๋งคํ•˜๋Š” ์–ด๋ฆฐ์ด์šฉ ์žฅ๋‚œ๊ฐ ์ค‘์—์„œ ๊ฐ€์žฅ ์ธ๊ธฐ๊ฐ€ ๋งŽ์€ ์ œํ’ˆ์€ ๊ตฌ์Šฌ ํƒˆ์ถœ์ด๋‹ค. + ๊ตฌ์Šฌ ํƒˆ์ถœ์€ ์ง์‚ฌ๊ฐํ˜• ๋ณด๋“œ์— ๋นจ๊ฐ„ ๊ตฌ์Šฌ๊ณผ ํŒŒ๋ž€ ๊ตฌ์Šฌ์„ ํ•˜๋‚˜์”ฉ ๋„ฃ์€ ๋‹ค์Œ, + ๋นจ๊ฐ„ ๊ตฌ์Šฌ์„ ๊ตฌ๋ฉ์„ ํ†ตํ•ด ๋นผ๋‚ด๋Š” ๊ฒŒ์ž„์ด๋‹ค. + + ๋ณด๋“œ์˜ ์„ธ๋กœ ํฌ๊ธฐ๋Š” N, ๊ฐ€๋กœ ํฌ๊ธฐ๋Š” M์ด๊ณ , ํŽธ์˜์ƒ 1ร—1 ํฌ๊ธฐ์˜ ์นธ์œผ๋กœ ๋‚˜๋ˆ„์–ด์ ธ ์žˆ๋‹ค. + ๊ฐ€์žฅ ๋ฐ”๊นฅ ํ–‰๊ณผ ์—ด์€ ๋ชจ๋‘ ๋ง‰ํ˜€์ ธ ์žˆ๊ณ , ๋ณด๋“œ์—๋Š” ๊ตฌ๋ฉ์ด ํ•˜๋‚˜ ์žˆ๋‹ค. + ๋นจ๊ฐ„ ๊ตฌ์Šฌ๊ณผ ํŒŒ๋ž€ ๊ตฌ์Šฌ์˜ ํฌ๊ธฐ๋Š” ๋ณด๋“œ์—์„œ 1ร—1 ํฌ๊ธฐ์˜ ์นธ์„ ๊ฐ€๋“ ์ฑ„์šฐ๋Š” ์‚ฌ์ด์ฆˆ์ด๊ณ , + ๊ฐ๊ฐ ํ•˜๋‚˜์”ฉ ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค. + + ์ด๋•Œ, ๊ตฌ์Šฌ์„ ์†์œผ๋กœ ๊ฑด๋“œ๋ฆด ์ˆ˜๋Š” ์—†๊ณ , ์ค‘๋ ฅ์„ ์ด์šฉํ•ด์„œ ์ด๋ฆฌ์ €๋ฆฌ ๊ตด๋ ค์•ผ ํ•œ๋‹ค. + ์™ผ์ชฝ์œผ๋กœ ๊ธฐ์šธ์ด๊ธฐ, ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๊ธฐ์šธ์ด๊ธฐ, ์œ„์ชฝ์œผ๋กœ ๊ธฐ์šธ์ด๊ธฐ, + ์•„๋ž˜์ชฝ์œผ๋กœ ๊ธฐ์šธ์ด๊ธฐ์™€ ๊ฐ™์€ ๋„ค ๊ฐ€์ง€ ๋™์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค. + + ๊ฐ๊ฐ์˜ ๋™์ž‘์—์„œ ๊ณต์€ ๋™์‹œ์— ์›€์ง์ธ๋‹ค. + ๋นจ๊ฐ„ ๊ตฌ์Šฌ์ด ๊ตฌ๋ฉ์— ๋น ์ง€๋ฉด ์„ฑ๊ณต์ด์ง€๋งŒ, ํŒŒ๋ž€ ๊ตฌ์Šฌ์ด ๊ตฌ๋ฉ์— ๋น ์ง€๋ฉด ์‹คํŒจ์ด๋‹ค. + ๋นจ๊ฐ„ ๊ตฌ์Šฌ๊ณผ ํŒŒ๋ž€ ๊ตฌ์Šฌ์ด ๋™์‹œ์— ๊ตฌ๋ฉ์— ๋น ์ ธ๋„ ์‹คํŒจ์ด๋‹ค. + ๋นจ๊ฐ„ ๊ตฌ์Šฌ๊ณผ ํŒŒ๋ž€ ๊ตฌ์Šฌ์€ ๋™์‹œ์— ๊ฐ™์€ ์นธ์— ์žˆ์„ ์ˆ˜ ์—†๋‹ค. + ๋˜, ๋นจ๊ฐ„ ๊ตฌ์Šฌ๊ณผ ํŒŒ๋ž€ ๊ตฌ์Šฌ์˜ ํฌ๊ธฐ๋Š” ํ•œ ์นธ์„ ๋ชจ๋‘ ์ฐจ์ง€ํ•œ๋‹ค. + ๊ธฐ์šธ์ด๋Š” ๋™์ž‘์„ ๊ทธ๋งŒํ•˜๋Š” ๊ฒƒ์€ ๋” ์ด์ƒ ๊ตฌ์Šฌ์ด ์›€์ง์ด์ง€ ์•Š์„ ๋•Œ๊นŒ์ง€์ด๋‹ค. + + ๋ณด๋“œ์˜ ์ƒํƒœ๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ, + ์ตœ์†Œ ๋ช‡ ๋ฒˆ ๋งŒ์— ๋นจ๊ฐ„ ๊ตฌ์Šฌ์„ ๊ตฌ๋ฉ์„ ํ†ตํ•ด ๋นผ๋‚ผ ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ ๋ฒˆ์งธ ์ค„์—๋Š” ๋ณด๋“œ์˜ ์„ธ๋กœ, ๊ฐ€๋กœ ํฌ๊ธฐ๋ฅผ ์˜๋ฏธํ•˜๋Š” ๋‘ ์ •์ˆ˜ N, M (3 โ‰ค N, M โ‰ค 10)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ N๊ฐœ์˜ ์ค„์— ๋ณด๋“œ์˜ ๋ชจ์–‘์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ธธ์ด M์˜ ๋ฌธ์ž์—ด์ด ์ฃผ์–ด์ง„๋‹ค. + ์ด ๋ฌธ์ž์—ด์€ '.', '#', 'O', 'R', 'B' ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค. + '.'์€ ๋นˆ ์นธ์„ ์˜๋ฏธํ•˜๊ณ , '#'์€ ๊ณต์ด ์ด๋™ํ•  ์ˆ˜ ์—†๋Š” ์žฅ์• ๋ฌผ ๋˜๋Š” ๋ฒฝ์„ ์˜๋ฏธํ•˜๋ฉฐ, + 'O'๋Š” ๊ตฌ๋ฉ์˜ ์œ„์น˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค. + 'R'์€ ๋นจ๊ฐ„ ๊ตฌ์Šฌ์˜ ์œ„์น˜, 'B'๋Š” ํŒŒ๋ž€ ๊ตฌ์Šฌ์˜ ์œ„์น˜์ด๋‹ค. + + ์ž…๋ ฅ๋˜๋Š” ๋ชจ๋“  ๋ณด๋“œ์˜ ๊ฐ€์žฅ์ž๋ฆฌ์—๋Š” ๋ชจ๋‘ '#'์ด ์žˆ๋‹ค. + ๊ตฌ๋ฉ์˜ ๊ฐœ์ˆ˜๋Š” ํ•œ ๊ฐœ์ด๋ฉฐ, ๋นจ๊ฐ„ ๊ตฌ์Šฌ๊ณผ ํŒŒ๋ž€ ๊ตฌ์Šฌ์€ ํ•ญ์ƒ 1๊ฐœ๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ตœ์†Œ ๋ช‡ ๋ฒˆ ๋งŒ์— ๋นจ๊ฐ„ ๊ตฌ์Šฌ์„ ๊ตฌ๋ฉ์„ ํ†ตํ•ด ๋นผ๋‚ผ ์ˆ˜ ์žˆ๋Š”์ง€ ์ถœ๋ ฅํ•œ๋‹ค. + ๋งŒ์•ฝ, 10๋ฒˆ ์ดํ•˜๋กœ ์›€์ง์—ฌ์„œ ๋นจ๊ฐ„ ๊ตฌ์Šฌ์„ ๊ตฌ๋ฉ์„ ํ†ตํ•ด ๋นผ๋‚ผ ์ˆ˜ ์—†์œผ๋ฉด -1์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๊ตฌ์Šฌ ํƒˆ์ถœ") + .difficulty(Difficulty.HARD) + .description(description) + .tags("simulation,bfs,implementation") + .build(); + + problemRepository.save(problem); + } + + // 17. ๋งˆ๋ฒ•์‚ฌ ์ƒ์–ด์™€ ๋ณต์ œ + private void createSharkCopyMagicProblem() { + if (problemRepository.existsByTitle("๋งˆ๋ฒ•์‚ฌ ์ƒ์–ด์™€ ๋ณต์ œ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ๋งˆ๋ฒ•์‚ฌ ์ƒ์–ด๋Š” ๋ฌผ๋ณต์‚ฌ๋ฒ„๊ทธ ๋งˆ๋ฒ•์˜ ์ƒ์œ„ ๋งˆ๋ฒ•์ธ ๋ณต์ œ๋ฅผ ๋ฐฐ์› ๊ณ , + 4 ร— 4 ํฌ๊ธฐ์˜ ๊ฒฉ์ž์—์„œ ์—ฐ์Šตํ•˜๋ ค๊ณ  ํ•œ๋‹ค. + + ๊ฒฉ์ž์—๋Š” ๋ฌผ๊ณ ๊ธฐ M๋งˆ๋ฆฌ๊ฐ€ ์žˆ๋‹ค. + ๊ฐ ๋ฌผ๊ณ ๊ธฐ๋Š” ๊ฒฉ์ž์˜ ์นธ ํ•˜๋‚˜์— ๋“ค์–ด๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ด๋™ ๋ฐฉํ–ฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. + ์ด๋™ ๋ฐฉํ–ฅ์€ 8๊ฐ€์ง€ ๋ฐฉํ–ฅ(์ƒํ•˜์ขŒ์šฐ, ๋Œ€๊ฐ์„ ) ์ค‘ ํ•˜๋‚˜์ด๋‹ค. + ๋งˆ๋ฒ•์‚ฌ ์ƒ์–ด๋„ ์—ฐ์Šต์„ ์œ„ํ•ด ๊ฒฉ์ž์— ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค. + + ์ƒ์–ด์˜ ๋งˆ๋ฒ• ์—ฐ์Šต ํ•œ ๋ฒˆ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค. + + 1. ์ƒ์–ด๊ฐ€ ๋ชจ๋“  ๋ฌผ๊ณ ๊ธฐ์—๊ฒŒ ๋ณต์ œ ๋งˆ๋ฒ•์„ ์‹œ์ „ํ•œ๋‹ค. + 2. ๋ชจ๋“  ๋ฌผ๊ณ ๊ธฐ๊ฐ€ ํ•œ ์นธ ์ด๋™ํ•œ๋‹ค. + 3. ์ƒ์–ด๊ฐ€ ์—ฐ์†ํ•ด์„œ 3์นธ ์ด๋™ํ•˜๋ฉด์„œ ๋ฌผ๊ณ ๊ธฐ๋ฅผ ๋จน๊ณ  ๋ƒ„์ƒˆ๋ฅผ ๋‚จ๊ธด๋‹ค. + 4. ๋‘ ๋ฒˆ ์ „ ์—ฐ์Šต์—์„œ ์ƒ๊ธด ๋ฌผ๊ณ ๊ธฐ์˜ ๋ƒ„์ƒˆ๊ฐ€ ๊ฒฉ์ž์—์„œ ์‚ฌ๋ผ์ง„๋‹ค. + 5. 1์—์„œ ์‚ฌ์šฉ๋œ ๋ณต์ œ ๋งˆ๋ฒ•์ด ์™„๋ฃŒ๋˜์–ด ๋ณต์ œ๋œ ๋ฌผ๊ณ ๊ธฐ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ๋ฌผ๊ณ ๊ธฐ์˜ ์ˆ˜ M, ์—ฐ์Šต ํšŸ์ˆ˜ S๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ M๊ฐœ์˜ ์ค„์—๋Š” ๋ฌผ๊ณ ๊ธฐ์˜ ์ •๋ณด (fx, fy, d)๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๋งˆ์ง€๋ง‰ ์ค„์—๋Š” ์ƒ์–ด์˜ ์œ„์น˜ (sx, sy)๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + S๋ฒˆ์˜ ์—ฐ์Šต์„ ๋งˆ์นœ ํ›„ ๊ฒฉ์ž์— ์žˆ๋Š” ๋ฌผ๊ณ ๊ธฐ์˜ ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๋งˆ๋ฒ•์‚ฌ ์ƒ์–ด์™€ ๋ณต์ œ") + .difficulty(Difficulty.HARD) + .description(description) + .tags("simulation,backtracking,implementation") + .build(); + + problemRepository.save(problem); + } + + // 18. ๋น„์Šทํ•œ ๋‹จ์–ด + private void createSimilarWordsProblem() { + if (problemRepository.existsByTitle("๋น„์Šทํ•œ ๋‹จ์–ด")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + N๊ฐœ์˜ ์˜๋‹จ์–ด๋“ค์ด ์ฃผ์–ด์กŒ์„ ๋•Œ, ๊ฐ€์žฅ ๋น„์Šทํ•œ ๋‘ ๋‹จ์–ด๋ฅผ ๊ตฌํ•ด๋‚ด๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + ๋‘ ๋‹จ์–ด์˜ ๋น„์Šทํ•œ ์ •๋„๋Š” ๋‘ ๋‹จ์–ด์˜ ์ ‘๋‘์‚ฌ์˜ ๊ธธ์ด๋กœ ์ธก์ •ํ•œ๋‹ค. + ์ ‘๋‘์‚ฌ๋ž€ ๋‘ ๋‹จ์–ด์˜ ์•ž๋ถ€๋ถ„์—์„œ ๊ณตํ†ต์ ์œผ๋กœ ๋‚˜ํƒ€๋‚˜๋Š” ๋ถ€๋ถ„๋ฌธ์ž์—ด์„ ๋งํ•œ๋‹ค. + ์ฆ‰, ๋‘ ๋‹จ์–ด์˜ ์•ž์—์„œ๋ถ€ํ„ฐ M๊ฐœ์˜ ๊ธ€์ž๋“ค์ด ๊ฐ™์œผ๋ฉด์„œ M์ด ์ตœ๋Œ€์ธ ๊ฒฝ์šฐ๋ฅผ ๊ตฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— N(2 โ‰ค N โ‰ค 20,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ N๊ฐœ์˜ ์ค„์— ์•ŒํŒŒ๋ฒณ ์†Œ๋ฌธ์ž๋กœ๋งŒ ์ด๋ฃจ์–ด์ง„ ๊ธธ์ด 100์ž ์ดํ•˜์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์˜๋‹จ์–ด๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ๊ฐ€์žฅ ๋น„์Šทํ•œ ๋‘ ๋‹จ์–ด S, T๋ฅผ ํ•œ ์ค„์— ํ•˜๋‚˜์”ฉ ์ถœ๋ ฅํ•œ๋‹ค. + ๋‘ ๋‹จ์–ด๋Š” ์„œ๋กœ ๋‹ฌ๋ผ์•ผ ํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๋น„์Šทํ•œ ๋‹จ์–ด") + .difficulty(Difficulty.HARD) + .description(description) + .tags("string,sorting") + .build(); + + problemRepository.save(problem); + } + + // 19. ๋ณด์„ ๋„๋‘‘ + private void createJewelThiefProblem() { + if (problemRepository.existsByTitle("๋ณด์„ ๋„๋‘‘")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์„ธ๊ณ„์ ์ธ ๋„๋‘‘ ์ƒ๋•์ด๋Š” ๋ณด์„์ ์„ ํ„ธ๊ธฐ๋กœ ๊ฒฐ์‹ฌํ–ˆ๋‹ค. + ์ƒ๋•์ด๊ฐ€ ํ„ธ ๋ณด์„์ ์—๋Š” ๋ณด์„์ด ์ด N๊ฐœ ์žˆ๋‹ค. + ๊ฐ ๋ณด์„์€ ๋ฌด๊ฒŒ Mi์™€ ๊ฐ€๊ฒฉ Vi๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. + + ์ƒ๋•์ด๋Š” ๊ฐ€๋ฐฉ์„ K๊ฐœ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , + ๊ฐ ๊ฐ€๋ฐฉ์— ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋ฌด๊ฒŒ๋Š” Ci์ด๋‹ค. + ๊ฐ€๋ฐฉ์—๋Š” ์ตœ๋Œ€ ํ•œ ๊ฐœ์˜ ๋ณด์„๋งŒ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. + + ์ƒ๋•์ด๊ฐ€ ํ›”์น  ์ˆ˜ ์žˆ๋Š” ๋ณด์„์˜ ์ตœ๋Œ€ ๊ฐ€๊ฒฉ ํ•ฉ์„ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— N๊ณผ K๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค N, K โ‰ค 300,000) + ๋‹ค์Œ N๊ฐœ ์ค„์—๋Š” ๊ฐ ๋ณด์„์˜ ์ •๋ณด Mi์™€ Vi๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ K๊ฐœ ์ค„์—๋Š” ๊ฐ€๋ฐฉ์— ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋ฌด๊ฒŒ Ci๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ƒ๋•์ด๊ฐ€ ํ›”์น  ์ˆ˜ ์žˆ๋Š” ๋ณด์„ ๊ฐ€๊ฒฉ์˜ ํ•ฉ์˜ ์ตœ๋Œ“๊ฐ’์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๋ณด์„ ๋„๋‘‘") + .difficulty(Difficulty.HARD) + .description(description) + .tags("greedy,sorting,priority_queue") + .build(); + + problemRepository.save(problem); + } + + // 20. ํ™”์„ฑ ํƒ์‚ฌ + private void createMarsExplorationProblem() { + if (problemRepository.existsByTitle("ํ™”์„ฑ ํƒ์‚ฌ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + NASA์—์„œ๋Š” ํ™”์„ฑ ํƒ์‚ฌ๋ฅผ ์œ„ํ•ด ํ™”์„ฑ์— ๋ฌด์„  ์กฐ์ข… ๋กœ๋ด‡์„ ๋ณด๋ƒˆ๋‹ค. + ์‹ค์ œ ํ™”์„ฑ์˜ ๋ชจ์Šต์€ ๋ณต์žกํ•˜์ง€๋งŒ, + ๋กœ๋ด‡์˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์ ๊ธฐ ๋•Œ๋ฌธ์— ์ง€ํ˜•์„ Nร—M ๋ฐฐ์—ด๋กœ ๋‹จ์ˆœํ™”ํ•˜์—ฌ ์ƒ๊ฐํ•˜๊ธฐ๋กœ ํ•œ๋‹ค. + + ์ง€ํ˜•์˜ ํŠน์„ฑ์ƒ, ๋กœ๋ด‡์€ ๋ฐฐ์—ด์—์„œ ์™ผ์ชฝ, ์˜ค๋ฅธ์ชฝ, ์•„๋ž˜์ชฝ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, + ์œ„์ชฝ์œผ๋กœ๋Š” ์ด๋™ํ•  ์ˆ˜ ์—†๋‹ค. + ๋˜ํ•œ ํ•œ ๋ฒˆ ํƒ์‚ฌํ•œ ์ง€์—ญ์€ ๋‹ค์‹œ ํƒ์‚ฌํ•˜์ง€ ์•Š๋Š”๋‹ค. + + ๊ฐ๊ฐ์˜ ์ง€์—ญ์€ ํƒ์‚ฌ ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š”๋ฐ, + ๋กœ๋ด‡์„ ๋ฐฐ์—ด์˜ ์™ผ์ชฝ ์œ„ (1, 1)์—์„œ ์ถœ๋ฐœ์‹œ์ผœ ์˜ค๋ฅธ์ชฝ ์•„๋ž˜ (N, M)์œผ๋กœ ๋ณด๋‚ด๋ ค๊ณ  ํ•œ๋‹ค. + ์ด๋•Œ, ์œ„์˜ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋ฉด์„œ ํƒ์‚ฌํ•œ ์ง€์—ญ๋“ค์˜ ๊ฐ€์น˜ ํ•ฉ์˜ ์ตœ๋Œ“๊ฐ’์„ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— N, M(1 โ‰ค N, M โ‰ค 1,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ N๊ฐœ์˜ ์ค„์—๋Š” M๊ฐœ์˜ ์ •์ˆ˜๋กœ ๋ฐฐ์—ด์ด ์ฃผ์–ด์ง„๋‹ค. + ๊ฐ ๊ฐ’์€ ๊ทธ ์ง€์—ญ์˜ ๊ฐ€์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ์ ˆ๋Œ“๊ฐ’์ด 100์„ ๋„˜์ง€ ์•Š๋Š”๋‹ค. + + + [์ถœ๋ ฅ] + + ์ตœ๋Œ€ ๊ฐ€์น˜์˜ ํ•ฉ์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("ํ™”์„ฑ ํƒ์‚ฌ") + .difficulty(Difficulty.HARD) + .description(description) + .tags("dp") + .build(); + + problemRepository.save(problem); + } + + // 21. ์ˆœํšŒ๊ฐ•์—ฐ + private void createLectureTourProblem() { + if (problemRepository.existsByTitle("์ˆœํšŒ๊ฐ•์—ฐ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ํ•œ ์ €๋ช…ํ•œ ํ•™์ž์—๊ฒŒ n(0 โ‰ค n โ‰ค 10,000)๊ฐœ์˜ ๋Œ€ํ•™์—์„œ ๊ฐ•์—ฐ ์š”์ฒญ์„ ํ•ด ์™”๋‹ค. + ๊ฐ ๋Œ€ํ•™์—์„œ๋Š” d(1 โ‰ค d โ‰ค 10,000)์ผ ์•ˆ์— ์™€์„œ ๊ฐ•์—ฐ์„ ํ•ด ์ฃผ๋ฉด + p(1 โ‰ค p โ‰ค 10,000)๋งŒํผ์˜ ๊ฐ•์—ฐ๋ฃŒ๋ฅผ ์ง€๋ถˆํ•˜๊ฒ ๋‹ค๊ณ  ์•Œ๋ ค์™”๋‹ค. + + ์ด ํ•™์ž๋Š” ํ•˜๋ฃจ์— ์ตœ๋Œ€ ํ•œ ๊ณณ์—์„œ๋งŒ ๊ฐ•์—ฐ์„ ํ•  ์ˆ˜ ์žˆ๊ณ , + ๊ฐ€์žฅ ๋งŽ์€ ๋ˆ์„ ๋ฒŒ ์ˆ˜ ์žˆ๋„๋ก ์ผ์ •ํ‘œ๋ฅผ ์งœ๋ ค๊ณ  ํ•œ๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์ •์ˆ˜ n์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‹ค์Œ n๊ฐœ์˜ ์ค„์—๋Š” ๊ฐ ๋Œ€ํ•™์—์„œ ์ œ์‹œํ•œ p์™€ d๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + + [์ถœ๋ ฅ] + + ์ฒซ์งธ ์ค„์— ์ตœ๋Œ€๋กœ ๋ฒŒ ์ˆ˜ ์žˆ๋Š” ๋ˆ์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์ˆœํšŒ๊ฐ•์—ฐ") + .difficulty(Difficulty.HARD) + .description(description) + .tags("greedy,priority_queue,sorting") + .build(); + + problemRepository.save(problem); + } + + // 22. ๊ฐ•์˜์‹ค ๋ฐฐ์ • + private void createLectureRoomAssignmentProblem() { + if (problemRepository.existsByTitle("๊ฐ•์˜์‹ค ๋ฐฐ์ •")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + N๊ฐœ์˜ ๊ฐ•์˜๊ฐ€ ์žˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ชจ๋“  ๊ฐ•์˜์˜ ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ๋๋‚˜๋Š” ์‹œ๊ฐ„์„ ์•Œ๊ณ  ์žˆ๋‹ค. + ์ด๋•Œ, ์šฐ๋ฆฌ๋Š” ์ตœ๋Œ€ํ•œ ์ ์€ ์ˆ˜์˜ ๊ฐ•์˜์‹ค์„ ์‚ฌ์šฉํ•˜์—ฌ + ๋ชจ๋“  ๊ฐ•์˜๊ฐ€ ์ด๋ฃจ์–ด์ง€๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค. + + ํ•œ ๊ฐ•์˜์‹ค์—์„œ๋Š” ๋™์‹œ์— 2๊ฐœ ์ด์ƒ์˜ ๊ฐ•์˜๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์—†๊ณ , + ํ•œ ๊ฐ•์˜์˜ ์ข…๋ฃŒ ์‹œ๊ฐ„๊ณผ ๋‹ค๋ฅธ ๊ฐ•์˜์˜ ์‹œ์ž‘ ์‹œ๊ฐ„์ด ๊ฒน์น˜๋Š” ๊ฒƒ์€ ์ƒ๊ด€์—†๋‹ค. + + ํ•„์š”ํ•œ ์ตœ์†Œ ๊ฐ•์˜์‹ค์˜ ์ˆ˜๋ฅผ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ๊ฐ•์˜์˜ ๊ฐœ์ˆ˜ N(1 โ‰ค N โ‰ค 100,000)์ด ์ฃผ์–ด์ง„๋‹ค. + ๋‘˜์งธ ์ค„๋ถ€ํ„ฐ N๊ฐœ์˜ ์ค„์— ๊ฑธ์ณ ๊ฐ ์ค„๋งˆ๋‹ค ์„ธ ๊ฐœ์˜ ์ •์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + ๊ฐ ์ค„์€ ๊ฐ•์˜ ๋ฒˆํ˜ธ, ๊ฐ•์˜ ์‹œ์ž‘ ์‹œ๊ฐ„, ๊ฐ•์˜ ์ข…๋ฃŒ ์‹œ๊ฐ„์„ ์˜๋ฏธํ•œ๋‹ค. + ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ์ข…๋ฃŒ ์‹œ๊ฐ„์€ 0 ์ด์ƒ 10์–ต ์ดํ•˜์˜ ์ •์ˆ˜์ด๊ณ , ์‹œ์ž‘ < ์ข…๋ฃŒ ์ด๋‹ค. + + + [์ถœ๋ ฅ] + + ํ•„์š”ํ•œ ์ตœ์†Œ ๊ฐ•์˜์‹ค ๊ฐœ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("๊ฐ•์˜์‹ค ๋ฐฐ์ •") + .difficulty(Difficulty.HARD) + .description(description) + .tags("greedy,priority_queue,sorting") + .build(); + + problemRepository.save(problem); + } + + // 23. ์ธ๊ตฌ ์ด๋™ + private void createPopulationMovementProblem() { + if (problemRepository.existsByTitle("์ธ๊ตฌ ์ด๋™")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + Nร—N ํฌ๊ธฐ์˜ ๋•…์ด ์žˆ๊ณ , ๊ฐ๊ฐ์˜ ์นธ์—๋Š” ๋‚˜๋ผ๊ฐ€ ํ•˜๋‚˜์”ฉ ์žˆ๋‹ค. + rํ–‰ c์—ด์— ์žˆ๋Š” ๋‚˜๋ผ์—๋Š” A[r][c]๋ช…์ด ์‚ด๊ณ  ์žˆ๋‹ค. + + ์ธ์ ‘ํ•œ ๋‚˜๋ผ ์‚ฌ์ด์—๋Š” ๊ตญ๊ฒฝ์„ ์ด ์กด์žฌํ•˜๋ฉฐ, + ๋‘ ๋‚˜๋ผ์˜ ์ธ๊ตฌ ์ฐจ์ด๊ฐ€ L๋ช… ์ด์ƒ, R๋ช… ์ดํ•˜๋ผ๋ฉด + ๊ตญ๊ฒฝ์„ ์„ ์˜ค๋Š˜ ํ•˜๋ฃจ ๋™์•ˆ ์—ฐ๋‹ค. + + ๊ตญ๊ฒฝ์„ ์ด ์—ด๋ ค์žˆ์–ด ์ธ์ ‘ํ•œ ์นธ๋งŒ์„ ์ด์šฉํ•ด ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๋‚˜๋ผ๋“ค์„ ์—ฐํ•ฉ์ด๋ผ๊ณ  ํ•˜๊ณ , + ์—ฐํ•ฉ์„ ์ด๋ฃจ๊ณ  ์žˆ๋Š” ๊ฐ ์นธ์˜ ์ธ๊ตฌ์ˆ˜๋Š” + (์—ฐํ•ฉ์˜ ์ธ๊ตฌ์ˆ˜ ํ•ฉ) / (์—ฐํ•ฉ์„ ์ด๋ฃจ๋Š” ์นธ์˜ ๊ฐœ์ˆ˜)๋กœ ๋ฐ”๋€๋‹ค. (์†Œ์ˆ˜์  ๋ฒ„๋ฆผ) + + ๋” ์ด์ƒ ์ธ๊ตฌ ์ด๋™์ด ์ผ์–ด๋‚˜์ง€ ์•Š์„ ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณต๋  ๋•Œ, + ์ธ๊ตฌ ์ด๋™์ด ๋ฉฐ์น  ๋™์•ˆ ๋ฐœ์ƒํ•˜๋Š”์ง€ ๊ตฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜์‹œ์˜ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— N, L, R์ด ์ฃผ์–ด์ง„๋‹ค. (1 โ‰ค N โ‰ค 50, 1 โ‰ค L โ‰ค R โ‰ค 100) + ๋‘˜์งธ ์ค„๋ถ€ํ„ฐ N๊ฐœ์˜ ์ค„์— ๊ฐ ๋‚˜๋ผ์˜ ์ธ๊ตฌ์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (0 โ‰ค A[r][c] โ‰ค 100) + + + [์ถœ๋ ฅ] + + ์ธ๊ตฌ ์ด๋™์ด ๋ฐœ์ƒํ•œ ์ผ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("์ธ๊ตฌ ์ด๋™") + .difficulty(Difficulty.HARD) + .description(description) + .tags("simulation,bfs,graph") + .build(); + + problemRepository.save(problem); + } + + // 24. ํƒˆ์˜ฅ + private void createPrisonBreakProblem() { + if (problemRepository.existsByTitle("ํƒˆ์˜ฅ")) { + return; + } + + String description = """ + [๋ฌธ์ œ] + + ์ƒ๊ทผ์ด๋Š” ๊ฐ์˜ฅ์—์„œ ์ฃ„์ˆ˜ ๋‘ ๋ช…์„ ํƒˆ์˜ฅ์‹œ์ผœ์•ผ ํ•œ๋‹ค. + ๊ฐ์˜ฅ์€ 1์ธต์งœ๋ฆฌ ๊ฑด๋ฌผ์ด๊ณ , ๊ฐ์˜ฅ์˜ ํ‰๋ฉด๋„๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. + + ํ‰๋ฉด๋„์—๋Š” ๋ชจ๋“  ๋ฒฝ๊ณผ ๋ฌธ์ด ๋‚˜ํƒ€๋‚˜ ์žˆ๊ณ , ์ฃ„์ˆ˜์˜ ์œ„์น˜๋„ ๋‚˜ํƒ€๋‚˜ ์žˆ๋‹ค. + ๊ฐ์˜ฅ์€ ๋ฌด์ธ ๊ฐ์˜ฅ์œผ๋กœ ์ฃ„์ˆ˜ ๋‘ ๋ช…์ด ๊ฐ์˜ฅ์— ์žˆ๋Š” ์œ ์ผํ•œ ์‚ฌ๋žŒ์ด๋‹ค. + + ๋ฌธ์€ ์ค‘์•™ ์ œ์–ด์‹ค์—์„œ๋งŒ ์—ด ์ˆ˜ ์žˆ์ง€๋งŒ, + ์ƒ๊ทผ์ด๋Š” ํŠน๋ณ„ํ•œ ๊ธฐ์ˆ ์„ ์ด์šฉํ•ด ์ œ์–ด์‹ค์„ ํ†ตํ•˜์ง€ ์•Š๊ณ  ๋ฌธ์„ ์—ด๋ ค๊ณ  ํ•œ๋‹ค. + ํ•˜์ง€๋งŒ ๋ฌธ์„ ์—ด๋ ค๋ฉด ์‹œ๊ฐ„์ด ๋งค์šฐ ๋งŽ์ด ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—, + ๋‘ ์ฃ„์ˆ˜๋ฅผ ํƒˆ์˜ฅ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์—ด์–ด์•ผ ํ•˜๋Š” ๋ฌธ์˜ ๊ฐœ์ˆ˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๋ ค๊ณ  ํ•œ๋‹ค. + + ๋ฌธ์„ ํ•œ ๋ฒˆ ์—ด๋ฉด ๊ณ„์† ์—ด๋ฆฐ ์ƒํƒœ๋กœ ์žˆ๋Š”๋‹ค. + + + [์ž…๋ ฅ] + + ์ฒซ์งธ ์ค„์— ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (100 ์ดํ•˜) + + ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์ฒซ ์ค„์—๋Š” ํ‰๋ฉด๋„์˜ ๋†’์ด h์™€ ๋„ˆ๋น„ w๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. (2 โ‰ค h, w โ‰ค 100) + ๋‹ค์Œ h๊ฐœ ์ค„์—๋Š” ๊ฐ์˜ฅ์˜ ํ‰๋ฉด๋„ ์ •๋ณด๊ฐ€ ์ฃผ์–ด์ง€๋ฉฐ, + ๋นˆ ๊ณต๊ฐ„์€ '.', ๋ฒฝ์€ '*', ๋ฌธ์€ '#', ์ฃ„์ˆ˜๋Š” '$'๋กœ ์ฃผ์–ด์ง„๋‹ค. + + ์ƒ๊ทผ์ด๋Š” ๊ฐ์˜ฅ ๋ฐ–์„ ์ž์œ ๋กญ๊ฒŒ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ณ , + ํ‰๋ฉด๋„์— ํ‘œ์‹œ๋œ ์ฃ„์ˆ˜์˜ ์ˆ˜๋Š” ํ•ญ์ƒ ๋‘ ๋ช…์ด๋‹ค. + + + [์ถœ๋ ฅ] + + ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋งˆ๋‹ค ๋‘ ์ฃ„์ˆ˜๋ฅผ ํƒˆ์˜ฅ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ + ์—ด์–ด์•ผ ํ•˜๋Š” ๋ฌธ์˜ ์ตœ์†Ÿ๊ฐ’์„ ์ถœ๋ ฅํ•œ๋‹ค. + """; + + CodingProblem problem = CodingProblem.builder() + .title("ํƒˆ์˜ฅ") + .difficulty(Difficulty.HARD) + .description(description) + .tags("graph,bfs,0_1_bfs") + .build(); + + problemRepository.save(problem); + } +} diff --git a/src/main/java/com/example/skillboost/codingtest/judge/GeminiJudge.java b/src/main/java/com/example/skillboost/codingtest/judge/GeminiJudge.java new file mode 100644 index 0000000..ecb286b --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/judge/GeminiJudge.java @@ -0,0 +1,249 @@ +package com.example.skillboost.codingtest.judge; + +import com.example.skillboost.codingtest.domain.CodingProblem; +import com.example.skillboost.codingtest.dto.SubmissionResultDto; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; + +/** + * Gemini API๋ฅผ ์ด์šฉํ•ด + * - ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ์ฑ„์ ํ•˜๊ณ  + * - ํ•œ๊ตญ์–ด ์ฝ”๋“œ ๋ฆฌ๋ทฐ(aiFeedback) + * - ์˜ˆ์ƒ ๋ฉด์ ‘ ์งˆ๋ฌธ(interviewQuestions) + * ์„ ์ƒ์„ฑํ•˜๋Š” Judge. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GeminiJudge { + + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate = new RestTemplate(); + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.model:gemini-2.0-flash}") + private String model; + + /** + * AI ์ฑ„์  ๋ฉ”์ธ ๋กœ์ง + */ + public SubmissionResultDto grade(CodingProblem problem, String userCode, String language) { + try { + // 1) ํ”„๋กฌํ”„ํŠธ ๋งŒ๋“ค๊ธฐ + String prompt = buildPrompt(problem, language, userCode); + + // 2) Gemini ์š”์ฒญ ๋ฐ”๋”” ๋งŒ๋“ค๊ธฐ + ObjectNode root = objectMapper.createObjectNode(); + ArrayNode contents = objectMapper.createArrayNode(); + ObjectNode content = objectMapper.createObjectNode(); + ArrayNode parts = objectMapper.createArrayNode(); + ObjectNode part = objectMapper.createObjectNode(); + part.put("text", prompt); + parts.add(part); + content.set("parts", parts); + contents.add(content); + root.set("contents", contents); + + String body = objectMapper.writeValueAsString(root); + + String url = + "https://generativelanguage.googleapis.com/v1beta/models/" + + model + + ":generateContent?key=" + + apiKey; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + new HttpEntity<>(body, headers), + String.class + ); + + if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) { + log.error("Gemini API ํ˜ธ์ถœ ์‹คํŒจ: {}", response.getBody()); + return buildErrorResult("AI ์ฑ„์  ์„œ๋ฒ„ ์‘๋‹ต์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); + } + + // 3) Gemini ์‘๋‹ต ํŒŒ์‹ฑ + JsonNode rootNode = objectMapper.readTree(response.getBody()); + + // ์ตœ์‹  Gemini ์‘๋‹ต๊ตฌ์กฐ: candidates โ†’ content โ†’ parts โ†’ text + JsonNode candidates = rootNode.path("candidates"); + if (!candidates.isArray() || candidates.size() == 0) { + log.error("Gemini ์‘๋‹ต์— candidates ์—†์Œ: {}", response.getBody()); + return buildErrorResult("AI ์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค."); + } + + JsonNode contentNode = candidates.get(0).path("content"); + JsonNode partsNode = contentNode.path("parts"); + if (!partsNode.isArray() || partsNode.size() == 0) { + log.error("Gemini ์‘๋‹ต์— parts ์—†์Œ: {}", response.getBody()); + return buildErrorResult("AI ์‘๋‹ต ํŒŒ์‹ฑ ์‹คํŒจ."); + } + + String rawText = partsNode.get(0).path("text").asText(); + if (rawText == null || rawText.isBlank()) { + log.error("Gemini ์‘๋‹ต text ์—†์Œ: {}", response.getBody()); + return buildErrorResult("AI ์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค."); + } + + // ๐Ÿ”ฅ 4) text ์•ˆ์—์„œ JSON ๋ถ€๋ถ„๋งŒ ์ถ”์ถœ + String jsonString = extractJsonString(rawText); + if (jsonString == null) { + log.error("Gemini ์‘๋‹ต์—์„œ JSON ๋ถ€๋ถ„ ์ถ”์ถœ ์‹คํŒจ. rawText={}", rawText); + return buildErrorResult("AI ์‘๋‹ต JSON ํŒŒ์‹ฑ ์‹คํŒจ"); + } + + // 5) JSON ํŒŒ์‹ฑ + JsonNode json; + try { + json = objectMapper.readTree(jsonString); + } catch (Exception e) { + log.error("AI JSON ํŒŒ์‹ฑ ์‹คํŒจ. jsonString={}", jsonString, e); + return buildErrorResult("AI ์‘๋‹ต JSON ํŒŒ์‹ฑ ์‹คํŒจ"); + } + + // 6) AI ๊ฒฐ๊ณผ ํ•ด์„ + String status = json.path("status").asText("WA"); // ๊ธฐ๋ณธ๊ฐ’ WA + int score = json.path("score").asInt(0); + String feedback = json.path("feedback").asText(""); + + // 7) ๋ฉด์ ‘ ์งˆ๋ฌธ ํŒŒ์‹ฑ + List interviewQuestions = new ArrayList<>(); + JsonNode qNode = json.path("interviewQuestions"); + if (qNode.isArray()) { + for (JsonNode q : qNode) { + if (q.isTextual()) interviewQuestions.add(q.asText()); + } + } + + // 8) ํ…Œ์ŠคํŠธ์ผ€์ด์Šค ๊ธฐ๋ฐ˜ ์ ์ˆ˜ ๊ณ„์‚ฐ (๋ฌธ์ œ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜) + Integer totalTestCases = problem.getTestCases() != null + ? problem.getTestCases().size() + : null; + Integer passedCount = null; + if (totalTestCases != null && totalTestCases > 0) { + passedCount = (int) Math.round(totalTestCases * (score / 100.0)); + } + + // 9) ์ตœ์ข… ๋ฐ˜ํ™˜ + return SubmissionResultDto.builder() + .status(status) + .score(score) + .passedCount(passedCount) + .totalCount(totalTestCases) + .message(status.equals("AC") ? "์ •๋‹ต์ž…๋‹ˆ๋‹ค! ๐ŸŽ‰" : "์˜ค๋‹ต์ž…๋‹ˆ๋‹ค.") + .aiFeedback(feedback) + .interviewQuestions(interviewQuestions) + .build(); + + } catch (Exception e) { + log.error("AI ์ฑ„์  ์‹คํŒจ", e); + return buildErrorResult("AI ์ฑ„์  ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ"); + } + } + + /** + * AI ์‹คํŒจ fallback + */ + private SubmissionResultDto buildErrorResult(String message) { + List fallbackQuestions = List.of( + "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„ ํƒํ•œ ์ž๋ฃŒ๊ตฌ์กฐ์™€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.", + "์‹œ๊ฐ„ ๋ณต์žก๋„๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ์–ด๋–ค ๊ฐœ์„ ์ด ๊ฐ€๋Šฅํ• ๊นŒ์š”?", + "๊ทน๋‹จ์ ์ธ ์ž…๋ ฅ๊ฐ’์ด ๋“ค์–ด์™”์„ ๋•Œ ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?" + ); + + return SubmissionResultDto.builder() + .status("WA") // ์‹คํŒจ ์‹œ ์ ˆ๋Œ€ AC๋กœ ๋ณด์ด์ง€ ์•Š๊ฒŒ + .score(0) + .message(message) + .aiFeedback("AI ๋ถ„์„ ์‹คํŒจ: " + message) + .interviewQuestions(fallbackQuestions) + .build(); + } + + /** + * ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ + */ + private String buildPrompt(CodingProblem problem, String language, String userCode) { + return """ + ๋„ˆ๋Š” ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ ๋ฌธ์ œ๋ฅผ ์ฑ„์ ํ•˜๋Š” ํ•œ๊ตญ์ธ ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž์ด๋‹ค. + + ์•„๋ž˜ ๋ฌธ์ œ์™€ ์‚ฌ์šฉ์ž์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๊ณ  JSON๋งŒ ์ถœ๋ ฅํ•ด๋ผ. + + ์˜ค์ง ์•„๋ž˜ JSON ํ˜•์‹๋งŒ, ์•ž๋’ค ์„ค๋ช… ์—†์ด ์ถœ๋ ฅํ•ด์•ผ ํ•œ๋‹ค: + + { + "status": "AC" ๋˜๋Š” "WA", + "score": 0~100, + "feedback": "ํ•œ๊ตญ์–ด ์ฝ”๋“œ ๋ฆฌ๋ทฐ", + "interviewQuestions": [ + "์งˆ๋ฌธ1", + "์งˆ๋ฌธ2", + "์งˆ๋ฌธ3" + ] + } + + --- ๋ฌธ์ œ ์ •๋ณด --- + ์ œ๋ชฉ: %s + + ์„ค๋ช…: + %s + + --- ์‚ฌ์šฉ ์–ธ์–ด --- + %s + + --- ์‚ฌ์šฉ์ž ์ฝ”๋“œ --- + %s + """.formatted( + problem.getTitle(), + problem.getDescription(), + language, + userCode + ); + } + + /** + * ๋ชจ๋ธ์ด ์“ธ๋ฐ์—†์ด ์•ž๋’ค์— ํ…์ŠคํŠธ๋ฅผ ๋ถ™์ผ ๋•Œ, + * ๊ทธ ์•ˆ์—์„œ JSON ๋ถ€๋ถ„๋งŒ ์ž˜๋ผ๋‚ด๊ธฐ ์œ„ํ•œ ์œ ํ‹ธ ํ•จ์ˆ˜. + */ + private String extractJsonString(String rawText) { + if (rawText == null) return null; + + String text = rawText.trim(); + + // ```json ... ``` ๊ฐ™์€ ์ฝ”๋“œ๋ธ”๋Ÿญ ์ œ๊ฑฐ + if (text.startsWith("```")) { + int firstBrace = text.indexOf('{'); + int lastBrace = text.lastIndexOf('}'); + if (firstBrace != -1 && lastBrace != -1 && lastBrace > firstBrace) { + return text.substring(firstBrace, lastBrace + 1); + } + } + + // ์ผ๋ฐ˜ ํ…์ŠคํŠธ์ผ ๋•Œ๋„ ์ฒซ '{' ~ ๋งˆ์ง€๋ง‰ '}' ์‚ฌ์ด๋งŒ ์ถ”์ถœ + int start = text.indexOf('{'); + int end = text.lastIndexOf('}'); + if (start == -1 || end == -1 || end <= start) { + return null; + } + + return text.substring(start, end + 1).trim(); + } +} diff --git a/src/main/java/com/example/skillboost/codingtest/judge/JudgeClient.java b/src/main/java/com/example/skillboost/codingtest/judge/JudgeClient.java new file mode 100644 index 0000000..3e4bd72 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/judge/JudgeClient.java @@ -0,0 +1,185 @@ +package com.example.skillboost.codingtest.judge; + +import org.springframework.stereotype.Component; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Component +public class JudgeClient { + + private static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); + private static final int TIMEOUT_SECONDS = 2; // ์‹œ๊ฐ„ ์ œํ•œ + + /** + * CodingTestService์—์„œ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ + * ์†Œ์Šค์ฝ”๋“œ, ์–ธ์–ด, ์ž…๋ ฅ๊ฐ’์„ ๋ฐ›์•„ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ + */ + public JudgeResult execute(String sourceCode, String language, String input) { + String uniqueId = UUID.randomUUID().toString(); + File sourceFile = createSourceFile(language, sourceCode, uniqueId); + + if (sourceFile == null) { + return JudgeResult.runtimeError("Internal Error: ํŒŒ์ผ ์ƒ์„ฑ ์‹คํŒจ"); + } + + try { + // 1. ์ปดํŒŒ์ผ (Java, C++ ๋งŒ) + if (language.equalsIgnoreCase("java") || language.equalsIgnoreCase("cpp")) { + String compileError = compileCode(language, sourceFile); + if (compileError != null) { + return JudgeResult.compileError(compileError); + } + } + + // 2. ์‹คํ–‰ + return runCode(language, sourceFile, input); + + } catch (Exception e) { + return JudgeResult.runtimeError(e.getMessage()); + } finally { + cleanup(sourceFile); + } + } + + // --- ๋‚ด๋ถ€ ํ—ฌํผ ๋ฉ”์„œ๋“œ --- + + private File createSourceFile(String language, String code, String uniqueId) { + try { + String fileName; + // ์–ธ์–ด๋ณ„ ํŒŒ์ผ ํ™•์žฅ์ž ๋ฐ ํด๋ž˜์Šค๋ช… ์ฒ˜๋ฆฌ + if (language.equalsIgnoreCase("java")) { + fileName = "Main.java"; // Java๋Š” Main ํด๋ž˜์Šค ๊ฐ•์ œ + } else if (language.equalsIgnoreCase("cpp")) { + fileName = uniqueId + ".cpp"; + } else { // python + fileName = uniqueId + ".py"; + } + + // ํด๋” ๋ถ„๋ฆฌ (๋™์‹œ ์‹คํ–‰ ์ถฉ๋Œ ๋ฐฉ์ง€) + Path dirPath = Path.of(TEMP_DIR, "judge_" + uniqueId); + Files.createDirectories(dirPath); + + File file = dirPath.resolve(fileName).toFile(); + try (FileWriter writer = new FileWriter(file)) { + writer.write(code); + } + return file; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private String compileCode(String language, File sourceFile) { + ProcessBuilder pb; + if (language.equalsIgnoreCase("java")) { + // javac -encoding UTF-8 Main.java + pb = new ProcessBuilder("javac", "-encoding", "UTF-8", sourceFile.getAbsolutePath()); + } else { + // g++ -o output source.cpp + String outputPath = sourceFile.getParent() + File.separator + "output"; + // Windows์ธ ๊ฒฝ์šฐ .exe ๋ถ™์ž„ + if (System.getProperty("os.name").toLowerCase().contains("win")) { + outputPath += ".exe"; + } + pb = new ProcessBuilder("g++", "-o", outputPath, sourceFile.getAbsolutePath()); + } + + pb.directory(sourceFile.getParentFile()); + pb.redirectErrorStream(true); + + try { + Process process = pb.start(); + boolean finished = process.waitFor(5, TimeUnit.SECONDS); + if (!finished) { + process.destroy(); + return "Time Limit Exceeded during Compilation"; + } + if (process.exitValue() != 0) { + return readProcessOutput(process.getInputStream()); + } + return null; // ์ปดํŒŒ์ผ ์„ฑ๊ณต + } catch (Exception e) { + return e.getMessage(); + } + } + + private JudgeResult runCode(String language, File sourceFile, String input) { + ProcessBuilder pb; + long startTime = System.currentTimeMillis(); + + try { + if (language.equalsIgnoreCase("java")) { + pb = new ProcessBuilder("java", "-cp", ".", "Main"); + } else if (language.equalsIgnoreCase("python")) { + pb = new ProcessBuilder("python", sourceFile.getName()); // python3 ๋ผ๋ฉด "python3" + } else { // cpp + String cmd = System.getProperty("os.name").toLowerCase().contains("win") ? "output.exe" : "./output"; + pb = new ProcessBuilder(cmd); + } + + pb.directory(sourceFile.getParentFile()); + Process process = pb.start(); + + // ์ž…๋ ฅ๊ฐ’ ์ฃผ์ž… + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) { + writer.write(input); + writer.flush(); + } + + // ์‹คํ–‰ ๋Œ€๊ธฐ + boolean finished = process.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS); + if (!finished) { + process.destroy(); + return JudgeResult.builder().statusId(5).message("Time Limit Exceeded").build(); + } + + // ๊ฒฐ๊ณผ ์ฝ๊ธฐ + String output = readProcessOutput(process.getInputStream()); + String error = readProcessOutput(process.getErrorStream()); + double duration = (System.currentTimeMillis() - startTime) / 1000.0; + + if (process.exitValue() != 0) { + return JudgeResult.runtimeError(error.isEmpty() ? "Runtime Error" : error); + } + + // ๋กœ์ปฌ ์‹คํ–‰ ์„ฑ๊ณต (์ •๋‹ต ์—ฌ๋ถ€๋Š” Service์—์„œ ํŒ๋‹จํ•˜๋ฏ€๋กœ ์—ฌ๊ธฐ์„  ์„ฑ๊ณต ์ƒํƒœ ๋ฆฌํ„ด) + // JudgeResult.accepted()๋Š” statusId=3์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ Service๊ฐ€ ์ •๋‹ต ๋น„๊ต๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ํ•จ + return JudgeResult.accepted(output, duration); + + } catch (Exception e) { + return JudgeResult.runtimeError(e.getMessage()); + } + } + + private String readProcessOutput(InputStream inputStream) throws IOException { + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + } + return sb.toString().trim(); + } + + private void cleanup(File sourceFile) { + try { + if (sourceFile == null) return; + File dir = sourceFile.getParentFile(); + if (dir != null && dir.exists()) { + File[] files = dir.listFiles(); + if (files != null) { + for (File f : files) f.delete(); + } + dir.delete(); + } + } catch (Exception e) { + // ignore + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codingtest/judge/JudgeResult.java b/src/main/java/com/example/skillboost/codingtest/judge/JudgeResult.java new file mode 100644 index 0000000..c09f6cf --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/judge/JudgeResult.java @@ -0,0 +1,55 @@ +package com.example.skillboost.codingtest.judge; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class JudgeResult { + // Judge0 ํ‘œ์ค€ ์ƒํƒœ ์ฝ”๋“œ (3: Accepted, 4: Wrong Answer, 5: Time Limit, 6: Compilation Error, 11: Runtime Error) + private int statusId; + + private String stdout; // ํ‘œ์ค€ ์ถœ๋ ฅ ๊ฒฐ๊ณผ + private String stderr; // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ + private String message; // ์„ค๋ช… + private double time; // ์‹คํ–‰ ์‹œ๊ฐ„ + private long memory; // ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ + + public static JudgeResult accepted(String output, double time) { + return JudgeResult.builder() + .statusId(3) // Accepted + .stdout(output) + .time(time) + .message("Accepted") + .build(); + } + + public static JudgeResult wrongAnswer(String output, double time) { + return JudgeResult.builder() + .statusId(4) // Wrong Answer + .stdout(output) + .time(time) + .message("Wrong Answer") + .build(); + } + + public static JudgeResult compileError(String errorMessage) { + return JudgeResult.builder() + .statusId(6) // Compilation Error + .stderr(errorMessage) + .message("Compilation Error") + .build(); + } + + public static JudgeResult runtimeError(String errorMessage) { + return JudgeResult.builder() + .statusId(11) // Runtime Error + .stderr(errorMessage) + .message("Runtime Error") + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codingtest/repository/CodingProblemRepository.java b/src/main/java/com/example/skillboost/codingtest/repository/CodingProblemRepository.java new file mode 100644 index 0000000..fe01138 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/repository/CodingProblemRepository.java @@ -0,0 +1,16 @@ +package com.example.skillboost.codingtest.repository; + +import com.example.skillboost.codingtest.domain.CodingProblem; +import com.example.skillboost.codingtest.domain.Difficulty; // โ˜… ์ด import๊ฐ€ ๊ผญ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CodingProblemRepository extends JpaRepository { + + // ์ œ๋ชฉ์œผ๋กœ ๋ฌธ์ œ ์ฐพ๊ธฐ (์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋ฐฉ์ง€์šฉ) + boolean existsByTitle(String title); + + // โ˜… [ํ•ต์‹ฌ] ์ด ์ค„์ด ์—†์–ด์„œ ์—๋Ÿฌ๊ฐ€ ๋‚œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”! + List findAllByDifficulty(Difficulty difficulty); +} \ No newline at end of file diff --git a/src/main/java/com/example/skillboost/codingtest/repository/CodingSubmissionRepository.java b/src/main/java/com/example/skillboost/codingtest/repository/CodingSubmissionRepository.java new file mode 100644 index 0000000..03d5c06 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/repository/CodingSubmissionRepository.java @@ -0,0 +1,7 @@ +package com.example.skillboost.codingtest.repository; + +import com.example.skillboost.codingtest.domain.CodingSubmission; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CodingSubmissionRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/skillboost/codingtest/repository/CodingTestCaseRepository.java b/src/main/java/com/example/skillboost/codingtest/repository/CodingTestCaseRepository.java new file mode 100644 index 0000000..8ce37b1 --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/repository/CodingTestCaseRepository.java @@ -0,0 +1,15 @@ +package com.example.skillboost.codingtest.repository; + +import com.example.skillboost.codingtest.domain.CodingProblem; +import com.example.skillboost.codingtest.domain.CodingTestCase; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CodingTestCaseRepository extends JpaRepository { + + List findByProblem(CodingProblem problem); + + // ๋˜๋Š” problemId๋กœ ๋ฐ”๋กœ ์ฐพ๊ณ  ์‹ถ์œผ๋ฉด + List findByProblem_Id(Long problemId); +} diff --git a/src/main/java/com/example/skillboost/codingtest/service/CodingTestService.java b/src/main/java/com/example/skillboost/codingtest/service/CodingTestService.java new file mode 100644 index 0000000..36c882b --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/service/CodingTestService.java @@ -0,0 +1,20 @@ +package com.example.skillboost.codingtest.service; + +import com.example.skillboost.codingtest.dto.SubmissionRequestDto; +import com.example.skillboost.codingtest.dto.SubmissionResultDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CodingTestService { + + private final GradingService gradingService; + + /** + * ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ ์ œ์ถœ ์ฒ˜๋ฆฌ + */ + public SubmissionResultDto submitCode(SubmissionRequestDto request) { + return gradingService.grade(request); + } +} diff --git a/src/main/java/com/example/skillboost/codingtest/service/GradingService.java b/src/main/java/com/example/skillboost/codingtest/service/GradingService.java new file mode 100644 index 0000000..7cf61de --- /dev/null +++ b/src/main/java/com/example/skillboost/codingtest/service/GradingService.java @@ -0,0 +1,37 @@ +package com.example.skillboost.codingtest.service; + +import com.example.skillboost.codingtest.domain.CodingProblem; +import com.example.skillboost.codingtest.dto.SubmissionRequestDto; +import com.example.skillboost.codingtest.dto.SubmissionResultDto; +import com.example.skillboost.codingtest.judge.GeminiJudge; +import com.example.skillboost.codingtest.repository.CodingProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GradingService { + + private final CodingProblemRepository problemRepository; + private final GeminiJudge geminiJudge; + + /** + * ์‹ค์ œ AI ๊ธฐ๋ฐ˜ ์ฑ„์  + */ + public SubmissionResultDto grade(SubmissionRequestDto request) { + + // 1) ๋ฌธ์ œ ์กฐํšŒ + CodingProblem problem = problemRepository.findById(request.getProblemId()) + .orElseThrow(() -> new IllegalArgumentException("๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")); + + // 2) AI ์ฑ„์  ์‹คํ–‰ + SubmissionResultDto aiResult = geminiJudge.grade( + problem, + request.getCode(), + request.getLanguage() + ); + + // 3) ๊ฒฐ๊ณผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ (AI๊ฐ€ ์ตœ์ข… ํŒ์ •) + return aiResult; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82026c5..35e1022 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,5 +1,27 @@ spring: + config: + import: optional:classpath:application-secret.yml application: name: skill-boost - profiles: - active: local \ No newline at end of file + + datasource: + url: jdbc:mysql://localhost:3306/mydatabase?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + username: myuser + password: secret + driver-class-name: com.mysql.cj.jdbc.Driver + + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + show-sql: true + +server: + port: 8080 + +gemini: + api: + key: # + model: gemini-2.5-flash