코틀린-안드로이드

63일차)알고리즘 문제(이중우선순위큐), 심화 주차 강의(Retrofit, 디버깅)

songyooho 2024. 8. 1. 21:24

>알고리즘 문제

1. 문제

문제 설명

이중 우선순위 큐는 다음 연산을 할 수 있는 자료구조를 말합니다.

명령어수신 탑(높이)
I 숫자 큐에 주어진 숫자를 삽입합니다.
D 1 큐에서 최댓값을 삭제합니다.
D -1 큐에서 최솟값을 삭제합니다.

이중 우선순위 큐가 할 연산 operations가 매개변수로 주어질 때, 모든 연산을 처리한 후 큐가 비어있으면 [0,0] 비어있지 않으면 [최댓값, 최솟값]을 return 하도록 solution 함수를 구현해주세요.

제한사항
  • operations는 길이가 1 이상 1,000,000 이하인 문자열 배열입니다.
  • operations의 원소는 큐가 수행할 연산을 나타냅니다.
    • 원소는 “명령어 데이터” 형식으로 주어집니다.- 최댓값/최솟값을 삭제하는 연산에서 최댓값/최솟값이 둘 이상인 경우, 하나만 삭제합니다.
  • 빈 큐에 데이터를 삭제하라는 연산이 주어질 경우, 해당 연산은 무시합니다.

2. 솔루션

import java.util.*
class Solution {
    fun solution(operations: Array<String>): IntArray {
        var answer = intArrayOf()
        var size=0
        
        val upq=PriorityQueue<Node>(compareBy{-it.n})
        val dpq=PriorityQueue<Node>(compareBy{it.n})
        
        for(i in operations){
            if(i == "D 1"){
                if(size>0){
                    getNonNull(upq)
                    size--
                }
            }else if(i =="D -1"){
                if(size>0){
                    getNonNull(dpq)
                    size--
                }
            }else{
                i.split(" ")[1].toInt().let{
                    val node=Node(it)
                    upq.offer(node)
                    dpq.offer(node)
                    size++
                }
                
            }
        }
        
        if(size==1){
            val tmp = getNonNull(upq)!!.n
            return intArrayOf(tmp,tmp)
        }else if(size>1){
            answer+=getNonNull(upq)!!.n
            answer+=getNonNull(dpq)!!.n 
        }
        else{
            return intArrayOf(0,0)
        }
        
        
        return answer
    }
    
    fun getNonNull(pq:PriorityQueue<Node>):Node?{
        var tmp:Node? =null
        while(true){
            tmp = pq.poll()
            if(tmp.check){
                tmp.check=false
                break
            }
        }
        return tmp
    }
}

data class Node(val n:Int, var check:Boolean=true)

-우선순위큐를 정순과 역순으로 하나씩 생성하고 사이즈를 재는 변수 하나 선언

-참조를 이용하기위해 데이터 클래스를 하나 생성

-노드추가는 둘 다 , 노드 제거는 한쪽에서만 진행하여 제거된 노드에 대해서는 check를 false로 바꾸어 반대 큐에서도 알 수 있게 함. 

-위 조건을 지키며 operation을 진행하여 최종 큐를 얻어 결과를 도출한다.

 

 

>심화 주차 강의

1. Retrofit

1)서버와 클라이언트

<1>기본 개념

-서버:데이터나 리소스를 제공하는 시스템. 요청이 오면 그에 맞는 응답 전송

-클라이언트: 사용자를 대표해 서버에 정보나 서비스를 요청하는 시스템

=>웹 브라우저, 앱등이 있음

- 3-Tier 아키텍쳐: 클라이언트 - 서버 - 데이터베이스 순으로 연결되어있는 형태

=>클라이언트는 리소스를 사용, 서버는 리소스를 전달, 데이터베이스는 리소스를 저장

 

<2>서버 - 클라이언트 통신: 사용 프로토콜, 용도 , 성능 요구사항등에 따라 선택

-HTTP/HTTPS: 웹 기반 앱에서 주로 사용. REST API나 SOAP같은 웹 서비스 통식 방식에서 기반

-WebSockets: 실시간 양방향 통신(채팅, 실시간 게임등)

-Socket(TCP/UDP): TCP나 UDP프로토콜을 사용해 데이터 전송. 지속적 연결 유지하며 양방향 통신

-FTP(File Transfer Protocol): 파일 전송에 특화. 클라이언트와 서버같의 파일전송에 사용

-SOAP(Simple Object Access Protocol): XML기반 메시징 프로토콜

 

<3>프로토콜(Protocol): 통신 규약. 요청과 응답에 대한 약속

 

<4>웹 어플리케이션 프로토콜: HTTP

-클라이언트와 서버가 HTTP프로토콜을 이용해 통신을 하고 이때 주고받는 메시지는 HTTP메시지라 부른다.

 

