TIL

32일차 TIL

h_luz 2024. 4. 11. 23:03

 

Swift 알고리즘 및 언어공부

 

 

우선 처음에 최적으로 m 만큼씩 상자에 나눠 담을 코드를 적었다.

만약에 총값이 상자에 딱 맞게 나누어 떨어지면 이런 방식으로 하고, 안 나누어 떨어지면 나머지 수만큼 가장 작은 수들을 버린 후에 이런 방식을 적용해 주기로 생각했는데, 자꾸 에러가 떴음.. 그래서 chat gpt에 왜 오류가 나는지 물어봤는데

 

-

주어진 코드에서 발생하는 주요 이슈는 arr이 빈 배열로 초기화되어 있어서, 배열 요소에 접근할 때 인덱스 오류가 발생한다는 점입니다. 코드에서 arr을 빈 배열로 선언한 후에는 이 배열에 요소를 추가하거나 요소에 접근하기 전에 요소를 추가해야 합니다. 현재 코드에서는 빈 배열 arr에 요소에 직접 접근하고 값을 할당하려고 하기 때문에 인덱스 오류가 발생합니다. 해결하기 위해서는 arr을 적절히 초기화해야 합니다. 예를 들어, arr을 2차원 배열로 초기화하고 각 요소를 빈 배열로 설정할 수 있습니다. 또한 요소를 추가할 때에는 append() 메서드를 사용하여 요소를 추가해야 합니다.

import Foundation

let k = 4
let m = 3
let score: [Int] = [4, 1, 2, 2, 4, 4, 4, 4, 1, 2, 4, 2]

var scoreValue: [Int] = score.sorted()
let rowCount = score.count / m
var arr: [[Int]] = []

for _ in 0..<rowCount {
    var rowArray: [Int] = []
    for _ in 0..<m {
        if let firstValue = scoreValue.first {
            rowArray.append(firstValue)
            scoreValue.removeFirst()
        }
    }
    arr.append(rowArray)
}

print(arr)

오오.. 이렇게하니까 배열이 내가 원하는 대로 잘 나왔음.. 역시 2차원 배열은 너무 헷갈린다...ㅠ 

rowArray 변수를 추가해서 배열 안에 배열 설정해 주고, 2차원 배열이니까 배열에 배열을 append 하는 방식을 사용 

 

import Foundation

func solution(_ k:Int, _ m:Int, _ score:[Int]) -> Int {
    var scoreValue: [Int] = score.sorted()
    let rowCount = score.count / m
    var arr: [[Int]] = []
    
    for _ in 0..<rowCount {
        if scoreValue.count % m != 0 {
            for n in 0 ... scoreValue.count % m {
                scoreValue.removeFirst()
            }
        }
        var rowArray: [Int] = []
        for _ in 0..<m {
            if let firstValue = scoreValue.first {
                rowArray.append(firstValue)
                scoreValue.removeFirst()
            }
        }
        arr.append(rowArray)
    }
    var result:Int = 0
    for a in 0..<arr.count {
        result += arr[a].min()! * m
    }
    return result
}

위에 if문으로 나머지 상자가 있는 경우도 추가해 주고, 마지막 return 값까지 구했다

그냥 코드 실행하면 통과하긴 하는데,, 제출 후 체점하면 실패함 ㅋㅋㅋ 역시 좋은 코드 같아 보이진 않음..

 

시간초과 나서 변수를 사용해서 반복되는 계산을 줄이고, 조건문을 최소화해 줌

import Foundation

func solution(_ k: Int, _ m: Int, _ score: [Int]) -> Int {
    var scoreValue: [Int] = score.sorted()
    let rowCount = score.count / m
    var arr: [[Int]] = []

    let excessCount = scoreValue.count % m
    if excessCount != 0 {
        scoreValue.removeFirst(excessCount)
    }
    var result: Int = 0
    var startIndex = 0
    for _ in 0..<rowCount {
        var minValue = Int.max //Int의 최댓값( chatGPT한테 배움 )
        for j in 0..<m {
            let value = scoreValue[startIndex + j]
            if value < minValue {
                minValue = value
            }
        }
        result += minValue * m
        startIndex += m
    }
    return result
}

겨우 풀었다..

 

//다른사람의 풀이
import Foundation

func solution(_ k:Int, _ m:Int, _ score:[Int]) -> Int {
    let s = score.sorted(by: >)
    return stride(from: m-1, to: score.count, by: m)
        .reduce(0) { $0 + s[$1] * m }
}

