본문 바로가기
♞ | 공부일지/♝ | TIL

[Android, 내일배움캠프] 공부일지(2024-06-05)

by immgga 2024. 6. 6.
오늘 공부한 내용 정리(2024년 6월 5일)

 

 

1. 코드카타 문제풀이

A. CCW(백준, 11758번)

문제 내용

 

문제 풀이 방법

  • 문제 이름에 힌트가 나와있다. ccw 알고리즘을 이용해 문제를 해결한다.
  • ccw 알고리즘은 3개의 점의 방향을 알 수 있는 알고리즘이다.

 

해결 코드(스포 주의)

더보기
import java.io.BufferedReader
import java.io.InputStreamReader

fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
    val pointList = mutableListOf<List<Int>>()
    for (i in 0 until 3) {
        val point = readLine().split(" ").map { it.toInt() }
        pointList.add(point)
    }

    // ccw 알고리즘: 각 점의 방향성을 파악할 수 있다.
    // 점들의 좌표를 정의한 후 사선 공식(신발끈 공식)을 이용해 문제를 해결할 수 있다.
    // x: x1, y1   y: x2, y2   z: x3, y3
    // x1 x2 x3 x1
    // y1 y2 y3 y1
    val ccw = ccw(
        x1 = pointList[0][0], y1 = pointList[0][1],
        x2 = pointList[1][0], y2 = pointList[1][1],
        x3 = pointList[2][0], y3 = pointList[2][1]
    )
    if (ccw > 0) println(1)
    else if (ccw < 0) println(-1)
    else println(0)
}

private fun ccw(x1: Int, y1: Int, x2: Int, y2: Int, x3: Int, y3: Int): Int {
    val a = x1 * y2 + x2 * y3 + x3 * y1
    val b = y1 * x2 + y2 * x3 + y3 * x1
    return a - b
}

 

풀이 과정

  • 좌표를 입력받고 ccw 알고리즘을 이용한다.
  • 점 3개의 좌표가 주어졌을 때(x: x1, y1, y: x2, y2, z: x3, y3) 사선 공식(신발끈 공식)을 이용해 문제를 해결한다.

출처: https://degurii.tistory.com/47#code

  • 빨간 빗금의 값을 a, 파란 빗금의 값을 b라고 할 때, a - b를 해서 점 3개의 방향성(ccw)을 숫자로 알 수 있다.

입력받은 [1,1], [5, 5], [7, 3]을 신발끈 공식을 쓰기 위해 정렬한 모습

  • ccw가 0보다 크면 시계 방향이므로 -1을, 작으면 반시계 방향으로 1을, 0인 경우는 직선이므로 0을 출력한다.

 

문제 해결 과정

  • ccw알고리즘을 알고 있으면 쉽게 문제를 해결할 수 있다.
  • ccw의 사선 공식을 알고 있으면 점들 간의 방향을 알 수 있다.
  • ccw는 기하학 알고리즘 중 가장 자주 쓰이는 알고리즘이라고 한다. 제대로 이해해서 유용하게 써먹자.
  • https://degurii.tistory.com/47#code

 

2. 스파르타 코딩 클럽 강의 수강

A. Kotlin 문법 종합반 4주 차

1강(접근 제한자)

  • 접근 제한자: 변수나 메서드의 접근을 제한한다.
    public, private, internal, protected로 접근을 제한한다.
  • public: 명시하지 않으면 기본적으로 public(어디서나 접근 가능)
  • private: 동일한 클래스 내부에서만 접근 가능
  • internal: 같은 모듈 내부에서만 접근 가능
  • protected: 기본적으로 private이지만 상속을 받은 경우에 타 모듈에서 접근 가능
  • 접근 제한자를 사용하는 이유
    전근권한을 설정해 무분별한 접근을 차단할 수 있다.
    클래스들 간의 접근하면 안 되는 상황을 구분하기 때문에 유지 보수에 용이하다.

 

2강(예외 처리)

  • 예외 처리: 프로그램 실행 도중 발생하는 예외를 적절히 처리하는 것.
    예외는 프로그램 실행 중에 발생하는 컴파일 에러인데, 예외가 발생하면 프로그램이 비정상적으로 종료된다.
