Python에는 두 가지 반복 구조가 있어요.
while문은 무한(횟수를 세지 않는) 반복에, for문은 유한(횟수를 세는) 반복에 사용해요.
break, continue, else 키워드를 사용하면 루프의 동작을 세밀하게 조정할 수 있어요.
range()와 enumerate()는 반복 횟수 세기와 인덱스 다루기를 도와줘요.
while문while문은 loop expression, 즉 "테스트"가 boolean context에서 True로 평가되는 동안 계속 실행되고, False로 평가되면 종료돼요:
# Lists are considered "truthy" in a boolean context if they
# contain one or more values, and "falsy" if they are empty.
>>> placeholders = ["spam", "ham", "eggs", "green_spam", "green_ham", "green_eggs"]
>>> while placeholders:
... print(placeholders.pop(0))
...
'spam'
'ham'
'eggs'
'green_spam'
'green_ham'
'green_eggs'
for문Python의 기본 for loop는 _for each_라고 부르는 편이 더 정확해요. 이터러블 객체의 값들을 차례로 순회하다가, next()를 호출해도 더 이상 값이 반환되지 않으면 종료되지요:
>>> word_list = ["bird", "chicken", "barrel", "bongo"]
>>> for word in word_list:
... if word.startswith("b"):
... print(f"{word.title()} starts with a B.")
... else:
... print(f"{word.title()} doesn't start with a B.")
...
'Bird starts with a B.'
'Chicken doesn\'t start with a B.'
'Barrel starts with a B.'
'Bongo starts with a B.'
range()
특정한 iterable이 주어지지 않으면, 특별한 range() 시퀀스가 루프 카운터로 사용돼요.
range()는 시퀀스를 stop할 int를 하나 받아야 하고, 선택적으로 start와 step 매개변수를 받을 수 있어요.
start 숫자를 주지 않으면 시퀀스는 0부터 시작해요.
range() 객체는 지연 평가 방식이라 (값은 요청할 때 생성돼요) 일반 시퀀스 연산을 모두 지원하고, 지정한 시퀀스가 아무리 길어도 메모리를 일정량만 차지해요.
# Here we use range to produce some numbers, rather than creating a list of them in memory.
# The values will start with 1 and stop *before* 7
>>> for number in range(1, 7):
... if number % 2 == 0:
... print(f"{number} is even.")
... else:
... print(f"{number} is odd.")
'1 is odd.'
'2 is even.'
'3 is odd.'
'4 is even.'
'5 is odd.'
'6 is even.'
# range() can also take a *step* parameter.
# Here we use range to produce only the "odd" numbers, starting with 3 and stopping *before* 15.
>>> for number in range(3, 15, 2):
... if number % 2 == 0:
... print(f"{number} is even.")
... else:
... print(f"{number} is odd.")
...
'3 is odd.'
'5 is odd.'
'7 is odd.'
'9 is odd.'
'11 is odd.'
'13 is odd.'
enumerate()로 값과 인덱스 얻기값과 그 인덱스가 모두 필요하면, 내장 함수 enumerate(<iterable>)가 (index, value) 쌍을 반환해요:
>>> word_list = ["bird", "chicken", "barrel", "apple"]
# *index* and *word* are the loop variables.
# Loop variables can be any valid python name.
>>> for index, word in enumerate(word_list):
... if word.startswith("b"):
... print(f"{word.title()} (at index {index}) starts with a B.")
... else:
... print(f"{word.title()} (at index {index}) doesn't start with a B.")
...
'Bird (at index 0) starts with a B.'
'Chicken (at index 1) doesn\'t start with a B.'
'Barrel (at index 2) starts with a B.'
'Apple (at index 3) doesn\'t start with a B.'
# The same method can be used as a "lookup" for pairing items between two lists.
# Of course, if the lengths or indexes don't line up, this doesn't work.
>>> word_list = ["cat", "chicken", "barrel", "apple", "spinach"]
>>> category_list = ["mammal", "bird", "thing", "fruit", "vegetable"]
>>> for index, word in enumerate(word_list):
... print(f"{word.title()} is in category: {category_list[index]}.")
...
'Cat is in category: mammal.'
'Chicken is in category: bird.'
'Barrel is in category: thing.'
'Apple is in category: fruit.'
'Spinach is in category: vegetable.'
continue 키워드를 사용하면 다음 반복 주기로 건너뛸 수 있어요:
word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple", "bear"]
# This will skip *bird*, at index 0
for index, word in enumerate(word_list):
if index == 0:
continue
if word.startswith("b"):
print(f"{word.title()} (at index {index}) starts with a b.")
'Barrel (at index 2) starts with a b.'
'Bongo (at index 3) starts with a b.'
'Bear (at index 6) starts with a b.'
break 키워드는 반복을 중단하고 가장 안쪽 loop에서 "빠져나오는" 데 사용할 수 있어요 (C 계열 언어에서처럼요):
>>> word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple"]
>>> for index, word in enumerate(word_list):
... if word.startswith("b"):
... print(f"{word.title()} (at index {index}) starts with a B.")
... elif word == "sliver":
... break
... else:
... print(f"{word.title()} doesn't start with a B.")
... print("loop broken.")
...
'Bird (at index 0) starts with a B.'
'Chicken doesn\'t start with a B.'
'Barrel (at index 2) starts with a B.'
'Bongo (at index 3) starts with a B.'
'loop broken.'
학생들의 시험을 채점하는 조교로 일하고 있어요. 결과를 손으로 기록하는 건 점점 지루해지고 실수하기도 쉬워져요. 그래서 반 전체의 결과를 세고 계산하는 함수 몇 개를 만들어서, 일을 조금 더 재미있게 해보기로 해요.
시험 문제에는 "부분 점수"를 줄 수 있지만, 전체 시험 점수는 int여야 해요.
그래서 반 점수로 다른 작업을 하기 전에, 먼저 성적을 훑으면서 float 점수를 모두 int로 바꿔야 해요. 다행히 Python에는 사용할 수 있는 내장 round() 함수가 있어요.
student_scores라는 list를 매개변수로 받는 round_scores(student_scores) 함수를 만들어요.
이 함수는 입력 list를 _소비_하고, 모든 점수를 int로 변환한 새 배열을 return해야 해요.
결과 list에서 점수의 순서는 중요하지 않아요.
>>> student_scores = [90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3]
>>> round_scores(student_scores)
...
[40, 39, 95, 80, 25, 31, 70, 55, 40, 90]
시험을 채점하다 보니, 기대만큼 성적이 좋지 않은 학생들이 눈에 띄었어요. 그런데 다른 데 정신이 팔려서, 학생이 _몇 명_인지 정확히 적어 두는 걸 깜빡했어요.
student_scores라는 list를 매개변수로 받는 count_failed_students(student_scores) 함수를 만들어요.
이 함수는 합격 점수를 받지 못한 학생 수를 세고, 그 수를 정수로 반환해야 해요.
시험에서 합격하려면 40보다 높은 점수를 받아야 해요.
>>> count_failed_students(student_scores=[90,40,55,70,30,25,80,95,38,40])
5
함께 일하는 선생님은 이번 시험에서 "가장 잘한" 학생들을 찾고 싶어 해요. "가장 잘한"의 기준은 매번 달라지기 때문에, 현재 기준값 이상인 점수를 찾아야 해요.
student_scores(성적의 list)와 threshold("최고 점수" 기준)를 매개변수로 받는 above_threshold(student_scores, threshold) 함수를 만들어요.
이 함수는 threshold보다 크거나 같은(>=) 모든 점수의 list를 반환해야 해요.
>>> above_threshold(student_scores=[90,40,55,70,30,68,70,75,83,96], threshold=75)
[90,75,83,96]
함께 일하는 선생님은 숫자 점수뿐만 아니라 알파벳 등급도 매기고 싶어 해요. 학생들이 시험에서 100점을 받는 일은 드물기 때문에, "알파벳 등급"의 하한 기준은 최고 점수를 바탕으로 계산하고, 최고 점수와 불합격 기준인 <= 40 사이를 균등하게 나눠요.
시험의 "최고" 점수를 인자로 받고, "미국식" 등급 구간 ["D", "C", "B", "A"] 각각의 하한 점수 list를 반환하는 letter_grades(highest) 함수를 만들어요.
"""Where the highest score is 100, and failing is <= 40.
"F" <= 40
41 <= "D" <= 55
56 <= "C" <= 70
71 <= "B" <= 85
86 <= "A" <= 100
"""
>>> letter_grades(highest=100)
[41, 56, 71, 86]
"""Where the highest score is 88, and failing is <= 40.
"F" <= 40
41 <= "D" <= 52
53 <= "C" <= 64
65 <= "B" <= 76
77 <= "A" <= 88
"""
>>> letter_grades(highest=88)
[41, 53, 65, 77]
내림차순으로 정렬된 시험 점수 배열과, 시험 점수를 기준으로 역시 내림차순으로 정렬된 학생 이름 배열이 있어요. 각 학생 이름을 시험 점수와 짝지어서, 반 전체 순위를 출력하고 싶어요.
student_scores와 student_names를 매개변수로 받는 student_ranking(student_scores, student_names) 함수를 만들어요.
student_names list의 각 학생 이름을 student_scores list의 점수와 짝지어요.
각 인자 list는 가장 높은 점수(를 받은 사람)부터 가장 낮은 점수(를 받은 사람) 순으로 정렬되어 있다고 가정해도 돼요.
이 함수는 <rank>. <student name>: <student score> 형식의 문자열 list를 반환해야 해요.
>>> student_scores = [100, 99, 90, 84, 66, 53, 47]
>>> student_names = ['Joci', 'Sara','Kora','Jan','John','Bern', 'Fred']
>>> student_ranking(student_scores, student_names)
...
['1. Joci: 100', '2. Sara: 99', '3. Kora: 90', '4. Jan: 84', '5. John: 66', '6. Bern: 53', '7. Fred: 47']
시험에서 100점이라는 "완벽한" 점수를 받는 일은 드물지만, 적어도 한 명의 학생이 그것을 달성했는지 아는 건 흥미로워요.
student_info를 매개변수로 받는 perfect_score(student_info) 함수를 만들어요.
student_info는 각 학생의 이름과 점수를 담은 list의 list예요: [["Charles", 90], ["Tony", 80]].
이 함수는 시험에서 100점을 받은 학생의 첫 번째 [<name>, <score>] 쌍을 return해야 해요.
student_info에서 100점이 없으면, 빈 list []를 반환해야 해요.
>>> perfect_score(student_info=[["Charles", 90], ["Tony", 80], ["Alex", 100]])
["Alex", 100]
>>> perfect_score(student_info=[["Charles", 90], ["Tony", 80]])
[]
Exercism에 가입하고 Python 트랙을 개념 17개연습 문제 146개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.