[프로그래머스] 파일명 정렬 (java)

zl존석동

·

2022. 8. 15. 21:51

프로그래머스 level2 - 파일명 정렬 자바 풀이


 

 

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

 

문제 요약

 

파일 문자열 배열 주는데 파일은 영문자와 '.', 공백, '-' 로 구성되는 HEAD 영역과 5자리 이하 숫자로 구성되는 NUMBER 영역, 이외의 부분인 TAIL 영역으로 나눌 수 있다.

 

HEAD -> NUMBER 순서로 오름차순으로 정렬된 파일 목록을 반환해라.

 

단 HEAD는 대소문자 구별이 없고 NUMBER는 절대적인 수치 기준이다.(00123 == 123)

 

즉 HEAD 먼저 사전순으로 정렬하고 같으면 숫자 작은 것 부터 정렬해서 반환하라는 소리이다.

 

 

나의 풀이

 

결국 생각해야 되는 부분은 세 가지라고 생각했다.

 

1. 파일명을 HEAD-NUMBER-TAIL 로 분리할 수 있어야 한다.

 

2. 요구에 맞게 정렬 정책을 만들고 분리된 파일 구조에 적용할 수 있어야 한다.

 

3. 정렬 정책을 주어진 파일 문자열 형태 그대로 만들어 반환한다.

 

 

자바 기준으로 정규식과 객체 정렬하는 방법만 알면 크게 어렵지 않게 풀 수 있는 것 같다.

 

사실 정규식을 안 쓰고도 순회하면서 분리하면 충분히 풀 수 있지만 코드가 복잡해질 것 같아 정규식을 사용했던 것 같다.

 

 

 

정규식 적용하기

 

[A-z\\s-.]+     //A~z 또는 공백 또는 - 또는 . 이 포함된 1글자 이상의 문자열

 

다음과 같은 패턴으로 된 문자를 주어진 문자열에서 찾는다고 생각해보자

 

Matcher matcher = Pattern.compile(regex).matcher(str);
if (matcher.find()) {
    return matcher.group();
}

 

Matcher 객체에 정규식을 컴파일하고 주어진 문자열과 비교하겠다는 상태를 담는다.

 

그리고 find() 메소드를 통해 해당 패턴이 있는지를  검사하고 group() 메소드로 해당되는 패턴의 문자열을 얻어내면 된다.

 

더 좋은 방법이 있을 수 있지만 일단 알고리즘 문제푸는데 클래스 메소드 문서들 까볼수도 없고 저것밖에 몰라서 저렇게 했다.

 

만약 문자열에서 해당되는 정규식 패턴의 모든 문자열을 찾아야 한다고 하면 조건문이 아니라 루프문을 이용하여 모두 찾아 얻어내야 하겠지만

 

이 문제에서의 관심사는 앞에서부터 요구되는 패턴을 최초에 1개씩만을 찾으면 되기 때문에 최초에만 find() 로 조회해주고 얻은 결과물을 그대로 한 번만 반환해주면 된다.

 

 

커스텀 파일 객체 만들기

 

주어진 파일 문자열로부터 중간에 얻어야 하는 것은 HEAD-NUMBER-TAIL 이라는 데이터이며

 

필요한 동작정렬모든 파일명 문자열을 반환 해야 하는 것이다.

 

따라서 클래스로 속성과 기능을 만들고 요구를 처리할 수 있도록 구성을 해보았다.

 

private static class File implements Comparable<File> {
    private final String head;
    private final String number;
    private final String tail;

    private static final String HEAD_REGEX = "[A-z\\s-.]+";
    private static final String NUMBER_REGEX = "[0-9]{1,5}+";

    public File(String file) {
        this.head = extractTextByRegex(HEAD_REGEX, file);
        this.number = extractTextByRegex(NUMBER_REGEX, file);
        this.tail = file.substring(head.length() + number.length());
    }

    @Override
    public String toString() {
        return head + number + tail;
    }

    @Override
    public int compareTo(File file) {
        if (head.equalsIgnoreCase(file.head)) {
            return Integer.parseInt(number) - Integer.parseInt(file.number);
        }
        return head.compareToIgnoreCase(file.head);
    }

    private String extractTextByRegex(String regex, String str) {
        Matcher matcher = Pattern.compile(regex).matcher(str);
        if (matcher.find()) {
            return matcher.group();
        }
        throw new IllegalArgumentException("주어진 문자열에 일치하는 패턴 없음");
    }
}

 

 

    public File(String file) {
        this.head = extractTextByRegex(HEAD_REGEX, file);
        this.number = extractTextByRegex(NUMBER_REGEX, file);
        this.tail = file.substring(head.length() + number.length());
    }

 

