9. 목록(list)

 
  • '평범한 한글'은 여러 개의 데이터를 한꺼번에 묶어서 다를 수 있는 목록(list)을 제공한다.
  • 목록이 있으면 반복해서 계산해야 할 내용을 정리하기가 편리하다.
 

목록 만들기 - ㅁㄹ

  • 목록을 만들려면 ㅁㄹ 함수를 사용한다.
    • ㅁㄹ: 목록. 인수의 개수 만큼 객체를 받아서 목록을 내놓는다.
      • [요소1] [요소2] [요소3]....(요소n] ㅁㄹ ㅎ[요소 개수]
 
ㄴ ㄷ ㄹ ㅁㄹ ㅎㄹ
▶️
[1, 2, 3]
  • 출력 결과로 [1, 2, 3]이 나왔는데, 여기서 [ ]는 일반적으로 목록을 나타내는 기호이다. 하지만 '평범한 한글'은 한글이 아닌 모든 기호를 무시하기 때문에 목록을 만들 때도 [ ] 기호를 쓰지 않는다. 출력할 때만 사용되는 기호이다.
  • 1, 2, 3 세 개의 인수를 사용하여 ㅁㄹ 함수를 호출(ㅎㄹ)하면 세 개의 실수를 담은 목록을 내놓는다.
  • 목록에 들어 있는 자료를 요소(element)라고 한다. [1, 2, 3]은 세 개의 요소를 가진 목록이다.
 
ㅈㅈ ㅎㄱ ㄱㅈ ㅎㄱ ㅁㄹ ㅎㄷ
▶️
[True, False]
  • 논릿값을 목록에 담을 수도 있다. ㅈㅈ ㅎㄱ 는 참(True)을, ㄱㅈ ㅎㄱ 는 거짓(False)을 내놓고, 두 개의 인수로 ㅁㄹ을 호출하여 목록을 만든다.
 
  • '평범한 한글'의 목록은 서로 다른 자료형의 요소를 담을 수 있다. C와 같은 일반적인 명령형 언어의 배열(array)과는 다른 특징이다.
ㄴ ㅈㅈㅎㄱ ㄷ ㄱㅈㅎㄱ ㄹ ㅁㄹ ㅎㅂ
▶️
[1, True, 2, False, 3]
  • 위의 목록에는 세 개의 실수와 두 개의 논릿값이 들어 있다.
    • 이처럼 서로 다른 자료형을 요소로 갖는 목록을 '이종 목록'(heterogeneous list)이라고 한다.
    • 반면, 같은 자료형만 담을 수 있는
      HaskellHaskell
      Haskell
      의 리스트는 '동종 목록'(homogeneous list)이다.
  • 목록에는 '평범한 한글'에서 제공하는 모든 종류의 객체를 다 넣을 수 있다.
ㄹ ㅎ ㅂㄱ ㅎㄱ ㄱㄴㄱ ㅁㅈ ㅎㄴ ㅂ ㅅ ㅈ ㅁㄹㅎㄹ ㅁㄹ ㅎㅁ
▶️
[<Closure created at depth 0>, Nil, '8', [5, 6, 7]]
  • ㄹ ㅎ는 3을 내놓는 함수, ㅂㄱ ㅎㄱ는 빈 값(Nil), ㄱㄴㄱ ㅁㅈ ㅎㄴ 는 문자열 '8'이다.
  • 당연히 목록 안에 또 다른 목록이 들어갈 수 있다. ㅂ ㅅ ㅈ ㅁㄹㅎㄹ 는 [5, 6, 7]이다.
  • 코드의 가독성을 높이려면 다음과 같이 기호를 사용할 수 있다.
[(ㄹ ㅎ), (ㅂㄱ ㅎㄱ), (ㄱㄴㄱ ㅁㅈ ㅎㄴ), [ㅂ ㅅ ㅈ ㅁㄹㅎㄹ]] ㅁㄹ ㅎㅁ
  • 위의 코드는 어디까지나 보기일 뿐이므로 자기가 알아보기 편하게 기호를 사용해도 상관 없다. ('평범한 한글'이 난해한 언어인데 굳이 이렇게 코드를 정리할 필요가.... 있다... 있어.)
  • 아무 요소도 없는 빈 목록을 만들 수도 있다.
ㅁㄹ ㅎㄱ
▶️
[]
  • 아무 인수 없이 ㅁㄹ 을 호출하면 빈 목록을 내놓는다.
 