<5>API(Application Programming Interface)

-두 구성 요소(앱)간 서로 통신할 수 있게 해주는 메커니즘. 

-정의 및 프로토콜의 집합으로 구성됨.

=>통신에 사용되는 언어나 메시지 형식을 의미. 즉  일종의 매개체.

=>즉, API는 두 어플리케이션간(주로 서버와 클라이언트)의 서비스 계약(=서로 통신하는 방법에 대한 정의)

 

 

2)REST API(Representational State Transfer)

@ Representational:표현의란 뜻으로 여기서는 리소스가 클라이언트에게 전달된때의 표형 형식을 의미.

=>표현 형식으로는 JSON, XML등이 있다.

@REST의 의미: 클라이언트가 서버 데이터에 액세스하는데 사용할 수 있는 함수집합인 GET, PUT, DELETE등을 의미

<1>구조

-Client 와 Server가 있고 요청은 HTTP,URL로 이루어지며 응답은 JSON, XML등으로 이우어 진다.

-웹의 장점을 활용할 수 있는 아키텍처 스타일

-WWW가 REST아키텍처 기반으로 구성되어 있음

 

<2>핵심 원칙

[1]자원 식별: 각 리소스는 고유 URI로 식별됨

[2]메시지의 상태를 통한 표현: 리소스는 JSON, XML등의 형식으로 표현

!![3]상태가 없는(stateless)통신: 각 요청은 서버에서 필요한 모든 정보를 포함해야 함. 즉, 서버는 클라이언트의 데이터를 저장하지 않음 => 무상태

=>이를 통해 서버는 각 요청을 개별적으로 처리 가능

[4]클라이언트 - 서버 구조: 사용자 인터페이스와 데이터 저장소가 분리되어 독립성이 높아짐

[5]캐시 처리 가능: 응답 데이터 캐싱이 가능한지 여부를 명시해 성능 향상

[6]계층화된 시스템: 서버와 클라이언트 사이에 다양한 계층 (보안, 로드 밸런싱등)이 존재하 수 있음

 

<3>REST와 HTTP

-REST는 주로 HTTP프로토콜 위에서 구현\

-주요 HTTP메서드

[1]GET: 리소스 조회

[2]POST: 리소스 생성

[3]PUT: 리소스 수정

[4]:DELETE: 리소스 삭제

 

<4>RESTful API: 위의 REST원칙을 지킨 API

 

 

3)JSON: 데이터 저장, 전송시 사용되는 경량의 DATA교환 형식

<1>데이터 구조: 각 데이터는 NAME -VALUE 형식으로 이루어짐

-예시: { "이름1": "데이터1" , "이름2": "데이터2", ...}

=>NAME은 String, VALUE엔 다양한 primitive type의 값과 null이 올 수 있음

 

<2>JSON배열

-대괄호로 둘러쌓아 표현

-여러 JSON데이터를 포함할 수있음

-예시

"dog":[
    {"name1":"value1"},
    {"name2":"value2"}
]

-위와 같이 VALUE에 배열이 들어갈 수 있다.

 

 

4)GSON

<1>GSON이란: JSON을 직렬화(JSON을 프로그래밍 언어 객체로 변환), 역직렬화(객체를 JSON으로 변환)하기를 간단하게 해주는 라이브러리

<2>사용이유

-코드 간결성: JSON과 객체간 변환작업이 간단함

-성능 효율성: 직렬화 및 역직렬화 작업을 빠르게 수행하여 대규모나 복잡한 데이터 처리에도 효과적

 

<3>사용법

[1]직렬화(객체 -> JSON)

val gson = Gson()
val jsonString = gson.toJson(someObject)

 

[2]역직렬화(JSON -> 객체)

-역직렬화 시킬때 사용할 클래스 생성

=>예시

data class Person(
    @SerializedName("person_name")
    val name: String
)

(1)필드명과 JSON키 이름이 다를경우 @SerializedName으로 매핑

(2)필요없는 필드에 대해서는 클래스에 포함시키지 않는 방식으로 역직렬화에서 제외시킬 수 있다.

 

 

4)Retrofit

<1>Retrofit: REST API의 HTTP요청을 자바 인터페이스로 변환하는 것을 주 목적으로 함

<2>장점

[1]코드 간결성

[2]안정성과 확정성

-OkHttp 라이브러리로 안정적인 통신

-인터셉터로 요청, 응답 프로세스를 확장 및 수정 가능

[3]다양한 플러그인과 컨버터 지원

-다양한 데이터 형식(JSON, XML등)에 대한 데이터 변환 컨버터 제공

-Rxjava, Coroutines와 같은 비동기 프로그래밍 라이브러리와 연동 가능

 

<3>사용법

