[kata][python] 백준 알고리즘 1032번 문제 (명령 프롬프트)

- 8 mins

명령 프롬프트

출처: 백준 알고리즘 1032번 문제

문제

시작 -> 실행 -> cmd를 쳐보자. 검정 화면이 눈에 보인다.

여기서 dir이라고 치면 그 디렉토리에 있는 서브디렉토리와 파일이 모두 나온다.

이때 원하는 파일을 찾으려면 다음과 같이 하면 된다.

dir *.exe라고 치면 확장자가 exe인 파일이 다 나온다.

“dir 패턴”과 같이 치면 그 패턴에 맞는 파일만 검색 결과로 나온다.

예를 들어, dir a?b.exe라고 검색하면 파일명의 첫 번째 글자가 a이고,

세 번째 글자가 b이고, 확장자가 exe인 것이 모두 나온다.

이때 두 번째 문자는 아무거나 나와도 된다.

예를 들어, acb.exe, aab.exe, apb.exe가 나온다.

이 문제는 검색 결과가 먼저 주어졌을 때,

패턴으로 뭘 쳐야 그 결과가 나오는지를 출력하는 문제이다.

패턴에는 알파벳과 “.” 그리고 “?”만 넣을 수 있다.

가능하면 ?을 적게 써야 한다.

그 디렉토리에는 검색 결과에 나온 파일만 있다고 가정하고, 파일 이름의 길이는 모두 같다.

입력

첫째 줄에 파일 이름의 개수 N이 주어진다.

둘째 줄부터 N개의 줄에는 파일 이름이 주어진다.

N은 50보다 작거나 같은 자연수이고 파일 이름의 길이는 모두 같고 길이는 최대 50이다.

파일이름은 알파벳과 “.” 그리고 “?”로만 이루어져 있다.

출력

첫째 줄에 패턴을 출력하면 된다.

예제입력
    3
    config.sys
    config.inf
    configures

예제출력
    config????

내 풀이

  1 import sys
  2
  3 n = int(sys.stdin.readline())
  4 file_name_list = []
  5
  6 for i in range(n):
  7     file_name_list.append(sys.stdin.readline().rstrip())
  8
  9 pattern = list(file_name_list[0])
 10
 11 for file_name in file_name_list[1:]:
 12     for i in range(len(file_name)):
 13         if pattern[i] == "?":
 14             pass
 15         elif pattern[i] != file_name[i]:
 16             pattern[i] = "?"
 17
 18 print("".join(pattern))

컨셉은 단순하다.

입력받은 문자들 중 맨 처음 받은 문자를 기준으로 나머지 문자를 하나씩 비교했다.

문자를 하나씩 비교하며 서로 다른 부분은 “?” 기호로 바꿔주면 된다.

(9) 맨 처음 받은 문자를 한 글자씩 쪼개어 리스트로 만들어주었다.

리스트로 만든 이유는 비교를하며 바로바로 문자열을 바꾸기 용이하게 하기 위함이다.

(11) 첫 번째로 받은 문자열은 비교의 기준(pattern)으로 사용했으므로,

2번째 문자열(1번인덱스)부터 시작해 이터레이션을 돌린다.

(13) 특정 순서의 패턴이 물음표라면, 이미 치환된 것이므로 물음표 그대로 놔두고 pass한다.

(14) 특정 순서의 패턴과 문자열이 서로 다르다면, 해당 순서의 패턴을 물음표로 바꾼다.

(18) 리스트를 모두 이어붙여서 패턴 문자열을 출력한다.

다른사람 풀이

  1 N=int(input());*x,=(input()for _ in range(N));print(''.join([i[0],'?'][i.count(i[0])<N]for i in zip(*x)))

아주 특이한 코드를 발견했다.

원본코드는 한줄로 쭉 이어져있었지만, 가독성을 위해 개행을 해보자.

  1 N=int(input())
  2 *x,=(input()for _ in range(N))
  3 print(''.join([i[0],'?'][i.count(i[0])<N]for i in zip(*x)))

두번째 줄을 보면 우선 generator expression 으로 input을 받았다.

변수를 받는 부분이 특이한데, 애스터리스크(*)와 변수명, 그리고 콤마(,)를 함꼐 적어주었다.

이렇게 하면 오른쪽에 있는 컨테이너 타입 변수를 언패킹해서 리스트로 넣어주는 것 같다.

즉,

x = list(("config.sys", "config.inf", "configures"))

*x, = ("config.sys", "config.inf", "configures") 는 같은 동작을 한다.

(근데 굳이 이렇게 쓰는 이유를 모르겠다)

어쨌든 2번째 줄은 입력받은 문자열들을 리스트로 만드는 역할을한다.

즉, 2번째 줄리 실행되면 x에는 다음과 같은 값이 저장된다.

["config.sys", "config.inf", "configures"]


마지막으로 3번째 줄도 아주 기괴하다.

일반 for문으로 바꾸어보면 분석이 한결 용이해진다.

for i in zip(*x):
    [i[0],'?'][i.count(i[0])<N]

우선 리스트 x를 언패킹하여 zip에 넣어주었다.

즉, 다음과 같은 형태로 넣어준 것이다.

for i in zip("config.sys", "config.inf", "configures"):
    [i[0],'?'][i.count(i[0])<N]

zip함수는 입력받은 컨테이너 타입의 동일 인덱스끼리 묶어주는 역할을 한다.

무슨 말이냐면,

for i in zip("config.sys", "config.inf", "configures"):
    print(i)