TMI: 빈 목록[], 빈 값(Nil)

 
프로그램을 짜다 보면 빈 목록(empty list)과 빈 값(Nil)이 헷갈릴 때가 종종 있다. 이 때 위의 '짤'을 보면 뭔가 무릎을 탁 치게 된다. 왼쪽이 빈 목록이고 오른쪽이 빈 값이다.
빈 목록은 목록 자체는 존재하지만 요소가 없는 것이고, 빈 값은 값 자체가 아예 존재하지 않는 상태이다. 그래서 빈 값은 프로그램에서 '정상적이지 않는 상태'를 나타낼 때 주로 쓰인다.
언어에 따라 빈 값을 나타내는 방식도 null, nil, Nil, none, None, Nothing 처럼 다양하다. 모두 정상적인 상태가 아니라 뭔가 잘못된 상태를 나타낸다.
빈 목록에 해당하는 다른 자료형으로는 실수나 숫자에서의 0이 있고, 문자열에서는 빈 문자열("")이 있다. 분명히 이들과 빈 값(Nil)은 달리 구분해야 한다. '평범한 한글'에도 빈 값과 빈 목록을 구분하고 있으며 서로 다른 의미로 사용된다.

 
 

길이도 재고 더하기도 하고... - ㅈㄷ, ㄷ

  • 목록 안에 몇 개의 요소가 들어있는지 확인하려면 ㅈㄷ 함수를 사용한다.
    • ㅈㄷ. 인수로 주어진 목록의 길이를 구한다. 즉 몇 개의 요소가 들어 있는지 구하여 내놓는다. ('길이를 재다'로 외우자)
      • [목록] ㅈㄷ ㅎㄴ
  • 지금까지 우리가 만든 목록의 길이를 재 보자.
ㄴ ㄷ ㄹ ㅁㄹ ㅎㄹ ㅈㄷ ㅎㄴ ㅈㅈ ㅎㄱ ㄱㅈ ㅎㄱ ㅁㄹ ㅎㄷ ㅈㄷ ㅎㄴ ㄴ ㅈㅈ ㅎㄱ ㄷ ㄱㅈ ㅎㄱ ㄹ ㅁㄹ ㅎㅂ ㅈㄷ ㅎㄴ ㄹ ㅎ ㅂㄱ ㅎㄱ ㄱㄴㄱ ㅁㅈ ㅎㄴ ㅂ ㅅ ㅈ ㅁㄹㅎㄹ ㅁㄹ ㅎㅁ ㅈㄷ ㅎㄴ ㅁㄹ ㅎㄱ ㅈㄷ ㅎㄴ
▶️
3 2 5 4 0
  • 마지막의 빈 목록(ㅁㄹ ㅎㄱ)은 아무 요소도 없기 때문에 길이가 0이다.
 
  • 으로 숫자를 더하는 것처럼 목록을 더할 수도 있다.
    • : 더하기. 여러 개의 목록을 더하여 하나의 목록을 내놓는다.
ㄴ ㄹ ㅂ ㅁㄹㅎㄹ ㄷ ㅁ ㅁㄹㅎㄷ ㄷㅎㄷ
▶️
[1, 3, 5, 2, 4]
  • 한 개의 목록에 다른 목록을 더할 때는 항상 이전 목록의 끝부터 더한다.
ㄴ ㅁㄹㅎㄴ ㄷ ㄹ ㅁㄹㅎㄷ ㅁ ㅂ ㅅ ㅁㄹㅎㄹ ㄷ ㅎㄹ
▶️
[1, 2, 3, 4, 5, 6]
  • [1], [2, 3], [4, 5, 6] 세 개의 목록을 더하면 당연히 [1, 2, 3, 4, 5, 6] 이 된다. 몇 개의 목록을 더하더라도 하나의 목록은 이전 목록의 제일 끝에 붙는다.
  • 당연한 것이지만 여러 개의 목록을 더할 때에는 더하려는 인수의 개수를 잘 따져서 함수를 호출해야 한다. 위에서는 세 개의 목록을 더하기 때문에 ㄷ ㅎㄹ를 사용했다.
 
  • 다음 코드의 실행 결과는 무엇일까?
