본문 바로가기

프로그래밍 언어/코틀린_Kotlin_더파기

Kotlin더파기_10_Collection_List

collect는 '모으다, 수집하다'

collection은 '수집품, 더미'의 뜻이다.

코딩에서 collection(컬렉션)은 '모여있는 값(value)'을 나타낸다.

컬렉션에 모여있는 각각의 값을 element(요소)라고 한다.

 

컬렉션은 3가지 종류가 있다.

 

  • List (리스트) : 각 요소가 순서대로 중복도 가능하게 저장되어 있다.
  • Set (세트) : 요소들이 중복되지 않게 저장되어 있다.
  • Map (맵) : key(키)와 value(값)이 한 쌍을 이루어 저장되어 있다.

 

코틀린의 컬렉션은 2가지 Type(타입)이 있다.

 

mutable : 변경 가능 타입

read-only : 읽기 전용 타입

 

List (리스트)

 

리스트를 저장할 변수를 만든다.

변수 선언은 변하지 않는 val로 선언한다.

listOf 함수는 읽기전용(read-only) list를 반환하기 때문에 var로 선언해도 리스트의 읽기전용은 변하지 않는다.

단, var로 선언하면 변수 자체가 다른 리스트를 참조할 수는 있다.

 

변수명은 myPetList (내 애완동물 목록) 

타입 지정은 변수가 리스트이므로 List 타입을 적고 저장할 요소가 문자열이므로 <String>을 붙여서  : List<String> 으로 적어준다. 이렇게 리스트에 저장된 요소의 타입을 제네릭 기호 < > 안에 적어주면 된다.

 

그런데 Int, String 타입을 사용할 때는 붙이지 않았던 제네릭 기호를 <> 추가로 붙인 이유는 뭘까?

리스트는 문자, 숫자 등 어떤 타입의 데이터도 저장할 수 있는 제네릭 타입(generic type)이기 때문이다.

 

리스트에 값을 넣을 때는 listOf()라는 함수를 사용할 수 있다.

listOf함수는 List객체도 만들고 값도 넣을 수 있기 때문에 코틀린의 타입 추론 기능에 의해 :List<String>은 생략해도 된다.

 

List 요소 사용하기

 

위 그림에서 요소값을 감싸고 있는 기호 [ ] 는 index(인덱스; 색인) 연산자라고 한다.

각 요소의 index는 0부터 시작한다.

그래서 dog는 index가 0이고, bird는 2다.

 

리스트의 요소를 불러오려면 다음과 같이 한다.

 

 

List 클래스에는 첫번째 요소를 불러오는 함수( first() )와 마지막 요소를 불러오는 함수( last() )가 있다.

이 두 함수를 사용해서 어떤 결과값이 나오는 지 확인해 보자.

 

 

 , 

index 유효 범위

 

만약 요소의 범위를 벗어난 index를 호출하면 에러가 나타난다.

코틀린의 REPL을 실행해서 확인해보자.

REPL은 코드를 간단히 빠르게 실행하기 위한 콘솔이다. 

 

참고로 REPL은 인텔리제이 설치에 설명되어 있다.

다음 링크를 참고한다. https://fiftiesstudy.tistory.com/72

 

 

REPL 창에 println(myPetList[5])를 입력하고 Ctrl+Enter(실행)를 누른다.

그러면 위와 같이 ArrayIndexOutOfBoundsException 에러가 나타난다.

설명에는 length(길이) 3 에서 벗어난 Index 5 라고 나타난다.

 

이런 예외 에러를 방지하기 위한 함수가 있다.

 

  • getOrElse ( index ) { 예외 일 때 }
  • getOrNull ( index ) ?: "예외 일 때"

 

REPL창에서 테스트 해보자.

 

첫번째 예에서 인덱스 4가 존재하지 않기 때문에 에러를 내는 대신 람다{ }의 내용을 반환한다.

두번째 예는 인덱스 5가 존재하지 않기 때문에 에러를 내는 대신 null 값을 반환한다.

null일 때 처리할 수 있는 null복합 연산자(일명 엘비스 연산자)를 사용해서 처리한다.