// 기본적인 try-catch문 사용 방법
try {
    예외가 발생할 가능성이 존재하는 코드
} catch(예외종류) {
    예외가 발생했을때 처리할 코드
} finally {
	try-catch와 함께 수행되는 코드
}

// throw 키워드를 사용해 예외를 강제로 발생시킬 수 있다.
if(num1 > 10) {
    throw 예외종류
}
  • 예외 처리를 하는 이유
    고품질의 프로그램은 사용성을 해치지 않아야 한다.
    프로그램이 비정상 종료되는 것은 심각한 문제이다.
    미리 예외가 발생하는 경우를 생각하고 소스 코드를 작성해야 안정성이 높을 프로그램을 만들 수 있다.

 

3강(지연 초기화)

  • 지연 초기화: 변수나 상수의 값을 나중에 초기화할 수 있다.
    코틀린은 클래스를 설계할 때, 안정성을 위해 반드시 변수의 값을 초기화할 것을 권장한다.
    클래스를 정의할 때, 초기의 값을 정의하기 난처한 상황이 올 때 나중에 대입하기 위한 방법이다.
  • lateinit, lazy 키워드를 사용해 지연 초기화를 할 수 있다.
    변수: lateinit, 상수: lazy
  • 지연 초기화를 적절히 사용해 컴퓨터의 메모리를 더 효율적으로 사용할 수 있다. 
// lateinit 기본적인 사용 방법.
lateinit var name:String
var age:Int = 0

// isInitialized로 lateinit 변수가 초기화되어 있는지 체크할 수 있다.
if(this::name.isInitialized) {
    println("이름은: ${name} 입니다.")
    println("나이는: ${age} 입니다.")
} else {
    println("name변수를 초기화해주세요.")
}

// lazy 기본적인 사용 방법
val address: String by lazy {
    println("address 초기화")
    "seoul"
}

// lazy 변수가 호출되는 시점에 초기화 코드를 수행한다.
println("주소는: ${address} 입니다.")

 

 

4강(널 세이프티)

  • null safety: 코틀린의 널 안정성을 향상시킬 수 있다.
    null은 프로그램의 기용성을 저하시키는 치명적인 오류이다.
    코틀린은 null로부터 안전한 설계를 위해 자료형에 null 여부를 설정할 수 있다.
  • ?, !!, ?:, ?. 키워드를 지원한다.
  • 강제로 null이 아니라고 단정짓는 !!은 최대한 사용을 지양해야 한다.
// nullable한 자료형 타입의 변수 선언.
var address:String? = null
address = "서울"

println("주소는: ${address} 입니다")

// !! 키워드는 null이 아니라고 단정지을 때 사용한다.
var inputData = readLine()!!
var data = inputData.toInt()
println("Null아닌 값: ${data}")

// ?. 키워드는 null인지 확인하고 null이 아닐 때만 데이터 참조.
lateinit var name:String
var address:String? = null

println("주소의 길이는: ${address?.length} 입니다")	// address가null인 경우는 null을 return.

// 엘비스 연산자(?:)를 이용해 데이터가 null인 경우의 로직을 따로 처리할 수 있다.
println("주소의 길이는: ${address?.length ?: "초기화하세요"} 입니다")

 

 

6강(배열)

  • 배열: 변수에 순서를 매겨 사용할 수 있다.
    일반적으로 변수를 선언하면, 코틀린은 메모리에 띄엄띄엄 변수를 랜덤으로 생성한다.
    변수의 위치정보가 연속적이지 않기 때문에 순서가 없다.
  • 배열을 이용해 데이터에 순서를 매겨 연속적으로 활용할 수 있다.
    배열을 사용하기 위해 arrayOf() 함수를 사용한다.
// 기본적인 배열 사용법
var arr = arrayOf(1,2,3,4,5)

// 배열요소를 모두 출력합니다
println(Arrays.toString(arr))

// 배열의 0번째 데이터 출력
println(arr[0])
  • 배열을 사용해서 반복적으로 변수를 생성 및 접근하는 행위를 방지할 수 있다.

 