ㄴ ㄷ ㄹ ㅁㄹㅎㄹ ㅁ ㅂ ㅁㄹㅎㄷ ㅁㄹㅎㄷ ㄴㄱ ㄷㄱ ㄹㄱ ㅁㄹㅎㄹ ㄷ ㅎㄷ
① [[1, 2, 3], [4, 5], [-1, -2, -3]] ② [[1, 2, 3], [4, 5], -1, -2, -3] ③ [1, 2, 3, 4, 5, -1, -2, -3]
정답
②번. 목록 안에 다시 목록이 있다고 해도, 더하려는 요소는 실수 객체이기 때문.
만일 ①처럼 만들고 싶다면 다음과 같이 해야 한다.
ㄴ ㄷ ㄹ ㅁㄹㅎㄹ ㅁ ㅂ ㅁㄹㅎㄷ ㅁㄹㅎㄷ ㄴㄱ ㄷㄱ ㄹㄱ ㅁㄹㅎㄹ ㅁㄹㅎㄴ ㄷ ㅎㄷ
 
 
  • 목록을 더할 때 빈 목록은 결과에 아무런 영향도 주지 않는다.
ㅁㄹㅎㄱ ㄴ ㅁㄹㅎㄴ ㄷ ㄹ ㅁㄹㅎㄷ ㅁ ㅂ ㅅ ㅁㄹㅎㄹ ㅁㄹㅎㄱ ㄷ ㅎㅂ
▶️
[1, 2, 3, 4, 5, 6]
  • 빈 목록은 여러 개를 아무리 더해도 빈 목록이다.
ㅁㄹㅎㄱ ㅁㄹㅎㄱ ㅁㄹㅎㄱ ㅁㄹㅎㄱ ㅁㄹㅎㄱ ㄷㅎㅂ
▶️
[]
  • 빈 목록 다섯 개를 더해도 결과는 빈 목록이다. 0 + 0 + 0 + 0 + 0 = 0과 같은 이치.
 

번호로 목록 요소 가져오기

  • 목록에 있는 요소는 위치에 따라 번호(index)를 갖고 있다. 첫번째 요소는 0, 두번째는 1, 세번째는 2... 이런 식이다. 일반적인 프로그래밍 언어에서 쓰는 방식이다.
  • 목록에서 특정 번호의 요소에 접근하려면 다음과 같이 한다
    • [요소 번호] [목록] ㅎㄴ
    • 요소 번호 가 0 이상 이면 해당하는 위치의 요소에 접근한다.
    • 요소 번호 가 음수이면 뒤에서부터 세어서 접근한다. 맨 뒤의 요소는 -1이다.
 
ㄱ ㅁ ㅂ ㅅ ㅈ ㅁㄹㅎㅁ ㅎㄴ ㄴㄱ ㅁ ㅂ ㅅ ㅈ ㅁㄹㅎㅁ ㅎㄴ
▶️
4 7
  • ㅁ ㅂ ㅅ ㅈ ㅁㄹㅎㅁ 은 [4, 5, 6, 7] 목록을 만든다.
  • 이 목록에 각각 0-1을 인수로 넣어서 호출(ㅎㄴ)하면 4와 7을 내놓는다. 0은 첫 번째 요소 번호이고, -1은 맨 뒤의 요소 번호이다.
  • 이렇게 보면 목록을 마치 함수 처럼 호술하는 모양세이다. 논릿값을 호출해서 조건 판단을 하는 것과 비슷한 원리로 볼 수 있다.
 
  • 만약에 목록의 길이보다 더 큰 요소 번호로 호출하면 어떻게 될까?
ㄷㄴㄱ ㅁ ㅂ ㅅ ㅈ ㅁㄹㅎㅁ ㅎㄴ
▶️
문제가 생겼습니다. EvalError: Unexpected value: undefined
  • undeinfed 는 정의되지 않은 값, 즉 의미가 없는 값이라는 뜻이다.
  • 목록의 길이보다 더 큰 요소 번호를 사용하면 안 되는 것은 너무나 당연한 일이지만, 대놓고 이렇게 틀리는 경우 보다 버그 때문에 요소 번호 계산에 오류가 생겨서 발생하는 때가 더 많다.
 

부분 목록 발췌하기 - ㅂㅈ

  • 목록에서 두 개 이상의 요소를 가져와서 새로운 목록을 만들려면 ㅂㅈ 함수를 사용한다.
    • ㅂㅈ. 발췌(slice). 목록 하나와 실수 세 개를 받아서 목록이 가진 요소를 발췌하여 새로운 목록을 내놓는다.
      • [목록] [처음 위치] [포함하지 않을 위치] [건너뛸 값] ㅂㅈ ㅎ[인수 개수)]
      • 목록: 발췌할 목록
      • 처음 위치: 발췌를 시작할 위치. 음수를 지정하면 끝에서 부터 센다.
      • 포함하지 않을 위치: 발췌를 마치고 포함하지 않을 위치. 생략하면 목록의 끝까지 발췌. 음수를 지정하면 끝에서 부터 센다.
      • 발췌할 단위: 요소를 발췌할 때 단위를 정한다. 생략하면 건너뛰지 않고 모든 요소를 발췌.
 
