본문 바로가기
Kotlin/Kotlin 프로그래밍

[Kotlin] 배열(Array)

by 주 녕 2021. 5. 18.
728x90

모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다. 

 

배열 다루기

코틀린에서 배열은 Array 클래스로 표현함

 

배열 사용 방법

[ 기본적인 배열 표현 ]

  • arrayOf() : 기본 생성자
  • Array() : 기본 생성자
  • arrayOfNulls() : 빈 상태의 배열
    val numbers = arrayOf(4, 5, 6, 3)
    val animals = arrayOf("Cat", "Dog", "Lion")

 

[ 다차원 배열 ]

    val array1 = arrayOf(1, 2, 3)
    val array2 = arrayOf(4, 5, 6)
    val array3 = arrayOf(7, 8, 9)
    
    // 방법 1
    val arr2d = arrayOf(array1, array2, array3)
    // 방법 2
    val arr2d = arrayOf(arrayOf(1, 2, 3), arrayOf(4, 5, 6), arrayOf(7, 8, 9))

2차원 이상의 배열도 표현 가능함.

하지만 너무 많은 차원의 배열은 접근하기 복잡해 버그가 발생활 확률이 높아짐!

 

[ 여러 가지 자료형이 혼합된 배열 ]

특정 자료형으로 제한하지 않는다면 배열의 요소로 정수, 문자열, Boolean 등 여러 가지 자료형을 혼합할 수 있음

특정 자료형으로 제한하기 위해서는

  • arrayOf<자료형>()
  • 자료형+ArrayOf()
    • 기본형의 배열 : charArrayOf(), booleanArrayOf(), longArrayOf(), shortArrayOf(), byteArrayOf(), intArrayOf() 등
    • 부호 없는 자료형에 대한 배열 : ubyteArrayOf(), ushortArrayOf(), uintArrayOf(), ulongArrayOf()

 

[ 배열 요소에 접근하기 ]

Array 클래스는 get()과 set() 메서드를 가지고 있는데 이것은 요소에 접근하기 위한 getter/setter임.

대괄호를 사용해도 접근할 수 있는데 이것은 연산자 오버로딩으로 정의되었기 때문

import java.util.Arrays

fun main() {
    val arr = intArrayOf(1, 2, 3, 4, 5)

    println("arr: ${Arrays.toString(arr)}")
    println("size: ${arr.size}")
    println("sum(): ${arr.sum()}")
    // getter에 의한 접근
    println(arr.get(2))
    println(arr[2])

    // setter에 의한 값 설정
    arr.set(2, 7)
    arr[0] = 8
    println("size: ${arr.size} arr[0]: ${arr[0]}, arr[2]: ${arr[2]}")
    
    // 루프를 통한 배열 요소 접근
    for(i in 0..arr.size-1) {
        println("arr[$i] = ${arr[i]}")
    }
}
  • Arrays.toString() : 배열의 내용을 문자열로 변환하여 출력 (java.util.Arrays를 임포트하여 문자열 변환을 쉽게 함)
    • Arrays.deepToString() : 다차원 배열을 문자열로 변환하여 출력
  • size : 배열의 크기
  • sum() : 배열의 합을 계산
  • 배열의 요소 접근 : arr.get()arr[]은 동일한 표현
  • 배열의 요소 설정 : arr.set(인덱스, 값)arr[인덱스] = 값은 동일한 표현
  • 루프를 통한 배열 접근 : in 키워드를 사용하며, 범위 지정자를 통해 0부터 size-1까지 반복(인덱스는 항상 0에서 시작하므로)

 

[ 표현식을 통해 배열 생성하기 ]

val | var 변수명 = Array(요소 개수, 초깃값)

  • Array() 생성자의 첫번째 인자 : 요소 개수 - 표현식 결과에 대한 요소 개수
  • Array() 생성자의 두번째 인자 : 초깃값 - 배열의 요소를 결정하는 표현식
    • 람다식 초깃값 - init: (Int) -> T
    • val arr3 = Array(5, {i -> i*2}) → [0, 2, 4, 6, 8]

 


 

배열 제한하고 처리하기

배열은 정의되면 배열의 길이와 내용이 메모리에 고정됨

→ 선언된 배열을 잘라내거나 덧붙일 수 없음

만일 고정되지 않은 동적 배열이 필요하다면 컬렉션의 하나인 List를 사용해야 함

 

[ 배열에 요소 추가하고 잘라내기 ]

배열이 정의되면 고정되기 때문에 새롭게 할당하는 방법으로 요소를 추가하거나 잘라낼 수 있음

    val arr1 = intArrayOf(1, 2, 3, 4, 5)
    
    val arr2 = arr1.plus(6)
    println("arr2: ${Arrays.toString(arr2)}")  // arr2: [1, 2, 3, 4, 5, 6]
    
    val arr3 = arr1.sliceArray(0..2)
    println("arr3: ${Arrays.toString(arr3)}")  // arr3: [1, 2, 3]
  • plus(새로운 요소) : 새로운 요소를 추가하기 위한 메서드
  • sliceArray(범위의 시작 인덱스..끝 인덱스) : 배열을 자르기 위한 메서드

 