7강(컬렉션)

  • 컬렉션(collection): 개발에 유용한 자료 구조들을 제공한다.
    코틀린에서는 대표적으로 List, Set, Map을 제공한다.
// list의 기본 사용법.
// 읽기전용 리스트 생성
var scores1 = listOf(값1, 값2, 값3)

// 수정가능 리스트입니다
// 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 있다.
var scores2 = mutableListOf(값1, 값2, 값3)
scores2.set(인덱스, 값)

// 수정가능 리스트입니다
// array로 데이터들을 저장하는 ArrayList도 mutableListOf와 동일하게 사용 가능.
var scores3 = ArrayList<자료형>(값1, 값2, 값3)
scores3.set(인덱스, 값)

// map 기본 사용법
// map은 key, value로 데이터가 구성된다.
// 읽기전용 map 생성.
// 변수명[키]로 데이터에 접근.
var scoreInfo1 = mapOf("kor" to 94, "math" to 90, "eng" to 92)
println(scoreInfo1["kor"])

// 수정가능 map 생성.
var scoreInfo2 = mutableMapOf("kor" to 94, "math" to 90)
scoreInfo2["eng"] = 92
println(scoreInfo2["eng"])

// set 기본 사용법
// set은 중복 없이 데이터를 관리할 수 있다.
// 읽기전용 set 생성.
var birdSet = setOf("닭", "참새", "비둘기")

// 수정가능 Set입니다.
var mutableBirdSet = mutableSetOf("닭", "참새", "비둘기")
mutableBirdSet.add("꿩")
mutableBirdSet.remove("꿩")

// set은 합집합, 차집합, 교집합 함수를 지원해 간편하게 필요한 요소를 추출할 수 있다.
var birdSet = setOf("닭", "참새", "비둘기", "물오리")
var flyBirdSet = setOf("참새", "비둘기", "까치")

// 합집합 사용
var unionBirdSet = birdSet.union(flyBirdSet)

// 교집합 사용
var intersectBirdSet = birdSet.intersect(flyBirdSet)

// 차집합 사용
var subtractBirdSet = birdSet.subtract(flyBirdSet)

 

 

8강(람다)

  • 람다(lambda): 메서드를 간결하게 정의할 때 사용한다.
    자바 8과 동일하게 람다식을 지원한다.
    하나의 메서드를 간결하게 표현할 수 있다.
// 람다의 기본 사용법
함수 이름() {매개변수1, 매개변수2... -> 
    // 코드
}

// 중괄호를 생략하는 방식.
fun add(num1:Int, num2:Int, num3:Int) = (num1+num2+num3)/3

// 변수에 괄호를 전개해 데이터를 집어넣는 방식
var add = {num1: Int, num2: Int, num3: Int -> (num1+num2+num3) / 3}

 

 

3. 개인 공부

A.  개인 과제(콘솔 계산기)

계산 함수 구성하기.

추상 클래스를 이용해 더하기(+), 빼기(-), 곱하기(*), 나누기(/) 연산을 할 수 있도록 추상 함수를 만들어 주었다.

abstract class AbstractOperation {
    // 덧셈
    abstract fun add(num1: Int, num2: Int): Int
    // 뺄셈
    abstract fun minus(num1: Int, num2: Int): Int
    // 곱셈
    abstract fun multiply(num1: Int, num2: Int): Int
    // 나눗셈
    abstract fun divide(num1: Int, num2: Int): Int
}

 

Calculator 클래스를 생성해 생성한 추상 클래스를 상속받아 추상 메서드들을 오버라이딩해 기능을 정의해 주었다.

class Calculator: AbstractOperation() {
    override fun add(num1: Int, num2: Int): Int {
        return num1 + num2
    }

    override fun minus(num1: Int, num2: Int): Int {
        return num1 - num2
    }

    override fun multiply(num1: Int, num2: Int): Int {
        return num1 * num2
    }

    override fun divide(num1: Int, num2: Int): Int {
        return num1 / num2
    }
}

 

마지막으로 콘솔에 계산기를 println()과 무한 반복문을 이용해 구현한다. 

import src.calculate.Calculator
import java.util.Scanner