엘비스 연산자( ?: ) 는 왼쪽의 값이 null일 때 오른쪽을 실행하는 null처리용 연산자다.

 

엘비스 연산자는 다음 링크를 참조: https://fiftiesstudy.tistory.com/86 

 

요소 검사하기

 

리스트에 어떤 요소가 있는 지 확인할 때 사용할 수 있는 함수가 있다.

 

contains ( )

containsAll ( )

 

contain은 '포함하다'라는 뜻이다.

contains 함수는 요소 하나를 확인 할 때, containsAll 함수는 하나 이상의 요소를 확인할 때 사용한다.

 

다음의 내용을 메인함수에 코딩해서 확인해보자.

 

"만약 리스트에 cat이 있으면 "고양이 있다" 라는 문자열을 출력하고, 없으면 "고양이 없다"를 출력한다."

 

 

다음 내용도 코딩해 보자.

 

"만약 리스트에 dog, pig가 모두 있으면 "모두 있다" 라는 문자열을 출력하고, 그렇지 않으면 "모두 있지는 않다"를 출력한다."

 

 

List 요소 변경하기

 

위에서 listOf 함수는 요소를 바꿀 수 없는 읽기전용 리스트를 반환한다고 했다.

그래서 리스트 요소를 바꿀 수 있는 함수도 필요하다.

 

mutableListOf ( )

 

mut (move(움직이다), change(바꾸다))는 뜻이고 able는 '할수있는'의 뜻이다. 

그래서 mutable은 "바꿀수 있는" 이라는 뜻이다.

 

mutableListOf 함수를 사용할 때 요소를 더하거나 뺄 때 사용하는 함수도 있어야 한다.

 

add ()

remove ()

 

mutableListOf로 바꾼 후 다음과 같은 내용을 코딩해 보자.

 

  • 리스트 요소를 출력한다.
  • dog를 없앤다.
  • pig를 추가한다.
  • 리스트 요소를 출력한다.

 

 

위에서 알 수 있듯이 새로운 요소는 리스트 끝에 추가된다.

특정 index(인덱스) 위치에 넣고 싶으면 다음과 같이 한다.

 

myPetList.add ( index, "pig")

제일 첫번째 위치에 넣으려면 myPetList.add(0, "pig") 처럼 하면 된다.

 

타입을 바꿔주는 casting(캐스팅)처럼 리스트도 읽기전용과 mutable로 서로 바꿀 수 있는 방법이 있다.

읽기전용으로 바꿀때는 toList() , mutable로 바꿀때는 toMutableList()를 사용한다.

 

val myPetList = mutableListOf("dog", "cat", "bird")

val readOnlyMyPetList = myPetList.toList()

 

요소의 이름을 바꾸고 싶을 때는 인덱스 연산자 [ ] 를 사용하면 된다.

 

첫번째 위치에 pig를 추가하고 이름을 piggy로 바꿔보자.

 

 

변경자 함수(mutator function)

변경자 함수는 변경할 수 있는 List의 요소를 변경하는 함수이다.

많이 사용하는 것들 중에서 위에서 다루지 않은 것은 다음과 같다.

 

addAll ; 같은 타입을 가진 다른 컬렉션의 모든 요소들을 List 끝에 추가함.

(예) myPetList.addAll(listOf("fish", "turtle"))

 

+= ; 지정한 하나의 요소 또는 컬렉션의 요소들을 List끝에 추가함.

(예) mutableListOf("dog", "cat", "bird") += "fish"

(예) mutableListOf("dog", "cat", "bird") += listOf("fish", "turtle")

 

-= ; 지정한 하나의 요소 또는 컬렉션의 요소들을 List에서 삭제함.

(예) mutableListOf("dog", "cat", "bird") -= "dog"

(예) myPetList -= listOf("dog", "cat")

 

clear ; List의 모든 요소들을 삭제함.

(예) mutableListOf("dog", "cat", "bird").clear()

 

removeIf ; 람다에 지정한 조건식을 바탕으로 List 요소들을 삭제함.