[ 기타 배열 관련 API ]

코틀린 표준 라이브러리 Array의 유용한 메서드

  • arr.first() : 첫번째 요소 확인
  • arr.last() : 마지막 요소 확인
  • arr.indexOf(인덱스) : 요소의 인덱스 출력
  • arr.average() : 배열의 평균 값 출력
  • arr.count() : 요소 개수 세기
  • reversedArray(), reverse() : 요소의 순서를 뒤집는 메서드
  • arr.sum() : 요소를 합산하는 메서드
  • arr.contains(요소) : 요소가 포함되어 있는지 확인하는 메서드 (요소 in arr 과 같은 표현)

 

[ Any로 선언된 배열 ]

자료형이 지정된 배열은 다른 자료형으로 변환할 수 없음

BUT, Any 자료형으로 만들어진 배열은 기존 자료형을 다른 자료형으로 지정할 수 있음

fun main() {
    val b = Array<Any>(10, {0})
    b[0] = "Hello World"
    b[1] = 1.1
    println(b[0])
    println(b[1])
    println(b[2])
}

0으로 채워진 배열 10개를 생성했고, 이후 문자열과 실수형 값을 지정하고 있음

처음에는 배열의 모든 요소가 정수형이었지만, 할당한 값에 따라 요소의 자료형이 변환되었음

→ Any를 사용하면 한 번에 기본적인 초기화를 하고 나중에 원하는 자료형으로 요소를 초기화할 수 있는 편리성이 있음

 

[ 멤버 메서드를 통한 배열 순환 ]

fun main() {
    var arr = intArrayOf(1, 2, 3, 4, 5)

    arr.forEach { element -> print("$element") }

    arr.forEachIndexed { i, e -> println("arr[$i] = $e") }

    val iter: Iterator<Int> = arr.iterator()
    while (iter.hasNext()) {
        val e = iter.next()
        print("$e ")
    }
}
  • forEach() : 요소 개수 만큼 지정한 구문을 반복 실행
  • forEachIndexed() : 순환하며 인덱스까지 출력
    • 인덱스는 i로, 요소는 e로 받아 화살표 표현식 오른쪽 구문에 넘겨 인덱스와 함께 출력
  • iterator() : 반복을 위한 요소를 처리하는 메서드
    • hasNext() : 배열에서 참조할 다음 요소가 있는지 확인하는 메서드
    • next () : 다음 요소를 반환하는 메서드

 


 

배열 정렬하기

정렬(sort)는 특정한 순서에 따라 나열한다는 것 (오름차순 - ascending, 내림차순 - descending)

어떤 정렬 알고리즘 기법을 사용하느냐에 따라 성능이 많이 좌우되며, 직접 정렬 기능을 만들어 쓸 수도 있지만

Array는 기본적인 정렬 알고리즘을 제공하고 있음

 

[ 기본 정렬 ]

fun main() {
    val arr = arrayOf(8, 4, 3, 2, 5, 9, 1)

    val sortedNum = arr.sortedArray()
    println("ASC : "+Arrays.toString(sortedNum))  // ASC : [1, 2, 3, 4, 5, 8, 9]

    val sortedNumDesc = arr.sortedArrayDescending()
    println("DEC : "+Arrays.toString(sortedNumDesc))  // DEC : [9, 8, 5, 4, 3, 2, 1]

    arr.sort(1, 3)
    println("ORI : "+Arrays.toString(arr))  // ORI : [8, 3, 4, 2, 5, 9, 1]
    arr.sortDescending()
    println("ORI : "+Arrays.toString(arr))  // ORI : [9, 8, 5, 4, 3, 2, 1]

    val listSorted: List<Int> = arr.sorted()
    val listDesc: List<Int> = arr.sortedDescending()
    println("LST : "+listSorted)  // LST : [1, 2, 3, 4, 5, 8, 9]
    println("LST : "+listDesc)  // LST : [9, 8, 5, 4, 3, 2, 1]

    val items = arrayOf<String>("Dog", "Cat", "Lion", "Kangaroo", "Po")
    items.sortBy { item -> item.length }
    println(Arrays.toString(items))  // [Po, Dog, Cat, Lion, Kangaroo]
}
  • sortedArray(), sortedArrayDescending() : 정렬된 배열 반환
  • sort(), sortDescending() : 원본 배열에 대한 정렬
    • sort(fromIndex, toIndex), sortDescending(fromIndex, toIndex) : 특정 인덱스 구간만 정렬
    • List 컬렉션으로 반환할 때도 해당 메서드를 사용하여 반환 (컬렉션은 Arrays.toString() 필요 없이 바로 출력 가능)
  • sortBy() sortByDescending() : 특정 표현식을 넣어 정렬

 

[ 데이터 클래스 정렬 ]

데이터 클래스도 Array에서 확장된 sortBy() 함수를 이용하여 해당 멤버 변수에 따라 정렬할 수 있음