ㅎㅎㅎ 살짝 자괴감 든다!ㅋㅋㅋㅋ 역시 함수를 잘 써주면 간단하게 구할 수 있구나....

난 reduce 함수가 어렵다.....ㅜ

이 코드는 stride로 상자에 들어가는 사과 중 가장 낮은 점수를 갖고 있는 사과에 인덱스 값들을 먼저 구하고,

큰 수부터 내림차순 정렬한 s에 [$1]을 해줘서(현재 인덱스를 나타냄) 각 상자에 가장 낮은 점수를 가진 사과의 점수를 골라냄

그 값에 m을 곱하고,

$0으로 이 곱한 값들을 누적해서 연산하도록 한 거 같음

2차원 배열 해줄 필요도 없이.. 이렇게 간단하게 할 수 있군 ^^!

 

그리고 새롭게 알게 된 stride 함수 정리

 

stride( from: to: by: )

: 시작 값부터 끝 값까지(포함하지 않음)의 시퀀스를 지정된 양만큼 단계별로 반환 (참고사이트)

    from: 시퀀스에 사용할 시작 값입니다. 시퀀스에 값이 포함되어 있으면 첫 번째 값

    to / through: 시퀀스를 제한하는 종료 값 ( to를 쓰면 마지막 값 포함 x, through는 포함 o )

    by: 각 반복마다 단계별로 수행할 양 

 

기존에 사용하던 for문과 비교해서 예시를 들어보자면,

//기존에 사용하던 for문
var sum:[Int] = []
for i in 0...5 {
    sum.append(i)
}
print(sum) // 0 1 2 3 4 5 (개행 생략)


//stride 함수를 사용한 for문
for b in stride(from: 0, through: 5, by: 1) {
    sum.append(b)
}
print(sum) // 0 1 2 3 4 5 (개행 생략)

 


 

강의 정리 (스파르타 코딩 클럽)

 

내부저장소

: 앱 종료와 함께 사라지지 않고, 앱이 기억하고 있어야할 비휘발성 데이터를 저장
  ( * 디바이스에 저장되기 때문에 많은 양에 데이터를 저장하기엔 적합하지 않음 )

 

1. UserDefaults

: 간단한 Key-Value ( 설정값, 사용자 기본설정과 같은 작은 정보 저장 )

// 데이터 추가 및 업데이트 ( 저장할 value값과 매칭 될 key값(String타입)을 넣어준다 )
UserDefaults.standard.set(value, forKey: key)

// 데이터 조회
// value메소드로 가져오면 Any?(옵셔널 Any)타입이여서 as를 사용한 타입 변환 후 사용
// 해당 타입과 동일한 메소드 네이밍을 사용하면 타입 변환없이 사용할 수 있다
UserDefaults.standard.value(forKey: key)

// 데이터 삭제
UserDefaults.standard.removeObject(forKey: key)

 

2. KeyChain

: 보안 정보를 안전하게 저장하고, 관리 ( 비밀번호, 토큰, 인증서와 같은 민감한 데이터, 보안 관련 정보 저장 )

  ㄴ 암호화 되어있어서 외부에서 쉽게 접근할 수 없으며, 데이터의 안정성 보장

 

//Keychain에 저장할 데이터를 추가하는 쿼리 생성
let addQuery: [String:Any] = [
    kSecClass as String: kSecClassGenericPassword, //Keychain내에서 다루고자 하는 데이터의 유형 지정
    kSecAttrService as String: "service" //데이터를 저장하는 서비스나 앱을 식별하는데 사용
    kSecAttrAccount as String: "key" // Keychain에서 데이터를 찾거나 식별하는데 사용
    kSecValueData as String: "data" //실제로 Keychain에 저장될 데이터
]
//데이터 Keychain에 추가
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)

kSecClass : 값이 항목의 클래스인 사전 키

kSecAttrService : 항목의 서비스를 나타내는 문자열 값을 갖는 키

    ㄴ 해당 값은 CFString과 관련된 서비스를 나타내는 문자열 유형. 클래스의 항목에는 kSecClassGenericPassword 속성이 있다.

kSecAttrAccount : 항목의 계정 이름을 나타내는 문자열 값을 갖는 키

    ㄴ CFString 유형, 계정 이름 포함. 클래스의 항목이고, kSecClassGenericPassword, kSecClassInternetPassword 속성을 가짐