fun main() = with(Scanner(System.`in`)) {
    println("=========================계산기 v1=========================")
    println("계산을 시작할까요?")
    println("계산을 시작하려면 0을 입력하고, 종료하려면 -1을 입력하세요.")

    var isCalculatorFinish = false
    while (true) {
        // 계산기 종료 여부가 true면 종료
        if (isCalculatorFinish) break

        val mainInput = next()
        // mainInput을 올바르게 작성시키기 위한 try catch
        if (mainInput == "0") {
            // 계산기 시작.
            println("계산을 시작합니다.")
            while (true) {
                println("사칙연산식을 입력하세요(종료하려면 ` 입력).")

                val formula = next()
                if (formula == "`") {
                    println("계산기를 종료합니다.")
                    println("=========================계산기 v1=========================")
                    isCalculatorFinish = true
                    break
                } else {
                    // 정규 표현식을 이용한 수식 분할(여러 개의 문자를 기준으로 분할 가능함).
                    val splitFormula = formula.split("+", "-", "*", "/")
                    val splitFormulaSymbol = formula.filter { it in setOf('+', '-', '*', '/') }.map { it.toString() }
                    // splitFormula에서 숫자와 괄호"()"만 포함되도록 하기(정규 표현식 사용).
                    if (!splitFormula.all { it.matches(Regex("\\d*")) })
                        println("연산식이 문자를 포함하고 있습니다. 다시 시도해 주세요.")
                    else {
                        calculation(
                            splitFormula = splitFormula,
                            splitFormulaSymbol = splitFormulaSymbol
                        )
                    }
                }
            }
        } else if (mainInput == "-1") {
            // 계산기 종료
            println("계산기를 종료합니다.")
            println("=========================계산기 v1=========================")
            break
        } else {
            println("올바른 숫자를 입력하세요.")
            println("계산을 시작하려면 0을 입력하고, 종료하려면 -1을 입력하세요.")
            continue
        }
    }
}

 

  • 계산을 시작할 때와 계산을 종료할 때 같은 상황들이 있을 때, 입력을 받아서 계산을 시작하거나 종료하도록 했다.
  • 먼저 String으로 입력받고(int로 정의하고 문자열을 입력받았을 때의 예외 상황을 방지) 입력받은 String이 "0"이면 계산을 시작하고 "-1"이면 종료하도록 했다.
    다른 문자 또는 숫자가 입력되면 else로 이동해 예외 print문을 출력하고 다시 입력받을 수 있도록 만들어 주었다.
  • mainInput이 "0"일 때, 계산식을 입력받을 수 있게 해 주었다.
    연산식을 입력받기 시작했을 때에도 언제든지 계산기를 종료할 수 있도록 "`"기호를 입력했을 때, 계산용 반복(내부 반복)을 빠져나가고 true로 변경한 isCalculatorFinish를 이용해 외부 반복도 종료해 프로그램을 완전히 끝낼 수 있도록 만들었다.
  • "`"기호가 아닌 경우에는 계산식을 입력받는데, 수식에 이상한 문자들(+, -, *, / 를 제외한 모든 문자)이 입력되면 안 되기 때문에 +, -, /, * 기호를 기준으로 수식을 list로 나누고(split 함수, splitFormula 변수) 정규 표현식을 이용해 splitFormula의 데이터가 숫자 이외의 문자를 포함하고 있으면 예외 print를 출력한다.

계산식에 수식 부호를 제외한 문자가 들어 있는 경우의 출력.

  • 아닌 경우에는 계산을 진행할 수 있도록 만드는 중이다.
    현재 사칙연산식이 수식 부호로 끝나는 경우의 처리와 사칙연산 우선순위(*, /가 먼저)를 구현하지 못했다.

 


 

오늘 공부 내용 정리 및 회고

  • 코틀린 4주 차 강의를 들었다. 원래는 3주 차까지 듣는 것이 이번주의 진도인데 미리 예습할 겸 4주 차 강의까지 들었다.
  • 콘솔을 이용한 서비스를 만드는 것은 오랜만에 하는 거라 설레기도(?)하고 어색하기도 했다.
    어떻게 해서 잘 구현은 하고 있는 것 같다. 
728x90