data class Product(val name: String, val price: Double)

fun main() {
    val products = arrayOf(
        Product("Snow Ball", 870.00),
        Product("Smart Phone", 999.00),
        Product("Drone", 240.00),
        Product("Mouse", 333.35),
        Product("Keyboard", 125.99),
        Product("Monitor", 1500.99),
        Product("Tablet", 512.99)
    )

    products.sortBy { it.price }
    products.forEach { println(it) }
}

→ products를 it으로 넘기고 제품의 가격인 price가 제일 낮은 것을 기준으로 오름차순 정렬

  • forEach나 sortBy처럼 소괄호를 사용하지 않고 있는 함수는 인자가 람다식 1개이므로 생략된 것
  • Array<out T>는 값을 만들어내는 출력의 용도로 sortBy()는 Array의 확장 함수 
    • 람다식에 T 인자를 전달해 그 결과를 R?과 같이 null이 가능한 형식으로 받을 수 있음
    • Ctrl+B 를 누르면 선언부를 참조해 함수의 정의를 확인할 수 있음

 

[ 비교자로 정렬 ]

public fun <T> Array<out T>.sortWith(comparator: Comparator<in T>) : Unit

fun main() {
    val products = arrayOf(
        Product("Snow Ball", 870.00),
        Product("Smart Phone", 999.00),
        Product("Drone", 240.00),
        Product("Mouse", 333.35),
        Product("Keyboard", 125.99),
        Product("Monitor", 1500.99),
        Product("Tablet", 512.99)
    )

    products.sortWith(
        Comparator<Product> { p1, p2 ->
            when {
                p1.price > p2.price -> 1
                p1.price == p2.price -> 0
                else -> 1
            }
        }
    )

    products.forEach { println(it) }
}
  • sortWith()는 Comparator를 매개변수로 가지고 있음을 알 수 있음
    • Comparator는 자바의 인터페이스로서 2개의 객체를 비교하는 compare()를 구현함
    • 람다식 p1, p2 -> when {...}을 이용해 비교 결과가 p1이 p2보다 크면 1, 같으면 0, 작으면 -1을 반환하도록 구현
    products.sortWith(compareBy({it.name}, {it.price}))
    products.forEach { println(it) }
  • compareBy()를 함께 사용하면 이름을 먼저 정렬하고, 그 다음 이름이 동일한 경우 가격 기준으로 다시 정렬함

compareBy를 사용하기 전, 사용한 후의 결과 비교

 

[ 배열 필터링 ]

filter() 메서드를 사용하면 원하는 데이터를 골라낼 수 있음

fun main() {
    val fruits = arrayOf("Banana", "Avocado", "Apple", "Kiwi")
    fruits.filter { it.startsWith("A") }
        .sortedBy { it }
        .map { it.toUpperCase() }
        .forEach { println(it) }  // APPLE AVOCADO
}
  1. filter()로 A로 시작하는 요소를 골라냄
  2. sortedBy()와 함께 필요한 정보를 골라내고 정렬함
  3. 골라낸 요소를 map으로 받아 toUppercase()로 대분자로 변경
  4. forEach()로 출력

이렇게 연속해서 호출하는 방법을 메서드 체이닝(Method Chaining)이라고 함

  • 각 결과를 it으로 넘겨받아 처리할 수 있어 유용
  • BUT 특정 메서드에서 오류가 나면 디버깅하기 어려워지기 때문에 주의해야 함
  • 배열에 특정 요소가 있는지 확인하는 간단한 방법은 when문을 사용하는 것
    println(products.minBy { it.price })
    println(products.maxBy { it.price })
  • 객체 product에서 가장 낮은 가격이나 높은 가격을 골라내기 위해서 minBymaxBy를 사용할 수 있음

 


 

배열 평탄화(flatten)

평탄화(flatten) : 다차원 배열을 단일 배열로 만드는 것 → flatten()

fun main() {
    val numbers = arrayOf(1, 2, 3)
    val strs = arrayOf("one", "two", "three")
    val simpleArray = arrayOf(numbers, strs)
    simpleArray.forEach { println(it) }
    // [Ljava.lang.Integer;@677327b6
    // [Ljava.lang.String;@14ae5a5

    val flattenSimpleArray = simpleArray.flatten()
    println(flattenSimpleArray)  // [1, 2, 3, one, two, three]
}
  • 첫번째 실행결과에서는 배열 자체가 들어있기 때문에 객체의 시그니처가 출력됨
  • 평탄화 작업을 거치면 2개의 배열을 단일 배열로 만들 수 있음

 

 

 

* <out T>, <in T>에 대한 내용은 아래 포스팅에서 확인!

 

[Kotlin] 제네릭(Generic)

모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다. 제네릭(Generic) : 클래스 내부에서 사용할 자료형을 인스턴스를 생성할 때 확정하는 것 자료형의 객체들을 다루는 메서드나

junyoung-developer.tistory.com

728x90

댓글