Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@

@Schema(description = "대중교통 길찾기 응답 DTO")
public record TransportationResponseDto(
List<Step> routes,
List<Route> routes,
PageInfo pageInfo
) {
public record Route(
Integer routeIndex, // 경로 인덱스
List<Step> steps // 해당 경로의 단계들
) {}
Comment on lines +11 to +17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

중첩 타입 Route 이름 충돌/혼동 가능성 — DTO 타입명 변경 또는 스키마 설명 강화 제안

엔티티 com.wayble.server.direction.entity.transportation.Route 와 DTO 내부 중첩 타입 Route 이름이 동일해, 서비스 코드에서 동시에 사용할 때 가독성 저하와 혼동을 유발합니다. 현재 서비스는 DTO 쪽을 TransportationResponseDto.Route 로 완전 수식하여 회피하고 있으나, 유지보수성과 실수 방지를 위해 DTO 타입명을 RouteDto 또는 RouteSummary 등으로 바꾸는 것을 권장합니다. 최소한 OpenAPI 스키마 설명으로 routeIndex의 식별 의미(페이지네이션 포함 전역 1-based 인덱스)와 steps의 의미를 명확히 해 주세요.

다음과 같이 최소 주석/스키마를 보강하는 방법도 고려해 주세요:

-    public record Route(
-            Integer routeIndex, // 경로 인덱스
-            List<Step> steps // 해당 경로의 단계들
-    ) {}
+    public record Route(
+            @Schema(description = "전역 1부터 시작하는 경로 인덱스(페이지네이션 적용 시 누적 기준).")
+            Integer routeIndex,
+            @Schema(description = "해당 경로를 구성하는 이동 단계 리스트")
+            List<Step> steps
+    ) {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<Route> routes,
PageInfo pageInfo
) {
public record Route(
Integer routeIndex, // 경로 인덱스
List<Step> steps // 해당 경로의 단계들
) {}
List<Route> routes,
PageInfo pageInfo
) {
public record Route(
@Schema(description = "전역 1부터 시작하는 경로 인덱스(페이지네이션 적용 시 누적 기준).")
Integer routeIndex,
@Schema(description = "해당 경로를 구성하는 이동 단계 리스트")
List<Step> steps
) {}


public record Step(
DirectionType mode, // 예: START, WALK, SUBWAY, BUS, FINISH
@Nullable List<MoveInfo> moveInfo, // 같은 Step으로 이동한 정류장(Node) 정보 (중간 정류장만)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class TransportationService {
private static final int ORIGIN_DESTINATION_WALK_DISTANCE = 1000; // 출발지/도착지에서 정류장까지 도보 연결 가능 거리 (m)
private static final int MAX_NEARBY_NODES = 5; // 출발지/도착지 주변에서 고려할 최대 정류장 수
private static final int MAX_DIJKSTRA_VISITS = 5000; // 다익스트라 알고리즘에서 방문할 수 있는 최대 노드 수 (무한 루프 방지)
private static final int MAX_ROUTES = 5; // 찾을 최대 경로 수

public TransportationResponseDto findRoutes(TransportationRequestDto request){

Expand All @@ -54,27 +55,39 @@ public TransportationResponseDto findRoutes(TransportationRequestDto request){
Node start = Node.createNode(-1L, origin.name(), DirectionType.FROM_WAYPOINT ,origin.latitude(), origin.longitude());
Node end = Node.createNode(-2L, destination.name(), DirectionType.TO_WAYPOINT,destination.latitude(), destination.longitude());

// 3. 경로 찾기
List<TransportationResponseDto.Step> steps = findTransportationRoute(start, end);
// 3. 여러 경로 찾기
List<List<TransportationResponseDto.Step>> allRoutes = findMultipleTransportationRoutes(start, end);

// 4. 페이징 처리
int startIndex = (request.cursor() != null) ? request.cursor() : 0;
int pageSize = request.size() != null ? request.size() : steps.size();
int endIndex = Math.min(startIndex + pageSize, steps.size());
boolean hasNext = endIndex < steps.size();
int pageSize = (request.size() != null) ? request.size() : 5; // 기본값 5로 설정
int endIndex = Math.min(startIndex + pageSize, allRoutes.size());
boolean hasNext = endIndex < allRoutes.size();
Integer nextCursor = hasNext ? endIndex : null;
Comment on lines +63 to 66
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

페이지네이션 경계값 검증 부족 — 음수/범위 초과 커서 처리 및 안전한 subList 필요

현재 cursor와 size에 대한 유효성 검증이 없어, 음수 입력 또는 cursor >= allRoutes.size() 일 때 subList에서 IndexOutOfBoundsException이 발생할 수 있습니다. 또한 size <= 0 입력도 방어해야 합니다.

다음과 같이 안전 가드를 추가해 주세요:

-        int pageSize = (request.size() != null) ? request.size() : 5; // 기본값 5로 설정
-        int endIndex = Math.min(startIndex + pageSize, allRoutes.size());
-        boolean hasNext = endIndex < allRoutes.size();
-        Integer nextCursor = hasNext ? endIndex : null;
+        int total = allRoutes.size();
+        int pageSize = (request.size() != null && request.size() > 0) ? request.size() : 5; // 기본값 5, 음수 방지
+        startIndex = Math.max(0, startIndex); // 음수 커서 방지
+        if (startIndex >= total) {
+            // 빈 페이지 반환 (또는 400 처리 선택)
+            TransportationResponseDto.PageInfo pageInfo = new TransportationResponseDto.PageInfo(null, false);
+            return new TransportationResponseDto(List.of(), pageInfo);
+        }
+        int endIndex = Math.min(startIndex + pageSize, total);
+        boolean hasNext = endIndex < total;
+        Integer nextCursor = hasNext ? endIndex : null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int pageSize = (request.size() != null) ? request.size() : 5; // 기본값 5로 설정
int endIndex = Math.min(startIndex + pageSize, allRoutes.size());
boolean hasNext = endIndex < allRoutes.size();
Integer nextCursor = hasNext ? endIndex : null;
int total = allRoutes.size();
int pageSize = (request.size() != null && request.size() > 0) ? request.size() : 5; // 기본값 5, 음수 방지
startIndex = Math.max(0, startIndex); // 음수 커서 방지
if (startIndex >= total) {
// 빈 페이지 반환 (또는 400 처리 선택)
TransportationResponseDto.PageInfo pageInfo = new TransportationResponseDto.PageInfo(null, false);
return new TransportationResponseDto(List.of(), pageInfo);
}
int endIndex = Math.min(startIndex + pageSize, total);
boolean hasNext = endIndex < total;
Integer nextCursor = hasNext ? endIndex : null;
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 63 to 66, validate and sanitize the incoming cursor and size before
using them to compute a subList: treat null/negative cursor as 0, enforce size >
0 (default to 5 if null or <= 0), clamp startIndex to the range [0,
allRoutes.size()], compute endIndex = Math.min(startIndex + pageSize,
allRoutes.size()), and if startIndex >= allRoutes.size() return an empty list
and null nextCursor; otherwise return allRoutes.subList(startIndex, endIndex)
and set nextCursor = (endIndex < allRoutes.size()) ? endIndex : null to avoid
IndexOutOfBoundsException.

TransportationResponseDto.PageInfo pageInfo = new TransportationResponseDto.PageInfo(nextCursor, hasNext);

// 경로를 찾지 못한 경우 처리
if (steps.isEmpty()) {
if (allRoutes.isEmpty()) {
throw new ApplicationException(PATH_NOT_FOUND);
}

return new TransportationResponseDto(steps, pageInfo);
// 페이징된 경로들을 Route 객체로 변환
List<TransportationResponseDto.Route> routeList = new ArrayList<>();
List<List<TransportationResponseDto.Step>> pagedRoutes = allRoutes.subList(startIndex, endIndex);
for (int i = 0; i < pagedRoutes.size(); i++) {
List<TransportationResponseDto.Step> route = pagedRoutes.get(i);
TransportationResponseDto.Route routeObj = createRoute(route, startIndex + i + 1);
routeList.add(routeObj);
}

return new TransportationResponseDto(routeList, pageInfo);
}

private TransportationResponseDto.Route createRoute(List<TransportationResponseDto.Step> steps, int routeIndex) {
return new TransportationResponseDto.Route(routeIndex, steps);
}

private List<TransportationResponseDto.Step> findTransportationRoute(Node startTmp, Node endTmp){
private List<List<TransportationResponseDto.Step>> findMultipleTransportationRoutes(Node startTmp, Node endTmp){
// 1. 데이터 로드
List<Node> nodes = new ArrayList<>(nodeRepository.findAll());
List<Edge> edges = new ArrayList<>(edgeRepository.findAllWithNodesAndRoute());
Expand All @@ -91,13 +104,12 @@ private List<TransportationResponseDto.Step> findTransportationRoute(Node startT
nodes.add(startTmp);
nodes.add(endTmp);

// 4. 그래프 빌드 및 최적 경로 찾기
// 4. 그래프 빌드 및 여러 경로 찾기
TransportationGraphDto graphData = buildGraph(nodes, edges, startTmp, endTmp);
return findOptimalRoute(graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd);
return findMultipleOptimalRoutes(graphData.graph(), startTmp, endTmp, graphData.weightMap(), nodes, nearestToStart, nearestToEnd);
}


private List<TransportationResponseDto.Step> findOptimalRoute(
private List<List<TransportationResponseDto.Step>> findMultipleOptimalRoutes(
Map<Long, List<Edge>> graph,
Node startTmp,
Node endTmp,
Expand All @@ -121,26 +133,179 @@ private List<TransportationResponseDto.Step> findOptimalRoute(
return new ArrayList<>();
}

// 2. 다익스트라 알고리즘으로 최적 경로 찾기
List<TransportationResponseDto.Step> route = runDijkstra(graph, startNode, endNode, weightMap, nodes);
// 2. 여러 경로 찾기
List<List<TransportationResponseDto.Step>> allRoutes = findMultipleRoutes(graph, startNode, endNode, weightMap, nodes);

// 3. 경로 필터링 및 정렬
return filterAndSortRoutes(allRoutes);
}

private List<List<TransportationResponseDto.Step>> findMultipleRoutes(
Map<Long, List<Edge>> graph,
Node start,
Node end,
Map<Pair<Long, Long>, Integer> weightMap,
List<Node> nodes) {

List<List<TransportationResponseDto.Step>> routes = new ArrayList<>();

// 1. 기본 다익스트라로 첫 번째 경로 찾기
List<TransportationResponseDto.Step> firstRoute = runDijkstra(graph, start, end, weightMap, nodes);
if (!firstRoute.isEmpty()) {
routes.add(firstRoute);
}

// 2. 효율적인 다중 경로 찾기 - 한 번의 탐색으로 여러 경로 생성
if (!firstRoute.isEmpty()) {
List<List<TransportationResponseDto.Step>> alternativeRoutes = findAlternativeRoutesEfficiently(
graph, start, end, weightMap, nodes, firstRoute
);
routes.addAll(alternativeRoutes);
}

if (!route.isEmpty()) {
// 3. 대중교통 포함 여부 확인
boolean hasPublicTransport = route.stream()
.anyMatch(step -> step.mode() == DirectionType.BUS || step.mode() == DirectionType.SUBWAY);
return routes;
}

private List<List<TransportationResponseDto.Step>> findAlternativeRoutesEfficiently(
Map<Long, List<Edge>> graph,
Node start,
Node end,
Map<Pair<Long, Long>, Integer> weightMap,
List<Node> nodes,
List<TransportationResponseDto.Step> firstRoute) {

List<List<TransportationResponseDto.Step>> alternativeRoutes = new ArrayList<>();

// 첫 번째 경로에서 실제 사용된 엣지들을 추출
Set<Pair<Long, Long>> usedEdges = extractActualEdgesFromRoute(firstRoute, graph);

// 최대 4개의 추가 경로 찾기
for (int i = 0; i < 4 && alternativeRoutes.size() < MAX_ROUTES - 1; i++) {
// 실제 사용된 엣지들에만 패널티를 적용한 가중치 맵 생성
Map<Pair<Long, Long>, Integer> penalizedWeightMap = createActualEdgePenalizedWeightMap(weightMap, usedEdges, i + 1);

// 다익스트라로 새로운 경로 찾기
List<TransportationResponseDto.Step> newRoute = runDijkstra(graph, start, end, penalizedWeightMap, nodes);

if (!hasPublicTransport) {
return new ArrayList<>();
if (newRoute.isEmpty()) {
break;
}

// 4. 환승 횟수 검증 (4회 이상 제외)
int transferCount = calculateTransferCount(route);
if (transferCount >= 4) {
return new ArrayList<>();
// 첫 번째 경로와 동일한지 확인
if (areRoutesIdentical(newRoute, firstRoute)) {
continue;
}

// 새로운 경로에서 사용된 엣지들도 추가
Set<Pair<Long, Long>> newUsedEdges = extractActualEdgesFromRoute(newRoute, graph);
usedEdges.addAll(newUsedEdges);

alternativeRoutes.add(newRoute);
}

return route;
return alternativeRoutes;
}
Comment on lines +169 to +207
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

대체 경로 중복 검출 범위 제한 — 첫 경로만 아닌 기존 대체 경로와도 중복 비교 필요

현재 areRoutesIdentical은 firstRoute와만 비교합니다. 이미 수집된 alternativeRoutes와의 중복을 허용할 수 있어, 동일 경로가 중복 반환될 위험이 있습니다.

다음과 같이 중복 비교를 확장하세요.

-            // 첫 번째 경로와 동일한지 확인
-            if (areRoutesIdentical(newRoute, firstRoute)) {
-                continue;
-            }
+            // 기존 경로(첫 경로 및 이미 수집된 대체 경로)와 중복 여부 확인
+            boolean isDuplicate = areRoutesIdentical(newRoute, firstRoute)
+                    || alternativeRoutes.stream().anyMatch(r -> areRoutesIdentical(r, newRoute));
+            if (isDuplicate) {
+                continue;
+            }

또한, 가능하면 K-Shortest Paths(Yen/Eppstein) 알고리즘 도입을 검토하면 품질과 성능이 더 좋아집니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private List<List<TransportationResponseDto.Step>> findAlternativeRoutesEfficiently(
Map<Long, List<Edge>> graph,
Node start,
Node end,
Map<Pair<Long, Long>, Integer> weightMap,
List<Node> nodes,
List<TransportationResponseDto.Step> firstRoute) {
List<List<TransportationResponseDto.Step>> alternativeRoutes = new ArrayList<>();
// 첫 번째 경로에서 실제 사용된 엣지들을 추출
Set<Pair<Long, Long>> usedEdges = extractActualEdgesFromRoute(firstRoute, graph);
// 최대 4개의 추가 경로 찾기
for (int i = 0; i < 4 && alternativeRoutes.size() < MAX_ROUTES - 1; i++) {
// 실제 사용된 엣지들에만 패널티를 적용한 가중치 맵 생성
Map<Pair<Long, Long>, Integer> penalizedWeightMap = createActualEdgePenalizedWeightMap(weightMap, usedEdges, i + 1);
// 다익스트라로 새로운 경로 찾기
List<TransportationResponseDto.Step> newRoute = runDijkstra(graph, start, end, penalizedWeightMap, nodes);
if (!hasPublicTransport) {
return new ArrayList<>();
if (newRoute.isEmpty()) {
break;
}
// 4. 환승 횟수 검증 (4회 이상 제외)
int transferCount = calculateTransferCount(route);
if (transferCount >= 4) {
return new ArrayList<>();
// 첫 번째 경로와 동일한지 확인
if (areRoutesIdentical(newRoute, firstRoute)) {
continue;
}
// 새로운 경로에서 사용된 엣지들도 추가
Set<Pair<Long, Long>> newUsedEdges = extractActualEdgesFromRoute(newRoute, graph);
usedEdges.addAll(newUsedEdges);
alternativeRoutes.add(newRoute);
}
return route;
return alternativeRoutes;
}
private List<List<TransportationResponseDto.Step>> findAlternativeRoutesEfficiently(
Map<Long, List<Edge>> graph,
Node start,
Node end,
Map<Pair<Long, Long>, Integer> weightMap,
List<Node> nodes,
List<TransportationResponseDto.Step> firstRoute) {
List<List<TransportationResponseDto.Step>> alternativeRoutes = new ArrayList<>();
// 첫 번째 경로에서 실제 사용된 엣지들을 추출
Set<Pair<Long, Long>> usedEdges = extractActualEdgesFromRoute(firstRoute, graph);
// 최대 4개의 추가 경로 찾기
for (int i = 0; i < 4 && alternativeRoutes.size() < MAX_ROUTES - 1; i++) {
// 실제 사용된 엣지들에만 패널티를 적용한 가중치 맵 생성
Map<Pair<Long, Long>, Integer> penalizedWeightMap = createActualEdgePenalizedWeightMap(weightMap, usedEdges, i + 1);
// 다익스트라로 새로운 경로 찾기
List<TransportationResponseDto.Step> newRoute = runDijkstra(graph, start, end, penalizedWeightMap, nodes);
if (newRoute.isEmpty()) {
break;
}
// 기존 경로(첫 경로 및 이미 수집된 대체 경로)와 중복 여부 확인
boolean isDuplicate = areRoutesIdentical(newRoute, firstRoute)
|| alternativeRoutes.stream().anyMatch(r -> areRoutesIdentical(r, newRoute));
if (isDuplicate) {
continue;
}
// 새로운 경로에서 사용된 엣지들도 추가
Set<Pair<Long, Long>> newUsedEdges = extractActualEdgesFromRoute(newRoute, graph);
usedEdges.addAll(newUsedEdges);
alternativeRoutes.add(newRoute);
}
return alternativeRoutes;
}






private Set<Pair<Long, Long>> extractActualEdgesFromRoute(List<TransportationResponseDto.Step> route, Map<Long, List<Edge>> graph) {
Set<Pair<Long, Long>> usedEdges = new HashSet<>();

for (TransportationResponseDto.Step step : route) {
String fromName = step.from();
String toName = step.to();

for (Map.Entry<Long, List<Edge>> entry : graph.entrySet()) {
Long nodeId = entry.getKey();
List<Edge> edges = entry.getValue();

for (Edge edge : edges) {
Node fromNode = edge.getStartNode();
Node toNode = edge.getEndNode();

if ((fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName)) ||
(fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName))) {
usedEdges.add(Pair.of(fromNode.getId(), toNode.getId()));
usedEdges.add(Pair.of(toNode.getId(), fromNode.getId()));
}
}
}
}

return usedEdges;
}

Comment on lines +213 to +239
Copy link

@coderabbitai coderabbitai bot Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

usedEdges 추출 정확도/성능 개선 필요 — 정류장명 매칭만으로는 오탐 가능

현재 from/to 정류장명만으로 그래프 전체를 스캔하며 usedEdges를 추출합니다. 동일한 정류장명이 여러 노드에 존재하거나, 모드/노선 구분이 다른 경우에도 매칭되어 과도한 패널티가 적용될 수 있습니다. WALK 구간까지 패널티에 포함되면 대체 경로 품질이 떨어질 수 있습니다.

  • WALK 단계는 패널티 대상에서 제외
  • 모드와 노선(routeName)까지 일치하는 엣지만 사용
-        for (TransportationResponseDto.Step step : route) {
+        for (TransportationResponseDto.Step step : route) {
+            // WALK 단계는 패널티 제외
+            if (step.mode() == DirectionType.WALK) {
+                continue;
+            }
             String fromName = step.from();
             String toName = step.to();
             
             for (Map.Entry<Long, List<Edge>> entry : graph.entrySet()) {
                 Long nodeId = entry.getKey();
                 List<Edge> edges = entry.getValue();
                 
                 for (Edge edge : edges) {
                     Node fromNode = edge.getStartNode();
                     Node toNode = edge.getEndNode();
                     
-                    if ((fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName)) ||
-                        (fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName))) {
+                    boolean nameMatch = (fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName))
+                            || (fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName));
+                    boolean modeMatch = edge.getEdgeType() == step.mode();
+                    boolean routeMatch = true;
+                    if (step.routeName() != null && edge.getRoute() != null && edge.getRoute().getRouteName() != null) {
+                        routeMatch = step.routeName().equals(edge.getRoute().getRouteName());
+                    }
+                    if (nameMatch && modeMatch && routeMatch) {
                         usedEdges.add(Pair.of(fromNode.getId(), toNode.getId()));
                         usedEdges.add(Pair.of(toNode.getId(), fromNode.getId()));
                     }
                 }
             }
         }

더 나은 접근: runDijkstra에서 이미 생성한 pathEdges를 반환(예: PathResult { steps, usedEdges })하여 O(E) 전체 스캔을 없애는 구조로 리팩터링하는 것을 권장합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Set<Pair<Long, Long>> extractActualEdgesFromRoute(List<TransportationResponseDto.Step> route, Map<Long, List<Edge>> graph) {
Set<Pair<Long, Long>> usedEdges = new HashSet<>();
for (TransportationResponseDto.Step step : route) {
String fromName = step.from();
String toName = step.to();
for (Map.Entry<Long, List<Edge>> entry : graph.entrySet()) {
Long nodeId = entry.getKey();
List<Edge> edges = entry.getValue();
for (Edge edge : edges) {
Node fromNode = edge.getStartNode();
Node toNode = edge.getEndNode();
if ((fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName)) ||
(fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName))) {
usedEdges.add(Pair.of(fromNode.getId(), toNode.getId()));
usedEdges.add(Pair.of(toNode.getId(), fromNode.getId()));
}
}
}
}
return usedEdges;
}
private Set<Pair<Long, Long>> extractActualEdgesFromRoute(List<TransportationResponseDto.Step> route, Map<Long, List<Edge>> graph) {
Set<Pair<Long, Long>> usedEdges = new HashSet<>();
for (TransportationResponseDto.Step step : route) {
// WALK 단계는 패널티 제외
if (step.mode() == DirectionType.WALK) {
continue;
}
String fromName = step.from();
String toName = step.to();
for (Map.Entry<Long, List<Edge>> entry : graph.entrySet()) {
Long nodeId = entry.getKey();
List<Edge> edges = entry.getValue();
for (Edge edge : edges) {
Node fromNode = edge.getStartNode();
Node toNode = edge.getEndNode();
boolean nameMatch = (fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName))
|| (fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName));
boolean modeMatch = edge.getEdgeType() == step.mode();
boolean routeMatch = true;
if (step.routeName() != null && edge.getRoute() != null && edge.getRoute().getRouteName() != null) {
routeMatch = step.routeName().equals(edge.getRoute().getRouteName());
}
if (nameMatch && modeMatch && routeMatch) {
usedEdges.add(Pair.of(fromNode.getId(), toNode.getId()));
usedEdges.add(Pair.of(toNode.getId(), fromNode.getId()));
}
}
}
}
return usedEdges;
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 213 to 239, the current extractActualEdgesFromRoute scans the whole
graph and matches nodes only by station name, causing false positives and
performance issues; update it to (1) skip steps whose mode equals WALK so they
are not added to usedEdges, (2) when matching edges require mode and routeName
to match the step in addition to station names (compare
edge.getMode()/edge.getRouteName() or the corresponding Node/Edge attributes
against step.mode() and step.routeName()), and (3) instead of scanning the
entire graph here, refactor runDijkstra to return the computed usedEdges (e.g.,
extend its result to include usedEdges or PathResult { steps, usedEdges }) and
use that returned set to avoid O(E) full-graph scans.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고 많으셨습니다!!!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KiSeungMin 감사합니다! 다중 경로 추천 기능 구현에 도움이 되었다면 기컐요. 코드 품질 개선과 성능 최적화를 통해 사용자들이 더 나은 경로 옵션을 받을 수 있게 되었네요!

(∩˃o˂∩)♡ 토끼가 행복해요~

private Map<Pair<Long, Long>, Integer> createActualEdgePenalizedWeightMap(Map<Pair<Long, Long>, Integer> originalWeightMap, Set<Pair<Long, Long>> usedEdges, int routeIndex) {
Map<Pair<Long, Long>, Integer> penalizedWeightMap = new HashMap<>();

for (Map.Entry<Pair<Long, Long>, Integer> entry : originalWeightMap.entrySet()) {
Pair<Long, Long> edge = entry.getKey();
int weight = entry.getValue();

if (usedEdges.contains(edge)) {
int penalty = routeIndex * 100000;
penalizedWeightMap.put(edge, weight + penalty);
} else {
penalizedWeightMap.put(edge, weight);
}
}

return penalizedWeightMap;
}

private boolean areRoutesIdentical(List<TransportationResponseDto.Step> route1, List<TransportationResponseDto.Step> route2) {
// 두 경로가 완전히 동일한지 확인
if (route1.size() != route2.size()) {
return false;
}

for (int i = 0; i < route1.size(); i++) {
TransportationResponseDto.Step step1 = route1.get(i);
TransportationResponseDto.Step step2 = route2.get(i);

if (step1.mode() != step2.mode() ||
!Objects.equals(step1.from(), step2.from()) ||
!Objects.equals(step1.to(), step2.to()) ||
!Objects.equals(step1.routeName(), step2.routeName())) {
return false;
}
}

return true;
}

private List<List<TransportationResponseDto.Step>> filterAndSortRoutes(List<List<TransportationResponseDto.Step>> routes) {
return routes.stream()
.filter(route -> {
// 대중교통 포함 여부 확인
boolean hasPublicTransport = route.stream()
.anyMatch(step -> step.mode() == DirectionType.BUS || step.mode() == DirectionType.SUBWAY);

if (!hasPublicTransport) {
return false;
}

// 환승 횟수 검증 (4회 이상 제외)
int transferCount = calculateTransferCount(route);
return transferCount < 4;
})
.sorted(Comparator
.<List<TransportationResponseDto.Step>>comparingInt(this::calculateTransferCount)
.thenComparingInt(this::calculateWalkDistance))
.limit(MAX_ROUTES)
.collect(Collectors.toList());
}

private int calculateWalkDistance(List<TransportationResponseDto.Step> route) {
return route.stream()
.filter(step -> step.mode() == DirectionType.WALK)
.mapToInt(step -> {
// 간단한 도보 거리 추정 (실제로는 정확한 거리 계산 필요)
return 500; // 기본값
})
.sum();
}

private TransportationGraphDto buildGraph(List<Node> nodes, List<Edge> edges, Node startTmp, Node endTmp) {
Expand Down Expand Up @@ -457,9 +622,9 @@ private List<TransportationResponseDto.Step> mergeConsecutiveRoutes(List<Edge> p
return new ArrayList<>();
}
}
} catch (Exception e) {
} catch (Exception e) {
log.info("버스 정보 조회 실패: {}", e.getMessage());
}
}
} else if (currentType == DirectionType.SUBWAY) {
try {
if (currentEdge.getStartNode() != null) {
Expand Down