인스턴스화 부분인데 주어지는 file 문자열을 그대로 받아와서 head, number 에 정규식을 적용시키고

 

나머지 부분은 tail 속성에 초기화되도록 설정했다.

 

 

사실 Comparable 함수형 인터페이스를 상속받아 compareTo를 구현하면 hashCode와 equals 도 재정의하는게 권장되지만 그냥 넘어가도록 하자. 비교로 객체 내부 문자열, 정수를 활용할 거라 여기선 괜찮아 보였다.

 

compareTo현재 인스턴스(this)와 또 다른 자기 자신 타입의 객체를 비교해주는

Comparable 함수형 인터페이스의 추상 메소드인데 문제 요구에 맞는 정렬 정책을 객체 정렬를 통해 적용하기 위해 커스텀해서 사용한다고 생각하면 될 것 같다.

 

    public int compareTo(File file) {
        if (head.equalsIgnoreCase(file.head)) {
            return Integer.parseInt(number) - Integer.parseInt(file.number);
        }
        return head.compareToIgnoreCase(file.head);
    }

 

문제의 요구사항 처럼 구현했다.

 

HEAD 가 다르다면 HEAD 를 기준으로 오름차순 비교하도록 했고 HEAD가 같으면 NUMBER 정수값을 통해 오름차순 비교하도록 했다.

 

클래스로 설계하지 않고 HEAD-NUMBER를 이차원 배열 같은 자료구조로 넣어서

 

Arrays.sort() 에 람다식이나 익명클래스로 Comparator 인터페이스를 구현해 정렬해도 되지만 이 방법이 훨씬 깔끔한 것 같다.

 

    public static String[] solution(String[] files) {
        return Arrays.stream(files)
                .map(File::new).sorted()
                .map(File::toString)
                .toArray(String[]::new);
    }

 

이렇게 객체로 풀어내니까 클라이언트 부분이라고 할 수 있는 메소드가 매우 간결해진다.

 

주어진 files 문자열 배열을 스트림 파이프라인에 넣어서 커스텀한 File 인스턴스로 만든 뒤 sorted() 로 정렬 시킨다.

 

여기서 sorted() 에 아무것도 없으면 우리가 클래스에 구현한 compareTo() 메소드를 활용해 객체를 정렬한다.

 

그 정렬된 결과물 스트림을 다시 toString() 메소드를 통해 문자열로 변경하고 문자열 배열 객체로 만들어 리턴했다.

 

 

 

자바 풀이 코드

 

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class 파일명정렬 {

    public static void main(String[] args) {
        String[] files = {"img12.png", "img10.png", "img02.png", "img1.png", "IMG01.GIF", "img2.JPG"};
        System.out.println(Arrays.toString(solution(files)));
    }

    public static String[] solution(String[] files) {
        return Arrays.stream(files)
                .map(File::new)
                .sorted()
                .map(File::toString)
                .toArray(String[]::new);
    }

    private static class File implements Comparable<File> {
        private final String head;
        private final String number;
        private final String tail;

        private static final String HEAD_REGEX = "[A-z\\s-.]+";
        private static final String NUMBER_REGEX = "[0-9]{1,5}+";

        public File(String file) {
            this.head = extractTextByRegex(HEAD_REGEX, file);
            this.number = extractTextByRegex(NUMBER_REGEX, file);
            this.tail = file.substring(head.length() + number.length());
        }

        @Override
        public String toString() {
            return head + number + tail;
        }

        @Override
        public int compareTo(File file) {
            if (head.equalsIgnoreCase(file.head)) {
                return Integer.parseInt(number) - Integer.parseInt(file.number);
            }
            return head.compareToIgnoreCase(file.head);
        }

        private String extractTextByRegex(String regex, String str) {
            Matcher matcher = Pattern.compile(regex).matcher(str);
            if (matcher.find()) {
                return matcher.group();
            }
            throw new IllegalArgumentException("주어진 문자열에 일치하는 패턴 없음");
        }
    }
}

 

 

문제가 그렇게 어렵지 않고 나름대로 데이터나 동작이 눈에 좀 보여서

 

구조를 신경써서 풀이를 해봤는데 구성할 때는 오래걸렸지만 읽기가 너무 편한 것 같아 그런대로 만족스러웠다.