이처럼 i의 값을 그대로 출력하면 다음과 같은 값이 나온다.

('c', 'c', 'c')
('o', 'o', 'o')
('n', 'n', 'n')
('f', 'f', 'f')
('i', 'i', 'i')
('g', 'g', 'g')
('.', '.', 'u')
('s', 'i', 'r')
('y', 'n', 'e')
('s', 'f', 's')

이제 좀 감히 잡힌다.

다시 원래 코드를 보자.

for i in zip("config.sys", "config.inf", "configures"):
    [i[0],'?'][i.count(i[0])<N]

이것도 다시

  1. [i[0],'?'] 부분과

  2. [i.count(i[0])<N] 부분으로 나누어보자.

참고로,

현재 i에는 ('c', 'c', 'c') 혹은 ('s', 'f', 's') 와 같은 값들이 들어있다는 것을 상기하자.

그리고 데이터 세 개를 받았으므로 N은 3이다.


우선 1 에서 이터레이션을 돌면서 i의 첫번째 값과 물음표(‘?’) 값을 묶은 리스트를 만들어주었다.

즉, ['c','?'] 와 같이 두 개의 값이 들어있는 리스트를 만든 것이다.


그리고 2 에서는 두 개의 값 중 어떤 값을 선택할지 결정하고있다.

i.count(i[0]) 는 i에서 i[0]과 같은 데이터가 몇 개 등장하는지 숫자를 센다.

만약 i가 ('c', 'c', 'c') 라면 i.count('c')는 3이 될 것이고, 이는 N보다 작지 않다. (현재 N은 3이다)

그러므로 [i.count(i[0])<N] 은 False를 반환할 것이다.

즉,

[i[0],'?'][i.count(i[0])<N] 에 실제 값을 넣어서 생각해보면

['c','?'][False] 와 같은 형태가 된다.

Python에서 ‘False’는 숫자 ‘0’과 같으므로 위 코드는 다시 ['c','?'][0] 이 되고,

이는 문자열 ‘c’를 반환할 것이다.

즉, 입력받은 문자열을 맨 앞부터 차례대로 비교했을때,

모든 문자열이 똑같다면 해당 문자열을 그대로 반환하고,

한 개의 문자열이라도 다르다면 i.count(i[0])<N 의 값은 ‘True’, 즉, 1이되어,

['c','?'][True] 즉, ['c','?'][1] 즉, 문자열 ‘?’ 를 반환할 것이다.

이렇게 순서대로 나온 문자열들을 join 메소드를 사용해 연결해주면 최종 패턴값이 나오게된다.


마지막으로 정리하면,

  1 N=int(input())
  2 *x,=(input()for _ in range(N))
  3 print(''.join([i[0],'?'][i.count(i[0])<N]for i in zip(*x)))

위 코드를 조금 풀어서 표현하자면 다음과 같다.

  1 N = int(input())
  2
  3 x = [input() for _ in range(N)]
  4
  5 pattern_list = []
  6 for char in zip(*x):
  7     if char.count(char[0]) == N:
  8         pattern_list.append(char[0])
  9     else:
 10         pattern_list.append('?')
 11
 12 print(''.join(pattern_list))

(3) 규칙을 찾고자하는 문자열을 입력받아 list로 만든다.

(6) 각 문자열의 같은 위치 문자끼리 전부 묶어서, 맨 첫번째 문자부터 차례로 비교한다.

(7~8) 특정 위치의 문자를 모든 문자열이 가지고 있다면, 이는 규칙적인 문자이므로 그대로 parttern에 넣는다.

(9~10) 한 개의 문자열이라도 해당 문자를 가지고 있지 않다면, 이는 규칙에 어긋나므로 물음표(‘?’) 를 넣어준다.

(12) 패턴 리스트를 모두 붙여서 문자열로 출력한다.

분석

문제가 어렵다기보단 다른사람이 짠 코드를 분석하는데 시간이 좀 걸렸다.

코드의 라인수를 줄이는 것보다 중요한 것은 가독성이라고 생각하는데,

이 가독성이라는 부분이 사실 상대적이라서 왈가왈부하기 조심스럽다.

예를들어 입력을 받는 부분에서 *x, = (input() for _ in range(N)) 같은 형태는 개인적으로 가독성이 더 떨어진다고 생각한다.

그냥 명시적으로 x = [input() for _ in range(N)] 와 같은 형태가 더 깔끔한 것 같다.

하지만 list comprehension대신 generator expression을 사용한 이유가 있을수도 있으므로, 단언할 수는 없다.

zip 함수를 사용할때 리스트를 언패킹해서 넣어주는 부분은 여러 방법으로 활용할 수 있을 것 같다.

마지막으로 True, False가 각각 0, 1 인것을 이용해 리스트의 값을 뽑아내는 부분은 얼마나 실용적일까 싶지만 한편으로는 꽤 재치있는 발상인 것 같다.


개인적으로는 코드가 어느정도 그 사람의 성격까지도 반영한다고 생각하기 때문에,

100명의 프로그래머가 있다면 100개의 코딩스타일이 있다고 생각한다. (이는 코딩 컨벤션과는 조금 다른 이야기이다)

나를 모르는 사람이 내 코드를 보면 나를 어떤 성격의 사람이라고 생각하게 될까?

그리고 그 생각과 실제 나의 성격이 어느정도 비슷하게 맞아 떨어질까?

궁금하다.




코딩장이

코딩장이

-장이: [접사] ‘그것과 관련된 기술을 가진 사람’의 뜻을 더하는 접미사.

rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora