문제 75
문자열 단어 단위로 분리하기
알파벳 소문자로 이루어진 단어들이 하나의 공백으로 구분된 문자열 $S$가 주어진다.
문자열 $S$에 등장하는 단어를 순서대로 출력하는 프로그램을 작성하라.
입력
첫 번째 줄에 문자열 $S$가 주어진다. $(1 \le \lvert S \rvert \le 200)$
$S$에 포함된 단어의 개수는 $20$개 이하이다.
출력
문자열 $S$에 등장하는 단어를 앞에서부터 순서대로 한 줄에 하나씩 출력한다.
예제 입력 1
today is a good day
예제 출력 1
today is a good day
노트
세종대학교 인터페이스 스터디에서는 다음 규칙을 따른다.
strlen,strcpy,strcmp등의 표준 문자열 처리 함수를 사용할 수 있다.- 단어를 분리할 때
strtok함수를 사용하지 않는다. 위반하면 $50\%$ 감점한다. - 문장을 입력받을 때
gets함수를 사용한다. - 문장을 단어 단위로 분리하여 이차원 배열에 저장한다.
공식 해설
단어는 공백 또는 문장의 끝에서 끝난다. 문장을 왼쪽부터 읽으며 현재 단어의 문자들을 순서대로 모은다.
공백을 만나면 지금까지 모은 단어를 저장하고 다음 단어를 시작한다. 문장의 끝에서도 마지막 단어를 저장해야 한다. 공백은 단어 사이에 정확히 하나씩 있으므로 빈 단어가 생기지 않는다.
각 단어를 이차원 배열의 한 행에 저장하면 단어의 순서와 내부 문자 순서를 모두 유지할 수 있다. 저장한 행들을 앞에서부터 한 줄에 하나씩 출력한다.
문자열 길이를 $L$이라 하면 시간복잡도와 공간복잡도는 $O(L)$이다.
구현 · 언어별 풀이
C++ 구현
C++에서는 getline으로 문장 전체를 읽고 문자를 직접 순회한다. 단어를 이차원 문자 배열에 저장한다면 각 단어의 끝에 \0을 붙인다. 문장의 끝에서도 마지막 단어를 마감하며, 단어 분리에 별도 토큰 분리 함수는 필요 없다.
C 구현
fgets로 문장 전체를 읽고 개행을 제거한 뒤 공백을 직접 검사하여 char words[20][201]에 저장한다. 각 단어 끝에 '\0'을 붙이고 마지막 단어도 마감한다. strtok은 사용하지 않는다. 과거 스터디 규칙의 gets는 C17에서 제거된 안전하지 않은 함수라 사용하지 않는다.
s에는 개행을 제거한 문장이 들어 있다. 단어를 마감할 때 널 문자를 넣고, 문장이 끝나면 마지막 단어도 마감한다.
char words[20][201];
int row = 0, col = 0;
for (int i = 0; s[i]; i++) {
if (s[i] == ' ') {
words[row++][col] = '\0';
col = 0;
} else {
words[row][col++] = s[i];
}
}
words[row][col] = '\0';
for (int i = 0; i <= row; i++) puts(words[i]);
Python 구현
문장을 직접 순회하며 단어별 문자 리스트를 이차원 리스트에 저장한다. 공백에서 다음 행으로 넘어가고 마지막 행도 출력한다. 이 스터디 문제에서는 split()로 분리를 대신하지 않고 분리 과정을 구현한다.
공백을 만날 때 다음 단어의 리스트를 만들고, 출력할 때만 문자를 이어 붙인다.
words = [[]]
for c in s:
if c == " ":
words.append([])
else:
words[-1].append(c)
for word in words:
print("".join(word))
Java 구현
BufferedReader.readLine()으로 문장을 읽고 char[20][201]과 단어별 길이를 둔다. 공백을 직접 검사하여 행을 바꾸며 String.split()은 쓰지 않는다. 출력할 때 각 행의 실제 길이만 사용한다.
s에는 readLine()으로 읽은 문장이 들어 있다. 배열 전체가 아니라 실제로 채운 길이만 출력한다.
out은 StringBuilder다.
char[][] words = new char[20][201];
int[] len = new int[20];
int row = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == ' ') row++;
else words[row][len[row]++] = c;
}
for (int i = 0; i <= row; i++) {
out.append(words[i], 0, len[i]).append('\n');
}
Rust 구현
문장을 바이트로 읽고 Vec<Vec<u8>>에 단어를 저장한다. 공백을 직접 검사하여 다음 Vec을 시작하며 split_whitespace()로 분리를 대신하지 않는다. 마지막 단어도 별도 공백 없이 출력한다.
s에는 줄 끝 문자를 제거한 문장이 들어 있다. 소문자 ASCII 입력이므로 바이트를 문자로 바꿔 출력할 수 있다.
out은 std::io::Write를 사용할 수 있는 버퍼 출력이다.
let mut words = vec![Vec::<u8>::new()];
for &c in s.as_bytes() {
if c == b' ' {
words.push(Vec::new());
} else {
words.last_mut().unwrap().push(c);
}
}
for word in words {
for c in word { write!(out, "{}", c as char).unwrap(); }
writeln!(out).unwrap();
}
JavaScript 구현
스터디에서 요구하는 이차원 단어 배열을 JS에서는 문자 배열들의 배열로 표현할 수 있다. 현재 단어의 문자 배열에 한 글자씩 넣고 공백을 만나면 단어 목록에 추가한다. 입력 끝에서도 마지막 단어를 추가해야 한다.
const words = [];
let word = [];
for (const ch of s) {
if (ch === ' ') {
words.push(word);
word = [];
} else {
word.push(ch);
}
}
words.push(word);
console.log(words.map(w => w.join('')).join('\n'));
s에는 입력 줄의 개행을 제외한 문자열을 둔다. 문자열 분리 학습을 위해 split으로 바로 끝내지 않고 직접 구분자를 확인한다. C 전용 함수인 gets는 Node.js에 없으므로 fs.readFileSync(0, 'utf8')로 한 줄을 읽는다.