ㄴ ㄷ ㄹ ㄴㄱ ㄷㄱ ㄹㄱ ㅁㄹㅎㅅ ㄱ ㄹ ㅂㅈㅎㄹ ㄴ ㄷ ㄹ ㄴㄱ ㄷㄱ ㄹㄱ ㅁㄹㅎㅅ ㄹㄱ ㅂㅈㅎㄷ ㄴ ㄷ ㄹ ㄴㄱ ㄷㄱ ㄹㄱ ㅁㄹㅎㅅ ㄱ ㅅ ㄹ ㅂㅈㅎㅁ
▶️
[1, 2, 3] [-1, -2, -3] [1, -1]
  • [1, 2, 3, -1, -2, -3] 목록에서 0번째 요소부터 3번째 요소 전까지 발췌하면 [1, 2, 3] 목록이 된다.
    • '처음 위치'와 '포함하지 않을 위치'를 지정해 주었다.
  • [1, 2, 3, -1, -2, -3] 목록의 -3번째, 즉 끝에서 베 번째 요소에서 끝까지 발췌하면 [1, 2, 3]이 된다.
    • '처음 위치'만 지정해 주었다. 포함하지 않을 위치를 생략하면 목록의 맨 끝 요소가 된다.
  • [1, 2, 3, -1, -2, -3] 목록의 0번째(처음)부터 6번째 전(5번째) 요소까지 발췌하되, 3 개 단위로 발췌하면 5번째와 3번째 요소가 발췌된다. [1, -1] 목록이 만들어진다.
    • '처음 위치', '포함하지 않을 위치', '발췌할 단위'가 모두 지정되었다.
    •  

요소 마다 함수 적용(map) - ㅁㄷ

  • 목록이 유용한 것은 여러 개의 데이터를 한 데 묵어서 다룰 수 있고, 함수를 이용해서 얼마든지 마음대로 가공할 수 있기 때문이다.
  • 목록에 있는 요소를 가공해서 새로운 목록을 만들어 낼 수 있다. 함수형 언어의 중요한 구성 요소 중 하나인 map 이다.
    • ㅁㄷ: 요소 '마다' 적용. 목록 하나와 함수 하나를 받아서 요소마다 함수를 적용하여 새 목록을 내놓는다.
      • [목록] [함수] ㅁㄷ ㅎㄷ
      • 목록: 적용할 요소들이 담긴 목록
      • 함수: 요소 마다 적용할 함수. 1개의 인수를 갖는다.
 
ㄴ ㄷ ㄹ ㅁ ㅂ ㅁㄹㅎㅂ ㄱㅇㄱ ㄷ ㅅㅎㄷ ㅎ ㅁㄷ ㅎㄷ
▶️
[1, 4, 9, 16, 25]
  • ㅁㄷ 함수는 두 개의 인수를 받는다.
    • 여기서는 [1, 2, 3, 4, 5] 목록과
    • 첫 번째 인수(ㄱㅇㄱ)를 제곱하는(ㄷ ㅅㅎㄷ) 함수()이다.
  • ㅁㄷ 함수는 목록의 각 요소에 ㄱㅇㄱ ㄷ ㅅㅎㄷ ㅎ 를 적용하면, 이 함수의 ㄱㅇㄱ 는 목록의 각 요소가 된다. 이를 이용해서 함수에서 필요한 가공을 하기만 하면 ㅁㄷ 함수가 이를 모아서 새로운 목록을 내놓는다.
 
ㄴ ㄹ ㅂ ㅈ ㄷ ㅁ ㅅ ㅁㄹㅎㅈ ㄱㅇㄱ ㅁㅈㅎㄴ ㅎ ㅁㄷㅎㄷ
▶️
['1', '3', '5', '7', '2', '4', '6']
  • 주어진 목록에 있는 요소를 문자열로 바꾼 목록을 만든다. 주어진 인수를 문자열로 바꾸는 함수(ㄱㅇㄱ ㅁㅈㅎㄴ ㅎ)가 열심히 일을 하면 ㅁㄷ 함수는 결괏값을 잘 갈무리하여 새로운 목록을 만들어 내놓는다.
 
