Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

๐Ÿš€ 1๋‹จ๊ณ„ - ๊ฒฝ๋กœ ์กฐํšŒ ํƒ€์ž… ์ถ”๊ฐ€ #490

Open
wants to merge 4 commits into
base: leejuoh
Choose a base branch
from
Open
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 @@ -7,14 +7,14 @@
public class AddSectionRequest {

private final Long upStationId;

private final Long downStationId;

private final Long distance;
private final Long duration;

public AddSectionRequest(Long upStationId, Long downStationId, Long distance) {
public AddSectionRequest(Long upStationId, Long downStationId, Long distance, Long duration) {
this.upStationId = upStationId;
this.downStationId = downStationId;
this.distance = distance;
this.duration = duration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ public class LineCreateRequest {
private final Long downStationId;

private final Long distance;
private final Long duration;

public LineCreateRequest(String name, String color, Long upStationId, Long downStationId, Long distance) {
public LineCreateRequest(String name, String color, Long upStationId, Long downStationId, Long distance,
Long duration) {
this.name = name;
this.color = color;
this.upStationId = upStationId;
this.downStationId = downStationId;
this.distance = distance;
this.duration = duration;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package nextstep.subway.application.dto.response;

import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.NoArgsConstructor;
import nextstep.subway.domain.entity.Path;
import nextstep.subway.domain.entity.Station;

@NoArgsConstructor
Expand All @@ -28,14 +26,11 @@ public StationDto(Station station) {

private List<StationDto> stations;
private long distance;
private long duration;

public PathResponse(Path shortestPath) {
this.stations = shortestPath.getStations().stream()
.map(StationDto::new)
.collect(Collectors.toList());
this.distance = (long) shortestPath.getDistance();

public PathResponse(List<StationDto> stations, long distance, long duration) {
this.stations = stations;
this.distance = distance;
this.duration = duration;
}


}
18 changes: 16 additions & 2 deletions src/main/java/nextstep/subway/application/service/LineService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ public LineResponse saveLine(LineCreateRequest lineCreateRequest) {
lineCreateRequest.getName(),
lineCreateRequest.getColor()
));
line.addSection(Section.of(line, upStation, downStation, lineCreateRequest.getDistance()));
line.addSection(
Section.of(
line,
upStation,
downStation,
lineCreateRequest.getDistance(),
lineCreateRequest.getDuration()
)
);
return new LineResponse(line);
}

Expand Down Expand Up @@ -67,7 +75,13 @@ public LineResponse addSection(Long lineId, AddSectionRequest addSectionRequest)
Station upStation = stationService.getStationById(addSectionRequest.getUpStationId());
Station downStation = stationService.getStationById(addSectionRequest.getDownStationId());
line.addSection(
Section.of(line, upStation, downStation, addSectionRequest.getDistance())
Section.of(
line,
upStation,
downStation,
addSectionRequest.getDistance(),
addSectionRequest.getDuration()
)
);
return new LineResponse(line);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import nextstep.subway.domain.entity.Path;
import nextstep.subway.domain.entity.PathFinder;
import nextstep.subway.domain.entity.Station;
import nextstep.subway.domain.enums.PathSearchType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -19,14 +20,18 @@ public class PathService {
private final StationService stationService;
private final LineService lineService;

public PathResponse findPath(Long sourceStationId, Long targetStationId) {
public PathResponse findPath(Long sourceStationId, Long targetStationId, PathSearchType type) {
Station source = stationService.getStationById(sourceStationId);
Station target = stationService.getStationById(targetStationId);
List<Line> lines = lineService.getLines();
PathFinder pathFinder = new PathFinder(lines);
Path shortestPath = pathFinder.findShortestPath(source, target)
Path shortestPath = pathFinder.findShortestPath(source, target, type)
.orElseThrow(() -> new CanNotFindPathException("Unable to find the shortest path."));
return new PathResponse(shortestPath);
return new PathResponse(
shortestPath.getStationDtoOfPath(target),
shortestPath.getDistance(),
shortestPath.getDuration()
);
}


Expand Down
38 changes: 33 additions & 5 deletions src/main/java/nextstep/subway/domain/entity/Path.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
package nextstep.subway.domain.entity;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.Getter;
import nextstep.subway.application.dto.response.PathResponse.StationDto;

@Getter
public class Path {

private List<Station> stations;
private double distance;
private final Sections sections;

public Path(List<Station> stations, double distance) {
this.stations = stations;
this.distance = distance;
public Path(Sections sections) {
this.sections = sections;
}

public List<StationDto> getStationDtoOfPath(Station target) {
List<Station> stations = this.getStationsOfPath();
if (!stations.isEmpty()) {
Station sourceStation = stations.get(0);
if (Objects.equals(sourceStation.getId(), target.getId())) {
Collections.reverse(stations);
}
}
return stations.stream()
.map(StationDto::new)
.collect(Collectors.toList());
}

public List<Station> getStationsOfPath() {
return this.sections.getSortedStationsByUpDirection(true);
}


public long getDistance() {
return this.sections.getDistance();
}

public long getDuration() {
return this.sections.getDuration();
}
}
69 changes: 56 additions & 13 deletions src/main/java/nextstep/subway/domain/entity/PathFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import nextstep.subway.domain.enums.PathSearchType;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.WeightedGraph;
Expand All @@ -13,52 +16,92 @@
@Slf4j
public class PathFinder {

@Getter
static class SectionEdge extends DefaultWeightedEdge {

private final Section section;

SectionEdge(Section section) {
this.section = section;
}
}

private final List<Line> lines;
private final DijkstraShortestPath<Station, DefaultWeightedEdge> dijkstraShortestPath;
private final DijkstraShortestPath<Station, SectionEdge> distanceShortestPath;
private final DijkstraShortestPath<Station, SectionEdge> durationShortestPath;

public PathFinder(List<Line> lines) {
this.lines = lines;
this.dijkstraShortestPath = new DijkstraShortestPath<>(createWeightedGraph());
this.distanceShortestPath = new DijkstraShortestPath<>(createWeightedGraph(PathSearchType.DISTANCE));
this.durationShortestPath = new DijkstraShortestPath<>(createWeightedGraph(PathSearchType.DURATION));
}

public Optional<Path> findShortestPath(Station source, Station target) {
public Optional<Path> findShortestPath(Station source, Station target, PathSearchType type) {
if (source == target) {
throw new IllegalArgumentException("Source and target stations are the same");
}
try {
GraphPath<Station, DefaultWeightedEdge> result = dijkstraShortestPath.getPath(source, target);
return Optional.of(new Path(result.getVertexList(), result.getWeight()));
GraphPath<Station, SectionEdge> result = getShortedPath(source, target, type);
List<Section> sections = result.getEdgeList().stream()
.map(SectionEdge::getSection)
.collect(Collectors.toList());
return Optional.of(new Path(new Sections(sections)));
} catch (Exception e) {
return Optional.empty();
}
}

public boolean isValidPath(Station source, Station target) {
try {
Optional<Path> path = findShortestPath(source, target);
Optional<Path> path = findShortestPath(source, target, PathSearchType.DISTANCE);
return path.isPresent();
} catch (IllegalArgumentException e) {
return false;
}
}

private WeightedGraph<Station, DefaultWeightedEdge> createWeightedGraph() {
SimpleWeightedGraph<Station, DefaultWeightedEdge> graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class);
private GraphPath<Station, SectionEdge> getShortedPath(Station source, Station target,
PathSearchType type) {
if (type == PathSearchType.DISTANCE) {
return distanceShortestPath.getPath(source, target);
}
if (type == PathSearchType.DURATION) {
return durationShortestPath.getPath(source, target);
}
throw new IllegalStateException("invalid PathSearchType type");
}

private WeightedGraph<Station, SectionEdge> createWeightedGraph(PathSearchType type) {
SimpleWeightedGraph<Station, SectionEdge> graph = new SimpleWeightedGraph<>(SectionEdge.class);
addStationsAsVerticesToGraph(graph);
addSectionsAdSeightedEdgeToGraph(graph);
addSectionsAsWeightedEdgeToGraph(graph, type);
return graph;
}

private void addStationsAsVerticesToGraph(Graph<Station, DefaultWeightedEdge> graph) {
private void addStationsAsVerticesToGraph(Graph<Station, SectionEdge> graph) {
this.lines.stream()
.flatMap(line -> line.getAllStationsByDistinct().stream())
.forEach(graph::addVertex);
}

private void addSectionsAdSeightedEdgeToGraph(WeightedGraph<Station, DefaultWeightedEdge> graph) {
private void addSectionsAsWeightedEdgeToGraph(WeightedGraph<Station, SectionEdge> graph,
PathSearchType type) {
this.lines.stream()
.flatMap(line -> line.getSections().getAllSections().stream())
.forEach(section -> graph.setEdgeWeight(
graph.addEdge(section.getUpStation(), section.getDownStation()), section.getDistance()));
.forEach(section -> {
SectionEdge sectionEdge = new SectionEdge(section);
graph.addEdge(section.getUpStation(), section.getDownStation(), sectionEdge);
graph.setEdgeWeight(sectionEdge, getSectionEdgeWeight(section, type));
});
}

private long getSectionEdgeWeight(Section section, PathSearchType type) {
if (type == PathSearchType.DISTANCE) {
return section.getDistance();
}
if (type == PathSearchType.DURATION) {
return section.getDuration();
}
throw new IllegalStateException("invalid PathSearchType type");
}
}
9 changes: 6 additions & 3 deletions src/main/java/nextstep/subway/domain/entity/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ public class Section implements Comparable<Section> {

private long distance;

public Section(Line line, Station upStation, Station downStation, long distance) {
private long duration;

public Section(Line line, Station upStation, Station downStation, long distance, long duration) {
this.line = line;
this.upStation = upStation;
this.downStation = downStation;
this.distance = distance;
this.duration = duration;
}

public static Section of(Line line, Station upStation, Station downStation, long distance) {
return new Section(line, upStation, downStation, distance);
public static Section of(Line line, Station upStation, Station downStation, long distance, long duration) {
return new Section(line, upStation, downStation, distance, duration);
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/nextstep/subway/domain/entity/Sections.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,12 @@ private Optional<Section> getSectionByDownStation(Station station) {
public List<Section> getAllSections() {
return this.sections;
}

public long getDistance() {
return this.sections.stream().mapToLong(Section::getDistance).sum();
}

public long getDuration() {
return this.sections.stream().mapToLong(Section::getDuration).sum();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package nextstep.subway.domain.enums;

public enum PathSearchType {
DISTANCE,
DURATION
}
6 changes: 4 additions & 2 deletions src/main/java/nextstep/subway/ui/PathController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import nextstep.subway.application.dto.response.PathResponse;
import nextstep.subway.application.service.PathService;
import nextstep.subway.domain.enums.PathSearchType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -20,9 +21,10 @@ public class PathController {
@GetMapping
public ResponseEntity<PathResponse> findPath(
@RequestParam(name = "source") Long sourceStationId,
@RequestParam(name = "target") Long targetStationId
@RequestParam(name = "target") Long targetStationId,
@RequestParam(name = "type") PathSearchType type
) {
return ResponseEntity.ok().body(pathService.findPath(sourceStationId, targetStationId));
return ResponseEntity.ok().body(pathService.findPath(sourceStationId, targetStationId, type));
}

}
4 changes: 4 additions & 0 deletions src/test/java/nextstep/cucumber/AcceptanceContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ public class AcceptanceContext {

public Map<String, Object> store = new HashMap<>();
public ExtractableResponse<Response> response;

public <T> T getValueFromStore(String name, Class<T> responseType) {
return responseType.cast(this.store.get(name));
}
}
6 changes: 4 additions & 2 deletions src/test/java/nextstep/cucumber/steps/LineStepDef.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ public LineStepDef() {
params.put("name", param.get("name"));
params.put("color", param.get("color"));
params.put("upStationId",
((StationResponse) context.store.get(param.get("upStation"))).getId().toString());
context.getValueFromStore(param.get("upStation"), StationResponse.class).getId().toString());
params.put("downStationId",
((StationResponse) context.store.get(param.get("downStation"))).getId().toString());
context.getValueFromStore(param.get("downStation"), StationResponse.class).getId().toString());
params.put("distance", param.get("distance"));
params.put("duration", param.get("duration"));
ExtractableResponse<Response> response = ์ง€ํ•˜์ฒ _๋…ธ์„ _์ƒ์„ฑ_์š”์ฒญ(params);
context.store.put(params.get("name").toString(), response.as(LineResponse.class));
});
});

}


}
Loading