해당 포스팅은 이전 기수 프리코스 1주차 7번 문제를 풀어보았다. 1주차의 마지막 문제답게 제한사항과 요구사항이 어느정도 있는편이지만 순서대로 로직을 구성하다보면 크게 어려운 문제는 아니였다.
private static List<String> solution(String userName, String[][] friends, String[] visitors) {
argumentLengthValidate(userName, friends, visitors);
List<String> myFriends = findByMyFriends(userName, friends);
Map<String, Integer> friendsForScore = friendsForScore(userName, friends, myFriends, visitors);
return sortResultFriends(friendsForScore);
}
private static List<String> sortResultFriends(Map<String, Integer> friendsForScore) {
return friendsForScore.entrySet().stream()
.sorted((entry1, entry2) -> {
int compare = entry2.getValue().compareTo(entry1.getValue());
if (compare == 0) {
return entry1.getKey().compareTo(entry2.getKey());
}
return compare;
})
.limit(5)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
private static Map<String, Integer> friendsForScore(String userName, String[][] friends, List<String> myFriends, String[] visitors) {
Map<String, Integer> friendsForScore = new HashMap<>();
calculateFriendshipScore(userName, friends, myFriends, friendsForScore);
calculateVisitScore(myFriends, visitors, friendsForScore);
return friendsForScore;
}
private static void calculateVisitScore(List<String> myFriends, String[] visitors, Map<String, Integer> friendsForScore) {
for (String visitor : visitors) {
friendIdLengthValidate(visitor);
if (friendsForScore.containsKey(visitor)) {
friendsForScore.put(visitor, friendsForScore.get(visitor) + 1);
} else {
if (myFriends.contains(visitor) == false) {
friendsForScore.put(visitor, friendsForScore.getOrDefault(visitor, 0) + 1);
}
}
}
}
private static void calculateFriendshipScore(String userName, String[][] friends, List<String> myFriends, Map<String, Integer> friendsForScore) {
for (String[] friend : friends) {
for (String myFriend : myFriends) {
if (friend[0].equals(myFriend) && !friend[1].equals(userName)) {
friendsForScore.put(friend[1], friendsForScore.getOrDefault(friend[1], 0) + 10);
} else if (friend[1].equals(myFriend) && !friend[0].equals(userName)) {
friendsForScore.put(friend[0], friendsForScore.getOrDefault(friend[0], 0) + 10);
}
}
}
}
private static List<String> findByMyFriends(String userName, String[][] friends) {
List<String> friendList = new ArrayList<>();
for (String[] friend : friends) {
friendValidate(friend);
if (friend[0].equals(userName)) {
friendList.add(friend[1]);
} else if (friend[1].equals(userName)) {
friendList.add(friend[0]);
}
}
return friendList;
}
private static void argumentLengthValidate(String userName, String[][] friends, String[] visitors) {
if (userName.length() < 1 || userName.length() > 30) {
throw new IllegalArgumentException("user 는 길이가 1 이상 30 이하인 문자열이여야 합니다.");
}
if (friends.length < 1 || friends.length > 10000) {
throw new IllegalArgumentException("friends 는 길이가 1 이상 10,000 이하이여야 합니다.");
}
if (visitors.length > 10000) {
throw new IllegalArgumentException("visitors 의 길이는 0 이상 10,000 이하여야 합니다.");
}
}
private static void friendValidate(String[] friend) {
if (friend.length != 2) {
throw new IllegalArgumentException("friends의 원소의 길이는 2인 리스트/배열 이여야 합니다.");
}
if (friendIdLengthValidate(friend[0]) == false || friendIdLengthValidate(friend[1]) == false) {
throw new IllegalArgumentException("아이디는 길이가 1 이상 30 이하여야 합니다.");
}
if (friendIdIsLowerCase(friend[0]) == false || friendIdIsLowerCase(friend[1]) == false) {
throw new IllegalArgumentException("사용자 아이디는 알파벳 소문자로만 이루어져 있어야 합니다.");
}
}
private static boolean friendIdLengthValidate(String id) {
return id.length() >= 1 && id.length() <= 30;
}
private static boolean friendIdIsLowerCase(String id) {
String lowerCase = id.toLowerCase();
return id.equals(lowerCase);
}
문제의 제한사항에 초점을 두고 기능을 구현했다. 제대로 검증 할 수 있는 Validate 메서드를 구성하여 기능이 요구 사항에 벗어나지 않도록 제한을 두고 실제 기능을 하는 로직을 구현하였는데, 기능 단위로 분리함에 있어서 어느정도로 분리할지 고민 하였다.
처음에는 argumentLengthValidate 메서드의 경우 안에 있는 검증 로직들이 전부 메서드 단위로 분리되어있었는데 너무 많은 분린느 오히려 코드의 가독성을 낮춘다고 생각해 매개변수의 길이를 검증하는 로직들은 하나로 통합하게 되었다.
findByMyFriends 메서드를 통해 내 친구의 목록을 저장하고 전체를 도는 for문 이기 때문에 해당 메서드에서 friendValidate를 통해 아이디의 길이, 소문자 여부등을 검증해주었다.
내 친구목록을 바탕으로 calculateFriendshipScore, calculateVisitScore 메서드를 통해 추천 친구를 등록하고 점수를 구하는데, 추천 점수가 0인 경우가 나올 수 없기에 따로 Validate를 구현하진 않았다.
마지막으로 sortResultFriends 를 통해 기능 요구 사항에 맞추어 정렬을 진행하였다.
일단, 해당 로직이 효율적이진 않다. 분명 개선의 여부가 있을 것 이라 생각하고 기능 분리에 있어서도 생각할 점이 많았다. 해당 코드를 구현하였을때 너무 많이 분리했나? 라는 생각이 들었다. 사실 분리하다가도 너무 많이 분리되는 것 같아 분리된 메서드를 다시 하나로 합친 경우도 있었지만 현재 코드도 이미 많이 분리된 느낌이 든다.
7번 문제를 해결할때 기능을 얼마나 분리하고 얼마나 효율적이냐가 핵심이였던 것 같다. 7번 문제를 풀어보고 리팩터링하면서 생각한 것은 코드리뷰를 받아보고 싶다는 생각 뿐이였다. 아무래도 확실한건 우테코를 통해 분명 큰 성장을 이룰 수 있을 것 같다...🙇♂️