[1]시작

-Gradle에 Retrofit라이브러리 추가

@libs.version.toml사용법

-[verision]에 사용한 버전 변수 생성

[versions]
.
.
retrofit = "2.11.0"
okhttp3 = "4.12.0"
.
.

-[libraries]에 원래 implementation에 들어갈 부분이 "imple~:1.3.5" 꼴이면 앞을 module에 넣고 버전은 위에서 선언한 것을 가져와 뒤에다 넣으면 된다.

okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3"}
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp3"}
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }

-마지막으로 [bundles]로 묶여놓으면 된다.

[bundles]
retrofit = [
    "retrofit",
    "retrofit-converter-gson",
    "okhttp",
    "okhttp-logging"
]

-이제 bundles에 있는 retrofit을 gradle에서 implementation해주면 끝

implementation(libs.bundles.retrofit)

 

 

[2]역직렬화에 사용될 데이터클래스 정의: 이때 변수들은 nullable로 만들

data class ImageResponse(
    @SerializedName("meta") val meta: MetaResponse?,
    @SerializedName("documents") val documents: List<ImageDocumentResponse>?,
)

data class MetaResponse(
    @SerializedName("total_count") val totalCount: Int?,
    @SerializedName("pageable_count") val pageableCount: Int?,
    @SerializedName("is_end") val isEnd: Boolean?,
)

data class ImageDocumentResponse(
    @SerializedName("collection") val collection: String?,
    @SerializedName("thumbnail_url") val thumbnailUrl: String?,
    @SerializedName("image_url") val imageUrl: String?,
    @SerializedName("width") val width: Int?,
    @SerializedName("height") val height: Int?,
    @SerializedName("display_sitename") val displaySitename: String?,
    @SerializedName("doc_url") val docUrl: String?,
    @SerializedName("datetime") val datetime: Date?,
    val isFavorite : Boolean = false
)

 

[3]API 인터페이스 정의: 인터페이스로 작성된 것을 Retrofit에 전달하면 알아서 실제 함수를 구현

=>실제 네트워킹을 진행해주는 Service객체로 변

interface SearchUserImageList {
    @GET("/v2/search/image")
    suspend fun getSearchImage(
        @Query("query") query: String,
        @Query("sort") sort: String = "accuracy",
        @Query("page") page: Int = 1,
        @Query("size") size: Int = 10,
    ): ImageResponse
}

-API문서에서 base를 제외한 GET인 경우에 대한 URL을 GET에 넣어줌(다른 HTTP프로토콜 메소드도 마찬가지)

=>사용하고자 하는 메소드 위에다가

=>통신관련 메소드이므로 코루틴스코프에서 진행돼야 하니 suspend

=>@Query는 API문서에 나와있는것을 쓰면 됨

=>결과값은 역직렬화된 값으로 나와야 하므로 해당 클래스를 결과 타입으로 설정

 

[4]RetrofitClient 오브젝트 클래스 생성: Retrofit은 여러번 생성될 필요가 없으므로 싱글톤으로 구현

-베이스 URL 변수 선언

private const val BASE_URL = "https://dapi.kakao.com"

 

-네트워크 요청을 위한 httpClient구성

private val okHttpClient by lazy {
    val interceptor = HttpLoggingInterceptor()
    interceptor.level = HttpLoggingInterceptor.Level.BODY
    OkHttpClient.Builder()
        .addInterceptor(AuthorizationInterceptor())
        .addNetworkInterceptor(interceptor)
        .build()
}

-Interceptor: 앱 - OkHttp core -네트워크 구조로 존재할때 코어와 앱사이에서 요청이나 응답, 혹은 네트워크와 코어사이에서의 요청이나 응답을 가로채서 특정 작업을 하는것.

=>앱-OkHttp core 사이는 Interceptor

=>OkHttp core - 네트워크 사이는 NetworkInterceptor

 

@요청과 응답에 대한 interceptor는 okHttpClient가 생성될때 작성한다

-인터셉터 작성법 예시

class FixedContentInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        // 요청 가로채기
        val originalRequest = chain.request()

        // 요청 본문을 고정된 값으로 설정
        val fixedRequestBody = RequestBody.create(MediaType.parse("text/plain"), "Fixed request body")
        val modifiedRequest = originalRequest.newBuilder()
            .method(originalRequest.method, fixedRequestBody)
            .build()

        // 수정된 요청 전송
        val response = chain.proceed(modifiedRequest)

        // 응답 본문을 고정된 값으로 설정
        val fixedResponseBody = ResponseBody.create(response.body?.contentType(), "Fixed response body")

        // 수정된 응답 반환
        return response.newBuilder()
            .body(fixedResponseBody)
            .build()
    }
}

-연결, 읽기, 쓰기에대해 timeout설정을 넣을 수 있다

return OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .addNetworkInterceptor(interceptor)
            .build()

 

@인터셉터 사용시 유의점: 최대한 인터셉터 수를 줄이는 방식으로 만들어야 함

=>인터셉터가 늘어날수록 커넥션까지 시간이 오래걸임

 

@인터셉터 처리방식: 그냥 addInterceptor끼리 처리되고 addNetworkInterceptor끼리 따로 처리된다. 

=>처리 순서는 요청은 추가된 순서대로, 응답은 역순으로

 

@참고 자료

https://bbluecoder.medium.com/a-deep-dive-into-okhttp-interceptors-8fd8d79869ec

 

A deep dive into OkHttp interceptors

OkHttp stands as a widely recognized library in both Android and Java development. Many HTTP libraries within the Android ecosystem, such…

bbluecoder.medium.com

 

 

-HttpLoggingInterceptor(): 네트워크 요청과 응답에 대한 로그 기록을 위한 interceptor

=>로그 수준 설정: interceptor.level

{1}Level.NONE: 로그를 출력하지 않음

{2}Level.BASIC: 요청의 메소드, URL, 응답 상태코드 및 콘텐츠 길이 같은 기본적인 정보만 로그로 기록

{3}Level.HEADERS: 요청 및 응답의 헤더 정보를 로그로 기록

{4}Level.BODY: 요청의 응답 본문(body)도 포함하여 로그로 기록 

=>데이터가 큰 경우 성능에 영향을 미칠 수 있음

 

 

[5]API 인터페이스 생성

val searchUserImageList : SearchUserImageList by lazy {
    retrofit.create(SearchUserImageList::class.java)
}

 

 

[6]응답 처리

(1)동기식: 현재 스레드에서 실행되며, 응답이 올 때 까지 다음코드 실행이 중단

val response: Response<User> = apiService.getUser(id).execute()

(2)비동기식 요청:

{1}그냥 받아오기

interface SearchUserImageList {
    @GET("/v2/search/image")
    suspend fun getSearchImage(
        @Query("query") query: String,
        @Query("sort") sort: String = "accuracy",
        @Query("page") page: Int = 1,
        @Query("size") size: Int = 10,
    ): ImageResponse
}

-API인터페이스에서 suspend로 함수 작

private fun getUserImageList(query: String) {
    viewLifecycleOwner.lifecycleScope.launch {
        val userList = RetrofitClient.searchUserImageList.getSearchImage(query)
        searchListAdapter.submitList(userList.documents)
    }
}

-코루틴을 이용해서 응답을 받아옴

 

{2}요청 후 콜백으로 처리:이때 Call과 Callback은 Retrofit2로 import

interface SearchUserImageList {
    @GET("/v2/search/image")
    fun getSearchImage(
        @Query("query") query: String,
        @Query("sort") sort: String = "accuracy",
        @Query("page") page: Int = 1,
        @Query("size") size: Int = 10,
    ): Call<ImageResponse>
}

-그냥 함수로 GET을 구현하고 리턴 타입은 Call<>로 감싼다.

 

=>이후 성공시 onResponse에서 작업, 실패시 onFilure에서 작업

RetrofitClient.searchUserImageList.getSearchImage(query).enqueue(object: Callback<ImageResponse>{
    override fun onResponse(p0: Call<ImageResponse>, p1: Response<ImageResponse>) {
        TODO("Not yet implemented")
    }

    override fun onFailure(p0: Call<ImageResponse>, p1: Throwable) {
        TODO("Not yet implemented")
    }
})

 

 

 

 

2. 디버깅 - 디버깅 모드 사용하기

1)브레이크 포인트 걸기: 라인 옆에 클릭해 동그라미 포인트를 만들면됨

=>함수에 걸면 느려지니 함수말고 필요하면 내부 라인에만

 

2)디버그툴 사용법

Resume Program: 재생모양

다음 중단점이 있을 때까지 앱을 실행함

Show Execution Point: 가로줄 세 개

클릭하면 현재 진행 중인 코드로 이동

Step Over: 회색 밑줄 위에 꺾인 화살표

break point에서 코드의 다음 줄로 이동

Step Into: 회색 밑줄 위에 아래 화살표

메서드 호출 내에서 첫 번째 줄로 이동

Step Over 와의 차이점은,

  • step over는 줄단위의 코드들에 대해 디버깅
  • step into는 메서드 단위로 이동하도록

보통은 메소드 안으로 들어가 step over로 디버깅하다가, 메서드를 디버깅하기 위해 step into 하면 된다.

 

Evaluate Expression: 계산기모양

break point로 captured 된 변수를 이용해 expression 또는 함수 등을 실행할 수 있다.

 

3)디버그모드로 변수값 변경: 우클릭, set Value로 값 변경 가능