kSecValueData : 값이 항목의 데이터인 키

    ㄴ CFData 유형. 키 및 비밀번호 항목의 경우 데이터는 비밀(암호화)이므로 사용자가 액세스 하려면 비밀번호를 입력해야 할 수도 있다.

CFDictionary : Key-Value 쌍의 연결을 관리

    ㄴ Core Foundation 프레임워크에서 제공하는 딕셔너리 데이터 구조

    ㄴ Swift의 Dictionary를 as CFDictionary로 처리하여 바로 사용 가능

 

//Keychain 데이터 조회
let getQuery: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword, //Keychain 내에서 다루고자 하는 데이터의 유형을 지정
    kSecAttrService as String: "service" //데이터를 저장하는 서비스나 앱을 식별하는데 사용
    kSecAttrAccount as String: "key" //Keychain에서 데이터를 찾거나 식별하는데 사용
    kSecReturnData as String: kCFBooleanTrue, //Keychain에서 데이터를 가져올 때, 반환할 데이터 형태 지정
    kSecMatchLimit as String: kSecMatchLimitOne //일치하는 항목 수를 제한
]
//데이터를 Keychain에서 조회
var item: CFTypeRef?
let getStatus = SecItemCopyMatching(getQuery as CFDictionary, &item)

 

kSecReturnData : 항목 데이터를 반환할지 여부를 나타내는 부울 값

   ㄴ CFBoolean유형. kCFBooleanTrue 값은 함수가 항목의 데이터를 CFData객체로 반환해야 함을 나타냅니다.

kSecMatchLimit : 일치 제한 값을 나타내는 키 (이 값을 제공하면 반환하거나 다른 작업을 수행할 최대 결과 수를 지정 )

    ㄴ CFNumber유형. 단일 항목의 경우, kSecMatchLimitOne 지정. 일치하는 모든 항목을 지정하려면 kSecMatchLimitAll 지정.

        (기본 동작은 함수에 따라 달라짐)

CFTypeRef? : 객체에 대한 유형이 지정되지 않은 "일반" 참조

    ㄴ Core Foundation에 정의된 기본 유형, 여러 다형성 함수에서 유형 및 반환 값으로 사용됩니다. 이는 다른 실제 Core Foundation            개체에 대한 자리 표시자 역할을 하는 일반 개체 참조

 

//Keychain에서 데이터 업데이트
let updateQuery:[String:Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrService as String: "service",
    kSecAttrAccount as String: "key",
]
// 업데이트할 데이터로 속성 설정
 let attributes:[String:Any] = [
     kSecValueData as String: "newData"
 ]
 // 데이터를 Keychain에서 업데이트
 let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributes as CFDictionary)

SecItemUpdate(_:_:) : 검색어와 일치하는 항목을 수정

 

//Keychain에서 데이터 삭제
let deleteQuery:[String:Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrService as String: "service",
    kSecAttrAccount as String: "key",
]
//데이터를 Keychain에서 삭제
let deleteStatus = SecItemDelete(deleteQuery as CFDictionary)

SecItemDelete(_:) : 검색어와 일치하는 항목을 삭제

 

3. 파일 시스템

: 앱 내부 또는 외부 파일 시스템에 데이터를 저장 (주로 텍스트 파일, 이미지, 동영상 등의 데이터를 저장할 때 사용)

let fileName = "example.txt"
let content = "이것은 파일에 쓰여진 내용"

//FileManager로 저장할 위치 지정
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
    let fileURL = dir.appendingPathComponent(fileName)
    do {
        try content.write(to:fileURL, atomically: false, encoding: .utf8)
        print("파일이 성공적으로 생성되었습니다.")
    } catch {
        print("파일 생성 중 에러가 발생했습니다: \(error.localizedDescription)")
    }
}

 

(중요!)

4. Core Data

: 데이터를 관리하고, 영구적으로 저장하는 데 사용되는 프레임 워크( 별도 외부 라이브러리를 사용하지 않는 경우 Core Data를 주로 사용)

    ㄴ 데이터 모델을 정의하고, 이를 기반으로 데이터를 읽고 쓸 수 있음

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistenContainer(name:/*생성한 Data Model명*/)
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

func saveContext () {
    let context = persistenContainer.viewContext
    if context.hasChanges {
       do {
          try context.save()
       catch {
          let nserror = error as NSError
          fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
       }
    }
}

 


 

내일 Core Data 이어서 더 자세하게 공부해야겠다..

물론 이해가 잘 안 되지만.. 그래도 한 번 보고 적어서 익숙해지면 나중에 이해할 때 도움이 되지 않을까... ㅠ