ㄱ ㅁㄹㅎㄴ ㄱ ㄴ ㄷ ㅁㄹㅎㄹ ㄱ ㄴ ㄷ ㄹ ㅁ ㅁㄹㅎㅂ ㄱ ㄴ ㄷ ㄹ ㅁ ㅂ ㅅ ㅁㄹㅎㅈ ㅁㄹㅎㅁ ㄱㅇㄱ ㅈㄷㅎㄴ ㅎ ㅁㄷㅎㄷ
▶️
[1, 3, 5, 7]
  • 네 개의 목록을 만들고 이 목록의 목록을(!) 만든다. 목록에는 '평범한 한글'의 어떤 자료형을 가진 요소도 다 넣을 수 있다는 점을 기억하면 목록 안에 목록을 담을 수 있다는 건 당연한 일이다.
  • 네 개의 목록이 담긴 목록에 각 요소의 길이를 구하는 함수(ㄱㅇㄱ ㅈㄷㅎㄴ ㅎ)를 전달하여 ㅁㄷㅎㄷ 를 호출하면 각 목록의 길이를 구해서 [1, 3, 5, 7]을 내놓는다.
     

    조건에 따라 선별(filter) - ㅅㅂ

    • 목록에 있는 요소들 중 조건에 맞는 것들만 선별해서 새로운 목록을 만들 수 있다. 함수형 언어에서 map과 함께 중요하게 다루어지는 filter이다.
      • ㅅㅂ: 선별. 목록 하나와 함수 하나를 받아서 조건에 맞는 요소만 뽑아내서 새로운 목록을 만든다.
        • [목록] [조건 함수] ㅅㅂ ㅎㄷ
        • 목록: 조건을 검사할 목록
        • 조건 함수: 요소 하나하나를 판별해서 논릿값(True, False)을 내놓는 함수.
     
    ㄴ ㄹ ㅂ ㅈ ㄷ ㅁ ㅅ ㅁㄹㅎㅈ ㄱㅇㄱ ㅂ ㅈㅎㄷ ㅎ ㅅㅂ ㅎㄷ
    ▶️
    [1, 3, 2, 4]
    • [1, 3, 5, 7, 2, 4, 6] 목록에서 5보다 작은지 판별하는 함수(ㄱㅇㄱ ㅂ ㅈㅎㄷ ㅎ)를 이용해서 목록을 걸러주면 [1, 3, 2, 4]로 이루어진 목록이 된다.
     
    ㄴ ㄹ ㅂ ㅈ ㄷ ㅁ ㅅ ㅁㄹㅎㅈ ㅂ ㄱㅇㄱ ㅈㅎㄷ ㅁㅎㄱ ㅎ ㅅㅂ ㅎㄷ
    ▶️
    [7, 6]
    • [1, 3, 5, 7, 2, 4, 6] 목록에서 5보다 큰 요소를 선별하려면 조건으로 ㅂ ㄱㅇㄱ ㅈㅎㄷ ㅎ 를 이용한다. "인수보다 5가 작다"는 것은 "인수가 5보다 크다"와 똑같은 말이다. 그래서 '평범한 한글'에서는 따로 '보다 크다' 함수가 없다.
    ㄴ ㄷ ㄹ ㅁ ㅂ ㅅ ㅈ ㄱㄴㄱ ㄴㄴㄱ ㄷㄴㄱ ㅁㄹ ㅎㄷㄴㄱ ㄴㄱ ㄱㅇㄱ ㅅㅎㄷ ㄴㄱ ㄴ ㅎㄷ ㅎ ㅅㅂ ㅎㄷ
    ▶️
    [1, 3, 5, 7, 9]
    • [1, 2, 3, 4, 5, 6 7, 8, 9, 10] 목록 중 홀수룰 선별한다. 어떤 값이 홀수인지 짝수인지 알아보는 방법은 많이 있지만 여기서는 을 이용한다. -1을 홀수번 곱하면 -1이, 짝수번 곱하면 1이 나오기 때문. ㄴㄱ ㄱㅇㄱ ㅅㅎㄷ ㄴㄱ ㄴ ㅎㄷ ㅎ인지 판별하는 함수이다.
     
    '평범한 한글'에서 목록을 다루는 방법은 이 밖에 굉장히 다양하다. 하지만 목록 다루는 기본 기술을 잘 갈고 닦다 보면 '함수형 프로그래밍'을 좀 더 몸으로 체험할 수 있으리라 생각한다.