(예) myPetList.removeIf { it.contains("d") }

 

반복 (iteration)

List의 각 요소를 자동으로 반복할 수 있는 함수를 사용해서 코드를 간결하게 나타낼 수 있다.

 

for 루프(loop,고리)을 이용한 반복 처리

for은 "~동안, ~를 위한"이라는 뜻이다.

for 루프의 괄호 안에 변수(myPet)를 만들고 리스트 안에 있는(in) 요소들을 위해 실행부분의 내용을 반복한다.

 

 

myPet의 타입은 mutableListOf의 타입에 의해 자동으로 추정된다. 여기서는 String(문자열)이다.

in 키워드는 반복할 객체를 나타낸다.

 

for 루프 대신 forEach 함수를 사용할 수 있다.

 

for, forEach 모두 결과는 같다.

for루프와 forEach 함수의 경우 index(인덱스)를 내부에서 처리한다.

그러므로 리스트의 각 요소의 index 사용해서 표현하려면 forEachIndexed함수를 이용한다.

 

 

forEach , forEachIndexed 함수는 각 요소를 불러와서 반복해서 어떤 일을 처리할 수 있기 때문에 이런 함수를 Iterable 타입이라고 한다.

 

코틀린 더파기08에서 만들었던 Waven's Shop 코드를 이용해서 여러 사람이 음료수를 주문하는 상황을 만들어 본다.

 

 

파일에 있는 데이터를 List에 넣는 방법

 

프로젝트 이름(여기서는 NetHack)에서 마우스 오른쪽 클릭 후 New > Directory(디렉토리) 클릭한다.

디렉토리명은 data로 하자.

 

data 라는 폴더만 만들어졌다.

 

data를 마우스 오른쪽 클릭 후 새 파일을 만든다.

 

파일명을 pet_goods.txt로 하면 텍스트 파일이 만들어진다.

 

텍스트의 내용을 입력한다.

애완동물에 필요한 물품과 가격을 적어 보았다.

 

File 클래스를 넣고 파일 경로명(pathname)을 입력한 후 java.io.File 객체를 생성한다.

 

File 클래스의 readText() 함수는 파일 내용을 한 문자열로 불러온다.

그 다음 각 항목을 분리해서 List 요소로 저장하기 위해 split(쪼개기) 함수를 사용한다.

분리할 문자열을 구분하기 위해 delimiters(구분자)를 사용한다.

 

\r(역슬래시r)은 carriage- return(CR ; 캐리지 리턴)을 나타낸다.(참고로 맥OS, 리눅스에서는 \r은 생략한다)

CR은 '캐리지를 되돌린다'의 뜻이며 타자기에서 글을 한 줄 입력한 후 다시 처음으로 되돌리는 용어다. 

 

\n(역슬래시n)은 newline(새 줄)을 나타낸다.

 

그러므로 텍스트의 내용을 읽어들여서 한 줄을 하나의 데이터 항목으로 분리하라는 명령이 된다.

 

데이터 출력

 

텍스트 내용을 불러와서 List에 추가하고 위와 같이 데이터가 인덱스 값과 함께 출력된다.

 

각 애완동물에 물품을 무작위(shuffled)로 주문하는 내용을 출력해 본다.

 

List는 제일 앞에서 5개 요소까지 변수로 해체(destructure)할 수 있는 기능이 있다.

위 코드 21번 줄과 같이 해체 선언을 사용하여 하나의 표현식에서 여러 개의 변수를 선언하고 값을 지정할 수 있다.

쉼표로 구분된 문자열 데이터에 split함수에 쉼표를 인자로 전달하여 호출하면 각각의 요소(goods, price)로 저장하는 List가 만들어지고 각 변수에 순서대로 저장된다.

 

해체를 원하지 않는 요소에는 밑줄( _ )을 사용하면 된다. 예를 들어 물품, 회사명, 가격이 적힌 데이터에서 회사명이 필요없다면 val (goods, _ , price)로 나타내면 된다.

 

위 코드의 결과는 아래와 같다.

끝.

다음은 두번째 컬렉션 set에 대해 알아본다.

Wraven...