앱 제작중에 있어서 서버로 보내기엔 너무 간단하지만, 앱이 종료되도 저장이 가능한 저장소를 이용했었다.

주로 UserDefaults를 이용했었다.

UserDefaults:

https://developer.apple.com/documentation/foundation/userdefaults

 

UserDefaults | Apple Developer Documentation

An interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app.

developer.apple.com

이는 Key-Value 값이 한쌍으로 저장되는 기본 데이터베이스인데, 해당하는 키를 부르면 쉽게 데이터에 접근할 수 있어서 자주 사용했었지만, 다양한 구조체를 저장하고, 부르기에는 좀 무거워진 느낌이 들었다. 또한 Key를 통해 접근하다보니 여러가지 데이터를 받는데 헷갈렸다.

(이런식으로 저장하고자 하는 구조체마다 싱글톤을 제작하여 데이터 저장 및 출력을 했다.)

class StudyListUserDefaults {
    var data :[Study] = {
        var arr = [Study]()
        if let data = UserDefaults.standard.value(forKey: "studyList") as? Data {
            arr = try! PropertyListDecoder().decode(Array<Study>.self, from: data)
        }
        return arr
    }() {
        didSet {
            UserDefaults.standard.setValue(try? PropertyListEncoder().encode(data), forKey: "studyList")
        }
    }
    static let shared = StudyListUserDefaults()
    private init(){}
    
    func add(new: Study) {
        data.append(new)
    }
    
    func remove(index: Int) {
        data.remove(at: index)
    }
    
    func set(new: [Study]) {
        data = new
    }
    func removeAll() {
        data.removeAll()
    }
}

 

앱에 있던 모든 데이터를 싱글톤인 유저디폴트로 저장하다보니까 한계가 느껴지는듯 해서 다른 로컬저장소를 찾아보았다.

이번 게시물은 유저디폴트에서 SQLite로 바꾸면서 느꼈던 점과 SQLite의 코드들을 살펴보려고 한다.

 

일단 코드를 보기전에 SQLite로 만들면서 좋았던점은

1. 구조체를 하나로 통일할 수 있었다.

-> 이전에 사용했던 유저디폴트를 마구마구 쓰지않게되었다.

2. 위 장점과 이어지는건데, 코드의 가독성이 좋아졌다.

3. 각 뷰마다 뷰모델에서 데이터의 접근이 매우 좋아졌다.

-> SQLite 싱글톤 하나로 어디든 데이터를 접근할 수 있었고, 뷰모델의 데이터로 UI그리기 용이했다.

4. SQL 기반의 오픈소스다.

-> import 하여 쉽게 접근할 수 있는점과, 익숙한 쿼리가 보인다. 익숙한면에서 점수 먹고들어간다

 

내가 느낀 단점도 역시 있다.

1. 아무래도 데이터를 직관적으로 보기 어렵다.

-> print를 이용해서 데이터를 확인해보지만, 단번에 파악하기는 좀 어려운듯하다.

2. 코드 구현 난이도가 있는편이다.

-> 아무래도 유저디폴트로 간편하게 저장했다보니까 비교적 어려웠다. 구현만 하면 편하다.

3. 데이터 가공이 좀 힘들다?

-> 내 코드실력도 한 몫했지만 데이터 저장방식과 입출력 파라미터 구현하는데 애먹었고, 복잡한 데이터베이스는 이와 맞지않는걸 느꼈다.

 

그밖에도 더있었지만.. 다시 로컬저장소를 이용한다면 또 이용해도 될 정도의 메리트는 있는것 같다.

 

SQLite

https://github.com/stephencelis/SQLite.swift

 

GitHub - stephencelis/SQLite.swift: A type-safe, Swift-language layer over SQLite3.

A type-safe, Swift-language layer over SQLite3. Contribute to stephencelis/SQLite.swift development by creating an account on GitHub.

github.com

위 링크는 SQLite고 나는 SQLite3를 이용했다.

 

차이점은 아래에서 확인할 수 있다.

https://linuxhint.com/what-are-sqlite-and-sqlite3/

 

What are SQLite and SQLite3? Are they the Same?

A database is like a big collection of information that is kept in a computer system. It is handled by the Database Management System which helps keep the information organized and safe. SQL (Structured Query Language) is a standard programming language th

linuxhint.com

 

코드로 구현하기

코드로 작성하기 앞서 SQLite를 추가해준다.

import SQLite3

 

 

클래스 이름과 데이터베이스 이름은 아무렇게나 만들어도 상관이 없다.

class DB {
    // 싱글톤
    static let shared = DB()
    
    // db를 가리키는 포인터
    var db: OpaquePointer? = nil
    // 데이터베이스 이름 형식: name.sqlite 로 만들것
    let databaseName = "database.sqlite"
    
    init() {
        self.db = createDB()
    }
    
    deinit {
        sqlite3_close(db)
    }
}

 

createDB(): 데이터베이스 생성

func createDB() -> OpaquePointer? {
	//파일 경로
    let filePath = try! FileManager.default.url(for: .documentDirectory,
                                                in: .userDomainMask,
                                                appropriateFor: nil,
                                                create: false).appendingPathExtension(databaseName).path
    
    var db: OpaquePointer? = nil
    // 해당 경로에 DB가 성공적으로 만들어지면 DB포인터 반환
    if sqlite3_open(filePath, &db) != SQLITE_OK {
        print("There is error in creating DB")
        return nil
    } else {
        print("Database is been created with path \(databaseName)")
        return db
    }
}

 

 

createTable(): 테이블 생성

func createTable(tableName: String, stringColumn: [String]) {
    """
    CREATE TABLE IF NOT EXISTS tablename(id INTEGER PRIMARY KEY AUTOINCREMENT, name: TEXT, done: TEXT NOT NULL, date: TEXT);
    이는 tablename이란 테이블이 존재하지 않으면 생성한다는 뜻입니다.
    id는 고유키가 되고, 데이터가 추가될때마다 자동으로 증가하는값을 가집니다.
    여기서 TEXT는 문자열을 받는것이고, NOT NULL은 NULL은 저장하지않는단얘기입니다. Swift에선 옵셔널이 아닌형식이 되겠네요
    저는 유연하게 테이블을 만들고자 테이블 이름과 저장할 데이터이름(column에 위치한)을 입력받습니다.
    물론 데이터의 형식도 위와같이 입력받아 더 유연하게 만들 수 있지만 복잡해지므로 문자열만 받겠습니다.
    """
    // 입력받은 데이터이름을 형식에 맞춰서 구성
    var column: String = {
        var str = "id INTEGER PRIMARY KEY AUTOINCREMENT"
        for col in stringColumn {
            str += ", \(col) TEXT"
        }
        return str
    }()
    // 쿼리 작성
    let query = "CREATE TABLE IF NOT EXISTS" + " \(tableName)" + "(\(column));"
    var createTable: OpaquePointer? = nil
    
    // 작성한 쿼리를 실행
    if sqlite3_prepare_v2(self.db, query, -1, &createTable, nil) == SQLITE_OK {
        if sqlite3_step(createTable) == SQLITE_DONE {
            print("Table creation success \(String(describing: self.db))")
        } else {
            print("Table creation fail")
        }
    } else {
        print("Prepation fail")
    }
    sqlite3_finalize(createTable)

 

 

insertData(): 데이터 삽입

func insertData(tableName: String, columns: [String], insertData: [String]) {
    """
    데이터도 역시 유연하게 받기 위해 테이블이름, 데이터이름, 넣을 데이터를 입력 받습니다.
    이것도 역시 쿼리를 구성하고 실행하면 됩니다. 쿼리는 아래와 같이 구성합니다.
    insert into tablename (id, name, done, date) values (?, ?, ?, ?);
    tablename이란 테이블에 데이터를 집어넣는다. values 뒤에 ?는 넣을 데이터수에 맞춰 구성합니다. id포함
    """
    // 입력받은 데이터이름을 형식에 맞춰 구성
    let column: String = {
        var column = "id"
        for col in columns {
            column += ", \(col)"
        }
        return column
    }()
    // 입력받은 데이터에따라 인자 구성
    var value: String = {
        var value = "?"
        for val in 0..<insertData.count {
            value += ", ?"
        }
        return value
    }()
    
    // 쿼리작성
    let insertQuery = "insert into \(tableName) (\(column)) values (\(value));"
    var statement: OpaquePointer? = nil
    // 작성한 쿼리로 실행
    if sqlite3_prepare_v2(self.db, insertQuery, -1, &statement, nil) == SQLITE_OK {
        // 데이터 삽입
        for i in 0..<insertData.count {
            // 여기서 두번째 인자가 위 values에서 몇번째 ?에넣는가를 의미합니다.
            // 세번째 인자는 넣을 데이터를 형식에 맞춰 변환하여 넣습니다.
            sqlite3_bind_text(statement, Int32(i)+2, NSString(string: insertData[i]).utf8String, -1, nil)
        }
        if sqlite3_step(statement) == SQLITE_DONE {
            print("insert success")
        } else {
            print("step fail")
        }
    } else {
        print("bind fail")
    }

 

데이터를 넣을때 데이터 형식에 맞게 넣어야합니다.

SQLite에서 지원하는 데이터형식: https://www.sqlite.org/datatype3.html

 

Datatypes In SQLite

1. Datatypes In SQLite Most SQL database engines (every SQL database engine other than SQLite, as far as we know) uses static, rigid typing. With static typing, the datatype of a value is determined by its container - the particular column in which the val

www.sqlite.org

 

 

readData(): 데이터 읽기

 

데이터를 불러오기에 앞서 알맞은 형식을 받기위해 구조체 하나를 선언합니다.

struct StudyModel: Equatable, Codable {
    var id: Int?
    var name: String?
    var done: String?
    var date: String?
    // 아래는 없어도됨
    static func ==(lhs: StudyModel, rhs: StudyModel) -> Bool {
        return lhs.name == rhs.name
    }
}

 

데이터를 읽습니다.

func readData(tableName: String, column: [String]) -> [StudyModel] {
    """
    데이터를 읽는것도 역시나 쿼리작성후 해당 쿼리를 실행하는것 입니다.
    데이터도 역시 유연하게 읽기위해 tablename과 받을 데이터이름이 담긴 배열을 입력받습니다.
    쿼리구성: select * from tablename;
    """
    let query: String = "select * from \(tableName);"
    var statement: OpaquePointer? = nil
    
    // 앞서 선언했던 데이터받을 구조체의 배열
    var result: [StudyModel] = []
    // 구성한 쿼리로 실행
    if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) != SQLITE_OK {
        let error = String(cString: sqlite3_errmsg(db)!)
        print("error while prepare: \(error)")
        return []
    }
    // Column에 값이 존재하지않을때 까지 모두 읽어옵니다.
    // 저는 입력받은 데이터를 순회하면서 리턴형식에 맞게 만들고 추가하는 방법을 사용했습니다.
    while sqlite3_step(statement) == SQLITE_ROW {
        // id 고유키는 따로 읽어옵니다.
        let id = sqlite3_column_int(statement, 0)
        
        // 출력형식을 만들어주고, 이미 읽어온 id만 초기화
        var d = StudyModel(id: Int(id), name: nil, done: nil, date: nil)
        // 받아온데이터를 data 딕셔너리에 먼저 넣고, 저장변수이름과 해당하는 값을 매칭시켜서 d에 저장합니다.
        var data = Dictionary<String, String>()
        for  i in 0..<column.count {
            data[column[i]] = String(cString: sqlite3_column_text(statement, Int32(i+1)))
            let load = String(cString: sqlite3_column_text(statement, Int32(i+1)))
            switch column[i] {
            case column[0]: d.name = load
            case column[1]: d.done = load
            case column[2]: d.date = load
            default: continue
            }
        }
        // 저장된 d를 출력배열 result에 넣습니다.
        result.append(d)
    }
    sqlite3_finalize(statement)
    return result
}

 

데이터를 읽고 쓰는데는 SQLite에서 지원하는 데이터형식을 고려하여 변환해야합니다.

 

updateData(): 데이터 수정

// 데이터 수정오류메세지
private func onSQLErrorPrintErrorMessage(_ db: OpaquePointer?) {
    let errorMessage = String(cString: sqlite3_errmsg(db))
    print("Error preparing Update \(errorMessage)")
    return
}

func updateData(tableName: String, id: Int, done: String, date: String) {
    """
    데이터 수정도 역시나 쿼리를 구성한 후 실행하는 과정을 거칩니다.
    쿼리구성: UPDATE tablename SET name='변경값',date='변경값' WHERE id==2
    TEXT형식은 반드시 변경값에 ''를 감싸줘야합니다.
    WHERE뒤에는 조건부가 나옵니다.
    변경하고자 하는 값을 입력받아 유연하게 데이터를 변경합니다.
    """
    var statement: OpaquePointer? = nil
    let query = "UPDATE \(tableName) SET done='\(done)',date='\(date)' WHERE id==\(id)"
    
    if sqlite3_prepare(db, query, -1, &statement, nil) != SQLITE_OK {
        onSQLErrorPrintErrorMessage(db)
        return
    }
    
    if sqlite3_step(statement) != SQLITE_DONE {
        onSQLErrorPrintErrorMessage(db)
        return
    }
    
    print("Update has been successfully done")
}

 

 

deleteData(): 데이터 삭제

func deleteData(tableName: String, id: Int) {
    """
    값 삭제도 쿼리를 구성후 실행합니다.
    데이터 변경과 비슷하게 조건부가 필요합니다.
    쿼리구성: delete from tablename where id == 2
    tablename이란 테이블에서 id(고유키)가 2인 데이터를 삭제한다.
    """
    let query = "delete from \(tableName) where id == \(id)"
    print(query)
    var statement: OpaquePointer? = nil
    
    if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
        if sqlite3_step(statement) == SQLITE_DONE {
            print("delete success")
        } else {
            print("delete fail")
        }
    } else {
        print("delete prepare fail")
    }
    sqlite3_finalize(statement)
}

 

 

이렇게 여러 메서드로 SQLite를 구현했습니다.

입력 파라미터 없이 메서드를 구현한다면 좀 딱딱한? 데이터베이스가 됩니다.

SQLite가 복잡한 데이터베이스구조와는 맞지않아서 간단하게 저장할 용도로 보통 사용하지만,

데이터를 조금씩 늘리고 테이블을 여러개 두고, 데이터형식도 좀 늘어난다면 이에 대응하기 위해 입력파라미터를 구성해놓는게 좋습니다.

 

아래 참고사이트와 전체코드를 두고 마무리합니다.

 

 

Reference:

https://42kchoi.tistory.com/387

 

[iOS][Swift]SQLite CRUD 처리하기 !

SQLite3 CRUD 처리 테스트 환경: xcode 12.5, swift 5 SQLite3 로 local db를 생성하고 TABLE을 CRUD 해보자 회사에서 프로그래밍을 하다가 토큰을 로컬 디비에 저장해야 하는 상황이 생겼습니다. 그래서 로컬 디

42kchoi.tistory.com

https://behan.tistory.com/8

 

Swift) SQLite 사용법

SQLite를 사용하면 별다른 설치 없이 내부적으로 사용이 가능하다 import SQLite3 class DBHelper { static let shared = DBHelper() var db: OpaquePointer? var path = "mySqlite.sqlite" init() { self.db = createDB() } func createDB() -> Opaq

behan.tistory.com

https://www.sqlite.org/docs.html

 

SQLite Documentation

 

www.sqlite.org

 

전체코드:

더보기
import SQLite3

struct StudyModel: Equatable, Codable {
    var id: Int?
    var name: String?
    var done: String?
    var date: String?
    
    static func ==(lhs: StudyModel, rhs: StudyModel) -> Bool {
        return lhs.name == rhs.name
    }
}

class DB {
    // 싱글톤
    static let shared = DB()
    
    // db를 가리키는 포인터
    var db: OpaquePointer? = nil
    // 데이터베이스 이름 형식: name.sqlite 로 만들것
    let databaseName = "database.sqlite"
    
    init() {
        self.db = createDB()
    }
    
    deinit {
        sqlite3_close(db)
    }
    func createDB() -> OpaquePointer? {
        let filePath = try! FileManager.default.url(for: .documentDirectory,
                                                    in: .userDomainMask,
                                                    appropriateFor: nil,
                                                    create: false).appendingPathExtension(databaseName).path
        
        var db: OpaquePointer? = nil
        
        if sqlite3_open(filePath, &db) != SQLITE_OK {
            print("There is error in creating DB")
            return nil
        } else {
            print("Database is been created with path \(databaseName)")
            return db
        }
    }
    
    func createTable(tableName: String, stringColumn: [String]) {
        """
        CREATE TABLE IF NOT EXISTS tablename(id INTEGER PRIMARY KEY AUTOINCREMENT, name: TEXT, done: TEXT NOT NULL, date: TEXT);
        이는 tablename이란 테이블이 존재하지 않으면 생성한다는 뜻입니다.
        id는 고유키가 되고, 데이터가 추가될때마다 자동으로 증가하는값을 가집니다.
        여기서 TEXT는 문자열을 받는것이고, NOT NULL은 NULL은 저장하지않는단얘기입니다. Swift에선 옵셔널이 아닌형식이 되겠네요
        저는 유연하게 테이블을 만들고자 테이블 이름과 저장할 데이터이름(column에 위치한)을 입력받습니다.
        물론 데이터의 형식도 위와같이 입력받아 더 유연하게 만들 수 있지만 복잡해지므로 문자열만 받겠습니다.
        """
        // 입력받은 데이터이름을 형식에 맞춰서 구성
        var column: String = {
            var str = "id INTEGER PRIMARY KEY AUTOINCREMENT"
            for col in stringColumn {
                str += ", \(col) TEXT"
            }
            return str
        }()
        // 쿼리 작성
        let query = "CREATE TABLE IF NOT EXISTS" + " \(tableName)" + "(\(column));"
        var createTable: OpaquePointer? = nil
        
        // 작성한 쿼리를 실행
        if sqlite3_prepare_v2(self.db, query, -1, &createTable, nil) == SQLITE_OK {
            if sqlite3_step(createTable) == SQLITE_DONE {
                print("Table creation success \(String(describing: self.db))")
            } else {
                print("Table creation fail")
            }
        } else {
            print("Prepation fail")
        }
        sqlite3_finalize(createTable)
    }
    
    func insertData(tableName: String, columns: [String], insertData: [String]) {
        """
        데이터도 역시 유연하게 받기 위해 테이블이름, 데이터이름, 넣을 데이터를 입력 받습니다.
        이것도 역시 쿼리를 구성하고 실행하면 됩니다. 쿼리는 아래와 같이 구성합니다.
        insert into tablename (id, name, done, date) values (?, ?, ?, ?);
        tablename이란 테이블에 데이터를 집어넣는다. values 뒤에 ?는 넣을 데이터수에 맞춰 구성합니다. id포함
        """
        // 입력받은 데이터이름을 형식에 맞춰 구성
        let column: String = {
            var column = "id"
            for col in columns {
                column += ", \(col)"
            }
            return column
        }()
        // 입력받은 데이터에따라 인자 구성
        var value: String = {
            var value = "?"
            for val in 0..<insertData.count {
                value += ", ?"
            }
            return value
        }()
        
        // 쿼리작성
        let insertQuery = "insert into \(tableName) (\(column)) values (\(value));"
        var statement: OpaquePointer? = nil
        // 작성한 쿼리로 실행
        if sqlite3_prepare_v2(self.db, insertQuery, -1, &statement, nil) == SQLITE_OK {
            // 데이터 삽입
            for i in 0..<insertData.count {
                // 여기서 두번째 인자가 위 values에서 몇번째 ?에넣는가를 의미합니다.
                // 세번째 인자는 넣을 데이터를 형식에 맞춰 변환하여 넣습니다.
                sqlite3_bind_text(statement, Int32(i)+2, NSString(string: insertData[i]).utf8String, -1, nil)
            }
            if sqlite3_step(statement) == SQLITE_DONE {
                print("insert success")
            } else {
                print("step fail")
            }
        } else {
            print("bind fail")
        }
    }
    
    func readData(tableName: String, column: [String]) -> [StudyModel] {
        """
        데이터를 읽는것도 역시나 쿼리작성후 해당 쿼리를 실행하는것 입니다.
        데이터도 역시 유연하게 읽기위해 tablename과 받을 데이터이름이 담긴 배열을 입력받습니다.
        쿼리구성: select * from tablename;
        """
        let query: String = "select * from \(tableName);"
        var statement: OpaquePointer? = nil
        
        // 앞서 선언했던 데이터받을 구조체의 배열
        var result: [StudyModel] = []
        // 구성한 쿼리로 실행
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) != SQLITE_OK {
            let error = String(cString: sqlite3_errmsg(db)!)
            print("error while prepare: \(error)")
            return []
        }
        // Column에 값이 존재하지않을때 까지 모두 읽어옵니다.
        // 저는 입력받은 데이터를 순회하면서 리턴형식에 맞게 만들고 추가하는 방법을 사용했습니다.
        while sqlite3_step(statement) == SQLITE_ROW {
            // id 고유키는 따로 읽어옵니다.
            let id = sqlite3_column_int(statement, 0)
            
            // 출력형식을 만들어주고, 이미 읽어온 id만 초기화
            var d = StudyModel(id: Int(id), name: nil, done: nil, date: nil)
            // 받아온데이터를 data 딕셔너리에 먼저 넣고, 저장변수이름과 해당하는 값을 매칭시켜서 d에 저장합니다.
            var data = Dictionary<String, String>()
            for  i in 0..<column.count {
                data[column[i]] = String(cString: sqlite3_column_text(statement, Int32(i+1)))
                let load = String(cString: sqlite3_column_text(statement, Int32(i+1)))
                switch column[i] {
                case column[0]: d.name = load
                case column[1]: d.done = load
                case column[2]: d.date = load
                default: continue
                }
            }
            // 저장된 d를 출력배열 result에 넣습니다.
            result.append(d)
        }
        sqlite3_finalize(statement)
        return result
    }
    
    // 데이터 수정오류메세지
    private func onSQLErrorPrintErrorMessage(_ db: OpaquePointer?) {
        let errorMessage = String(cString: sqlite3_errmsg(db))
        print("Error preparing Update \(errorMessage)")
        return
    }

    func updateData(tableName: String, id: Int, done: String, date: String) {
        """
        데이터 수정도 역시나 쿼리를 구성한 후 실행하는 과정을 거칩니다.
        쿼리구성: UPDATE tablename SET name='변경값',date='변경값' WHERE id==2
        TEXT형식은 반드시 변경값에 ''를 감싸줘야합니다.
        WHERE뒤에는 조건부가 나옵니다.
        """
        var statement: OpaquePointer? = nil
        let query = "UPDATE \(tableName) SET done='\(done)',date='\(date)' WHERE id==\(id)"
        
        if sqlite3_prepare(db, query, -1, &statement, nil) != SQLITE_OK {
            onSQLErrorPrintErrorMessage(db)
            return
        }
        
        if sqlite3_step(statement) != SQLITE_DONE {
            onSQLErrorPrintErrorMessage(db)
            return
        }
        
        print("Update has been successfully done")
    }

    func deleteData(tableName: String, id: Int) {
        """
        값 삭제도 쿼리를 구성후 실행합니다.
        데이터 변경과 비슷하게 조건부가 필요합니다.
        쿼리구성: delete from tablename where id == 2
        tablename이란 테이블에서 id(고유키)가 2인 데이터를 삭제한다.
        """
        let query = "delete from \(tableName) where id == \(id)"
        print(query)
        var statement: OpaquePointer? = nil
        
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            if sqlite3_step(statement) == SQLITE_DONE {
                print("delete success")
            } else {
                print("delete fail")
            }
        } else {
            print("delete prepare fail")
        }
        sqlite3_finalize(statement)
    }
}

문제

로봇 청소기와 방의 상태가 주어졌을 때, 청소하는 영역의 개수를 구하는 프로그램을 작성하시오.

로봇 청소기가 있는 방은 N X M 크기의 직사각형으로 나타낼 수 있으며, 1×1 크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 벽 또는 빈 칸이다. 청소기는 바라보는 방향이 있으며, 이 방향은 동, 서, 남, 북 중 하나이다. 방의 각 칸은 좌표 (로 나타낼 수 있고, 가장 북쪽 줄의 가장 서쪽 칸의 좌표가 (0,0) 가장 남쪽 줄의 가장 동쪽 칸의 좌표가 (이다. 즉, 좌표 는 북쪽에서 번째에 있는 줄의 서쪽에서 번째 칸을 가리킨다. 처음에 빈 칸은 전부 청소되지 않은 상태이다.

로봇 청소기는 다음과 같이 작동한다.

  1. 현재 칸이 아직 청소되지 않은 경우, 현재 칸을 청소한다.
  2. 현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 없는 경우,
    1. 바라보는 방향을 유지한 채로 한 칸 후진할 수 있다면 한 칸 후진하고 1번으로 돌아간다.
    2. 바라보는 방향의 뒤쪽 칸이 벽이라 후진할 수 없다면 작동을 멈춘다.
  3. 현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 있는 경우,
    1. 반시계 방향으로 90도 회전한다.
    2. 바라보는 방향을 기준으로 앞쪽 칸이 청소되지 않은 빈 칸인 경우 한 칸 전진한다.
    3. 1번으로 돌아간다.

입력

첫째 줄에 방의 크기 이 입력된다. (3≤N,M≤50)  둘째 줄에 처음에 로봇 청소기가 있는 칸의 좌표 와 처음에 로봇 청소기가 바라보는 방향 d가 입력된다.d가 0인 경우 북쪽, 1인 경우 동쪽, 2인 경우 남쪽, 3인 경우 서쪽을 바라보고 있는 것이다.

셋째 줄부터 개의 줄에 각 장소의 상태를 나타내는 N X M개의 값이 한 줄에 개씩 입력된다. 번째 줄의 번째 값은 칸 의 상태를 나타내며, 이 값이 0인 경우가 청소되지 않은 빈 칸이고, 1인 경우 에 벽이 있는 것이다. 방의 가장 북쪽, 가장 남쪽, 가장 서쪽, 가장 동쪽 줄 중 하나 이상에 위치한 모든 칸에는 벽이 있다. 로봇 청소기가 있는 칸은 항상 빈 칸이다.

출력

로봇 청소기가 작동을 시작한 후 작동을 멈출 때까지 청소하는 칸의 개수를 출력한다.

내가 푼 풀이

- 문제는 어렵지않으나 해당 로봇의 작동을 구현하는데 좀 생각이 많아졌다.

- 작동할 수 없을때 까지 최대로 돌려서 청소한 구역의 수를 구하는문제다.

- 해당 로봇의 작동을 함수로 정하고, while을 무한으로 돌려 탈출조건(4구역이 청소되거나 벽인상태에서 후진할수 없는 상태)이 성립하면 나오도록 구현하였다.

 

import Foundation

// 입력받기
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], M = input[1]
let input2 = readLine()!.split(separator: " ").map{Int(String($0))!}
var robotLoc = (x: input2[0], y: input2[1])
var dir = input2[2]
var room = [[Int]]()
var count = 0
for i in 0..<N {
    room.append(readLine()!.split(separator: " ").map{Int(String($0))!})
}

// 탈출조건이 만족할때 까지 청소
while true {
    if !find(loc: &robotLoc, dir: &dir) {
        break
    }
}
// 청소된 방의 구역수를 세고 출력
for i in 0..<room.count {
    count += room[i].filter{ $0 == -1}.count
}
print(count)

// 구역 청소
func cleaning(loc: (x: Int, y: Int)) {
    if room[loc.x][loc.y] == 0 {
        room[loc.x][loc.y] = -1
    }
}

// 인접한 4개의 구역 확인
func find(loc: inout (x: Int, y: Int), dir: inout Int) -> Bool{
    // 4개 구역 임의의 수입력
    var upp = -2, rightp = -2, downp = -2, leftp = -2
    // 청소할수 없다 (벽이거나 이미 청소된곳)
    var cant = [1,-1]
    // 처음에 주어지는 위치는 청소되지않는곳으로 부여되므로 일단 청소
    cleaning(loc: loc)
    
    // 주어진 위치의 상 하 좌 우 위치를 구한다.
    // 인덱스 범위를 넘어도 벽(1) 로 설정
    if loc.x-1 >= 0 {
        upp = room[loc.x-1][loc.y]
    } else { upp = 1 }
    
    if loc.y+1 < M {
        rightp = room[loc.x][loc.y+1]
    } else { rightp = 1 }
    
    if loc.x+1 < N {
        downp = room[loc.x+1][loc.y]
    } else { downp = 1 }
    
    if loc.y-1 >= 0 {
        leftp = room[loc.x][loc.y-1]
    } else { leftp = 1 }
    
    // 4개 인접한 구역이 청소할 수 없는경우(이미청소됬거나, 벽인 경우)
    if cant.contains(upp) && cant.contains(downp) && cant.contains(leftp) && cant.contains(rightp) {
        // 방향에 따라 후진한다.
        // 후진 할 수 없는경우 로봇의 작동 멈춤
        if dir == 0 {
            if downp == 1 {
                return false
            } else {
                loc.x += 1
                return true
            }
        }
        if dir == 1 {
            if leftp == 1 {
                return false
            } else {
                loc.y -= 1
                return true
            }
        }
        if dir == 2 {
            if upp == 1 {
                return false
            } else {
                loc.x -= 1
                return true
            }
        }
        if dir == 3 {
            if rightp == 1 {
                return false
            } else {
                loc.y += 1
                return true
            }
        }
    } else {
        // 4군데중 청소할 곳이 있는경우
        // 방향에 따라 위치로 이동하고 청소
        dir -= 1
        if dir == -1 {
            dir = 3
        }
        if dir == 0 {
            if upp == 0 {
                loc.x -= 1
                cleaning(loc: loc)
            }
        } else if dir == 1 {
            if rightp == 0 {
                loc.y += 1
                cleaning(loc: loc)
            }
        } else if dir == 2 {
            if downp == 0 {
                loc.x += 1
                cleaning(loc: loc)
            }
        }else {
            if leftp == 0 {
                loc.y -= 1
                cleaning(loc: loc)
            }
        }
        return true
    }
    return true
}

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-5052 전화번호 목록 Swift  (0) 2024.04.14
BOJ-2470 두 용액 Swift  (0) 2024.04.14
BOJ-1011 Fly me to the Alpha Centauri Swift  (0) 2024.04.13
BOJ-10974 모든 순열 Swift  (0) 2024.04.13
BOJ-15683 감시 Swift  (1) 2024.04.12

문제

우현이는 어린 시절, 지구 외의 다른 행성에서도 인류들이 살아갈 수 있는 미래가 오리라 믿었다. 그리고 그가 지구라는 세상에 발을 내려 놓은 지 23년이 지난 지금, 세계 최연소 ASNA 우주 비행사가 되어 새로운 세계에 발을 내려 놓는 영광의 순간을 기다리고 있다.

그가 탑승하게 될 우주선은 Alpha Centauri라는 새로운 인류의 보금자리를 개척하기 위한 대규모 생활 유지 시스템을 탑재하고 있기 때문에, 그 크기와 질량이 엄청난 이유로 최신기술력을 총 동원하여 개발한 공간이동 장치를 탑재하였다. 하지만 이 공간이동 장치는 이동 거리를 급격하게 늘릴 경우 기계에 심각한 결함이 발생하는 단점이 있어서, 이전 작동시기에 k광년을 이동하였을 때는 k-1 , k 혹은 k+1 광년만을 다시 이동할 수 있다. 예를 들어, 이 장치를 처음 작동시킬 경우 -1 , 0 , 1 광년을 이론상 이동할 수 있으나 사실상 음수 혹은 0 거리만큼의 이동은 의미가 없으므로 1 광년을 이동할 수 있으며, 그 다음에는 0 , 1 , 2 광년을 이동할 수 있는 것이다. ( 여기서 다시 2광년을 이동한다면 다음 시기엔 1, 2, 3 광년을 이동할 수 있다. )

김우현은 공간이동 장치 작동시의 에너지 소모가 크다는 점을 잘 알고 있기 때문에 x지점에서 y지점을 향해 최소한의 작동 횟수로 이동하려 한다. 하지만 y지점에 도착해서도 공간 이동장치의 안전성을 위하여 y지점에 도착하기 바로 직전의 이동거리는 반드시 1광년으로 하려 한다.

김우현을 위해 x지점부터 정확히 y지점으로 이동하는데 필요한 공간 이동 장치 작동 횟수의 최솟값을 구하는 프로그램을 작성하라.

입력

입력의 첫 줄에는 테스트케이스의 개수 T가 주어진다. 각각의 테스트 케이스에 대해 현재 위치 x 와 목표 위치 y 가 정수로 주어지며, x는 항상 y보다 작은 값을 갖는다. (0 ≤ x < y < 231)

출력

각 테스트 케이스에 대해 x지점으로부터 y지점까지 정확히 도달하는데 필요한 최소한의 공간이동 장치 작동 횟수를 출력한다.

내가 푼 풀이

- 장치는 1씩 증가하는 등차수열의 모습으로 이동할 수 있고, 목적지 y직전 위치에서 y까지는 이동거리 1광년으로 움직여야한다.

- 작동횟수를 최소로 줄이려면 이동거리를 등차수열로 중간지점만큼 움직이고, 남은 지점은 감소하는 등차수열로 이동한다면 y직전의 지점과 y의 거리가 1이 되고 최소로 움직이게된다.

- 이것을 작동횟수의 기준으로 작동횟수당 최대로 움직일 수 있는 거리는 다음과 나타낼 수 있다.

작동횟수 최대 이동거리
1 1 = 1
2 1 1 = 2
3 1 2 1 = 4
4 1 2 2 1 = 6
5 1 2 3 2 1 = 9
6 1 2 3 3 2 1 = 12

예로 거리가 5가되는 지점을 이동할때 작동횟수의 최솟값은 4가 된다.

(4번 장치를 작동시켰을 때, 최대 이동가능거리가 6이 되기때문에)

 

위 최대 이동거리는 가운데를 중심으로 등차수열이 대칭되어있다.

홀수는 1부터 n까지의 등차수열과 n-1 부터 1까지의 등차수열이 나열되있고,

짝수는 1부터 n까지의 등차수열과 n부터 1까지의 등차수열이 나열되어있다.

등차수열의 합은 n(n+1) / 2 인 점을 이용해

홀수의 등차수열 합은 n^2 이 되고, 짝수의 등차수열 합은 n * (n + 1)이 된다.

 

작동횟수를 1부터 증가시켜가며 x와 y지점의 거리가 범위에 존재하는지 파악하고, 최솟값을 출력한다.

 

import Foundation

// 입력받기
let T = Int(readLine()!)!

// 테스트케이스
for i in 0..<T {
    let input = readLine()!.split(separator: " ").map{Int(String($0))!}
    // 거리
    var distance = input[1] - input[0]
    // 작동횟수
    var count = 1
    // 1부터 n까지의 등차수열
    var n = 1
    while true {
        if count % 2 == 0 {
            // 작동횟수가 짝수번이라면 등차수열은 1...N과 N...1 두개가 존재
            // 최대이동거리보다 주어진 거리가 작다면 이 횟수로 이동가능하다고 판단
            if n * (n + 1) >= distance {
                print(count)
                break
            }
        } else {
            // 작동횟수가 홀수번이라면 등차수열은 1...N과 N-1...1 두개가 존재
            // 최대이동거리보다 주어진 거리가 작다면 이 횟수로 이동가능하다고 판단
            if n * n >= distance {
                print(count)
                break
            }
        }
        // 작동횟수 증가
        count += 1
        // 작동횟수가 증가하고 홀수번이 된다면 n도 1 증가한다.
        // 1번: 1 , 2번: 1 1, 3번: 1 2 1 이되므로 n = 2가 된다.
        if count % 2 != 0 {
            n += 1
        }
    }
}

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-2470 두 용액 Swift  (0) 2024.04.14
BOJ-14503 로봇 청소기 Swift  (0) 2024.04.13
BOJ-10974 모든 순열 Swift  (0) 2024.04.13
BOJ-15683 감시 Swift  (1) 2024.04.12
BOJ-10819 차이를 최대로 Swift  (0) 2024.04.12

문제

N이 주어졌을 때, 1부터 N까지의 수로 이루어진 순열을 사전순으로 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 8)이 주어진다. 

출력

첫째 줄부터 N!개의 줄에 걸쳐서 모든 순열을 사전순으로 출력한다.

내가 푼 풀이

- dfs로 1부터 N까지 N개의 원소를 갖는 배열을 뽑는다.

- 1부터 원소를 뽑는다고 하면 dfs로 뽑힌 원소들은 자연스럽게 사전순으로 정렬되어있다.

 

import Foundation

let N = Int(readLine()!)!
var visited = Array(repeating: false, count: N+1)

func dfs(count: Int, arr: [Int]) {
    if count == N {
        print(arr.map{String($0)}.joined(separator: " "))
        return
    }
    
    for i in 1...N {
        if !visited[i] {
            visited[i] = true
            dfs(count: count+1, arr: arr + [i])
            visited[i] = false
        }
    }
}

dfs(count: 0, arr: [])

 

 

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-14503 로봇 청소기 Swift  (0) 2024.04.13
BOJ-1011 Fly me to the Alpha Centauri Swift  (0) 2024.04.13
BOJ-15683 감시 Swift  (1) 2024.04.12
BOJ-10819 차이를 최대로 Swift  (0) 2024.04.12
BOJ-12100 2048(Easy) Swift  (0) 2024.04.12

문제

스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감시할 수 있는 방법은 다음과 같다.

1번 CCTV는 한 쪽 방향만 감시할 수 있다. 2번과 3번은 두 방향을 감시할 수 있는데, 2번은 감시하는 방향이 서로 반대방향이어야 하고, 3번은 직각 방향이어야 한다. 4번은 세 방향, 5번은 네 방향을 감시할 수 있다.

CCTV는 감시할 수 있는 방향에 있는 칸 전체를 감시할 수 있다. 사무실에는 벽이 있는데, CCTV는 벽을 통과할 수 없다. CCTV가 감시할 수 없는 영역은 사각지대라고 한다.

CCTV는 회전시킬 수 있는데, 회전은 항상 90도 방향으로 해야 하며, 감시하려고 하는 방향이 가로 또는 세로 방향이어야 한다.

CCTV는 CCTV를 통과할 수 있다. 아래 예시를 보자.

0 0 2 0 3
0 6 0 0 0
0 0 6 6 0
0 0 0 0 0

위와 같은 경우에 2의 방향이 ↕ 3의 방향이 ←와 ↓인 경우 감시받는 영역은 다음과 같다.

# # 2 # 3
0 6 # 0 #
0 0 6 6 #
0 0 0 0 #

사무실의 크기와 상태, 그리고 CCTV의 정보가 주어졌을 때, CCTV의 방향을 적절히 정해서, 사각 지대의 최소 크기를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 사무실의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에는 사무실 각 칸의 정보가 주어진다. 0은 빈 칸, 6은 벽, 1~5는 CCTV를 나타내고, 문제에서 설명한 CCTV의 종류이다. 

CCTV의 최대 개수는 8개를 넘지 않는다.

출력

첫째 줄에 사각 지대의 최소 크기를 출력한다.

내가 푼 풀이

- 아주 정성스럽게 완전탐색을 하였다.

- 오른쪽 왼쪽 위 아래 탐색함수를 구현해보고 이를 재사용하였고, 각각 cctv의 탐색가능방향을 모두 탐색하고 최솟값을 구했다.

 

import Foundation

// 입력받기
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], M = input[1]
var office = [[Int]]()
var cctvs = [(serial: Int, x: Int, y: Int)]()
var result = Int.max

// cctv의 위치 저장
for i in 0..<N {
    office.append(readLine()!.split(separator: " ").map{Int(String($0))!})
    for j in 0..<office[i].count {
        if office[i][j] != 0 && office[i][j] != 6 {
            cctvs.append((serial: office[i][j], x: i, y: j))
        }
    }
}
// 모든 경우의 수 탐색
func dfs(count: Int, arr: [[Int]]) {
    if count == cctvs.count {
        result = min(result, sum(arr: arr))
        return
    }
    var cctv = cctvs[count]
    // 1번 cctv의 모든방향 탐색 (4가지)
    if cctv.serial == 1 {
        for i in 0..<4 {
            dfs(count: count+1, arr: serial1Range(direction: i, loc: (x: cctv.x, y: cctv.y), arr: arr))
        }
    }
    // 2번 cctv의 모든방향 탐색 (2가지)
    if cctv.serial == 2 {
        for i in 0..<2 {
            dfs(count: count+1, arr: serial2Range(direction: i, loc: (x: cctv.x, y: cctv.y), arr: arr))
        }
    }
    // 3번 cctv의 모든방향 탐색 (4가지)
    if cctv.serial == 3 {
        for i in 0..<4 {
            dfs(count: count+1, arr: serial3Range(direction: i, loc: (x: cctv.x, y: cctv.y), arr: arr))
        }
    }
    // 4번 cctv의 모든방향 탐색 (4가지)
    if cctv.serial == 4 {
        for i in 0..<4 {
            dfs(count: count+1, arr: serial4Range(direction: i, loc: (x: cctv.x, y: cctv.y), arr: arr))
        }
    }
    // 5번 cctv의 모든방향 탐색 (1가지)
    if cctv.serial == 5 {
        dfs(count: count+1, arr: serial5Range( loc: (x: cctv.x, y: cctv.y), arr: arr))
    }
    
}

// 탐색방향 모두 구현
// 오른쪽
func right(loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    for i in loc.y+1..<M {
        if arr[loc.x][i] == 6 { break }
        if arr[loc.x][i] == 0 { arr[loc.x][i] = -1 }
    }
    return arr
}
// 위쪽
func up(loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    for i in (0..<loc.x).reversed() {
        if arr[i][loc.y] == 6 { break }
        if arr[i][loc.y] == 0 { arr[i][loc.y] = -1}
    }
    return arr
}
// 왼쪽
func left(loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    for i in (0..<loc.y).reversed() {
        if arr[loc.x][i] == 6 { break }
        if arr[loc.x][i] == 0 { arr[loc.x][i] = -1 }
    }
    return arr
}
// 아래쪽
func down(loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    for i in loc.x+1..<N {
        if arr[i][loc.y] == 6 { break }
        if arr[i][loc.y] == 0 { arr[i][loc.y] = -1}
    }
    return arr
}

// 1번의 탐색
func serial1Range(direction: Int, loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    // 동
    if direction == 0 {
        return right(loc: loc, arr: arr)
    }
    
    // 서
    if direction == 1 {
        return left(loc: loc, arr: arr)
    }
    
    // 남
    if direction == 2 {
        return down(loc: loc, arr: arr)
    }
    
    // 북
    if direction == 3 {
        return up(loc: loc, arr: arr)
    }
    return arr
}

// 2번의 탐색
func serial2Range(direction: Int, loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    // 가로
    if direction == 0 {
        arr = right(loc: loc, arr: arr)
        arr = left(loc: loc, arr: arr)
    }
    
    // 세로
    if direction == 1 {
        arr = up(loc: loc, arr: arr)
        arr = down(loc: loc, arr: arr)
    }
    return arr
}

// 3번의 탐색
func serial3Range(direction: Int, loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    // 위 오
    if direction == 0 {
        arr = up(loc: loc, arr: arr)
        arr = right(loc: loc, arr: arr)
    }
    // 오 아래
    if direction == 1 {
        arr = right(loc: loc, arr: arr)
        arr = down(loc: loc, arr: arr)
    }
    // 아래 왼
    if direction == 2 {
        arr = down(loc: loc, arr: arr)
        arr = left(loc: loc, arr: arr)
    }
    // 왼 위
    if direction == 3 {
        arr = left(loc: loc, arr: arr)
        arr = up(loc: loc, arr: arr)
    }
    return arr
}

// 4번의 탐색
func serial4Range(direction: Int, loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    // 왼 위 오 (가로 + 위)
    if direction == 0 {
        arr = serial2Range(direction: 0, loc: loc, arr: arr)
        arr = up(loc: loc, arr: arr)
    }
    // 위 오 아래 (세로 + 오)
    if direction == 1 {
        arr = serial2Range(direction: 1, loc: loc, arr: arr)
        arr = right(loc: loc, arr: arr)
    }
    // 오 아래 왼 (가로 + 아래)
    if direction == 2 {
        arr = serial2Range(direction: 0, loc: loc, arr: arr)
        arr = down(loc: loc, arr: arr)
    }
    // 아래 왼 오 (세로 + 왼)
    if direction == 3 {
        arr = serial2Range(direction: 1, loc: loc, arr: arr)
        arr = left(loc: loc, arr: arr)
    }
    return arr
}

// 5번의 탐색
func serial5Range(loc: (x: Int, y: Int), arr: [[Int]]) -> [[Int]] {
    var arr = arr
    arr = serial2Range(direction: 0, loc: loc, arr: arr)
    arr = serial2Range(direction: 1, loc: loc, arr: arr)
    return arr
}

// 사각지대 계산
func sum(arr: [[Int]]) -> Int{
    var sum = 0
    for i in 0..<arr.count {
        sum += arr[i].filter{ $0 == 0}.count
    }
    return sum
}

dfs(count: 0, arr: office)
print(result)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-1011 Fly me to the Alpha Centauri Swift  (0) 2024.04.13
BOJ-10974 모든 순열 Swift  (0) 2024.04.13
BOJ-10819 차이를 최대로 Swift  (0) 2024.04.12
BOJ-12100 2048(Easy) Swift  (0) 2024.04.12
BOJ-2003 수들의 합 2 Swift  (0) 2024.04.11

문제

N개의 정수로 이루어진 배열 A가 주어진다. 이때, 배열에 들어있는 정수의 순서를 적절히 바꿔서 다음 식의 최댓값을 구하는 프로그램을 작성하시오.

|A[0] - A[1]| + |A[1] - A[2]| + ... + |A[N-2] - A[N-1]|

입력

첫째 줄에 N (3 ≤ N ≤ 8)이 주어진다. 둘째 줄에는 배열 A에 들어있는 정수가 주어진다. 배열에 들어있는 정수는 -100보다 크거나 같고, 100보다 작거나 같다.

출력

첫째 줄에 배열에 들어있는 수의 순서를 적절히 바꿔서 얻을 수 있는 식의 최댓값을 출력한다.

내가 푼 풀이

- 백트래킹을 이용해서 주어진 A배열 순서의 모든 경우의수를 구해서 최대값을 갱신한다.

 

import Foundation

//입력받기
let N = Int(readLine()!)!
var arr = readLine()!.split(separator: " ").map{Int(String($0))!}
var result = 0
var visited = Array(repeating: false, count: N)

// DFS 백트래킹
func dfs(count: Int, nums: [Int]) {
    if count == N {
        result = max(result, sum(nums: nums))
        return
    }
    
    for i in 0..<N {
        if !visited[i] {
            visited[i] = true
            dfs(count: count+1, nums: nums + [arr[i]])
            visited[i] = false
        }
    }
}

// 합산
func sum(nums: [Int]) -> Int{
    var sum = 0
    for i in 0..<nums.count-1 {
        sum += abs(nums[i] - nums[i+1])
    }
    return sum
}

dfs(count: 0, nums: [])
print(result)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-10974 모든 순열 Swift  (0) 2024.04.13
BOJ-15683 감시 Swift  (1) 2024.04.12
BOJ-12100 2048(Easy) Swift  (0) 2024.04.12
BOJ-2003 수들의 합 2 Swift  (0) 2024.04.11
BOJ-1107 리모컨 Swift  (0) 2024.04.11

문제

2048 게임은 4×4 크기의 보드에서 혼자 즐기는 재미있는 게임이다. 이 링크를 누르면 게임을 해볼 수 있다.

이 게임에서 한 번의 이동은 보드 위에 있는 전체 블록을 상하좌우 네 방향 중 하나로 이동시키는 것이다. 이때, 같은 값을 갖는 두 블록이 충돌하면 두 블록은 하나로 합쳐지게 된다. 한 번의 이동에서 이미 합쳐진 블록은 또 다른 블록과 다시 합쳐질 수 없다. (실제 게임에서는 이동을 한 번 할 때마다 블록이 추가되지만, 이 문제에서 블록이 추가되는 경우는 없다)

<그림 1>의 경우에서 위로 블록을 이동시키면 <그림 2>의 상태가 된다. 여기서, 왼쪽으로 블록을 이동시키면 <그림 3>의 상태가 된다.

<그림 4>의 상태에서 블록을 오른쪽으로 이동시키면 <그림 5>가 되고, 여기서 다시 위로 블록을 이동시키면 <그림 6>이 된다. 여기서 오른쪽으로 블록을 이동시켜 <그림 7>을 만들 수 있다.

 

이 문제에서 다루는 2048 게임은 보드의 크기가 N×N 이다. 보드의 크기와 보드판의 블록 상태가 주어졌을 때, 최대 5번 이동해서 만들 수 있는 가장 큰 블록의 값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.

출력

최대 5번 이동시켜서 얻을 수 있는 가장 큰 블록을 출력한다.

내가 푼 풀이

- 다른 알고리즘 풀이방법이 떠오르지않아 위 아래 왼쪽 오른쪽 방향으로 이동하는 동작을 구현해서 모든 경우의수를 탐색했다.

- 최대 5번 움직일 수 있으니, 동작을 구현해서 5번 굴려줬다.

 

import Foundation

// 입력받기
let N = Int(readLine()!)!
var arr = [[Int]]()
var result = Int.min
for i in 0..<N {
    arr.append(readLine()!.split(separator: " ").map{Int(String($0))!})
}

// 움직이기
func move(count: Int, arr: [[Int]]) {
    // 5번 움직였다면 최댓값 구하고 끝내기
    if count == 5 {
        for i in 0..<N {
            result = max(result, arr[i].max()!)
        }
        return
    }
    
    //UP 위로이동
    var board = arr
    for i in 0..<N {
        var insert = [Int]()
        for j in 0..<N {
            // 0이 아닌값들을 저장
            if board[j][i] != 0 {
                insert.append(board[j][i])
                board[j][i] = 0
            }
        }
        // 모든값이 0이라면 insert에 저장되지않았으므로 패스
        if !insert.isEmpty {
            var index = 0
            var arrIndex = 0
            // 같은값이면 더해서 가장 위로 올려주기
            while arrIndex < insert.count - 1 {
                if insert[arrIndex] == insert[arrIndex+1] {
                    board[index][i] = insert[arrIndex] * 2
                    arrIndex += 2
                } else {
                    board[index][i] = insert[arrIndex]
                    arrIndex += 1
                }
                index += 1
            }
            // 맨 마지막번째 수가 합쳐지지않았다면 마지막번에 저장
            if arrIndex < insert.count {
                board[index][i] = insert[arrIndex]
            }
        }
    }
    // 위로 이동 후 이동한 보드를 그대로 다른방향으로 이동
    move(count: count+1, arr: board)
    
    //DOWN 아래로 이동
    board = arr
    for i in 0..<N {
        var insert = [Int]()
        for j in (0..<N).reversed() {
            if board[j][i] != 0 {
                insert.append(board[j][i])
                board[j][i] = 0
            }
        }
        if !insert.isEmpty {
            var index = N-1
            var arrIndex = 0
            while arrIndex < insert.count - 1 {
                if insert[arrIndex] == insert[arrIndex+1] {
                    board[index][i] = insert[arrIndex] * 2
                    arrIndex += 2
                } else {
                    board[index][i] = insert[arrIndex]
                    arrIndex += 1
                }
                index -= 1
            }
            if arrIndex < insert.count {
                board[index][i] = insert[arrIndex]
            }
        }
    }
    // 아래로 이동후 다른방향으로 이동
    move(count: count+1, arr: board)
    
    //LEFT 왼쪽 이동
    board = arr
    for i in 0..<N {
        var insert = [Int]()
        for j in 0..<N {
            if board[i][j] != 0 {
                insert.append(board[i][j])
                board[i][j] = 0
            }
        }
        if !insert.isEmpty {
            var index = 0
            var arrIndex = 0
            while arrIndex < insert.count - 1 {
                if insert[arrIndex] == insert[arrIndex+1] {
                    board[i][index] = insert[arrIndex] * 2
                    arrIndex += 2
                } else {
                    board[i][index] = insert[arrIndex]
                    arrIndex += 1
                }
                index += 1
            }
            if arrIndex < insert.count {
                board[i][index] = insert[arrIndex]
            }
        }
    }
    // 왼쪽으로 이동후 다른방향으로 이동
    move(count: count+1, arr: board)
    
    //RIGHT 오른쪽 이동
    board = arr
    for i in 0..<N {
        var insert = [Int]()
        for j in (0..<N).reversed() {
            if board[i][j] != 0 {
                insert.append(board[i][j])
                board[i][j] = 0
            }
        }
        if !insert.isEmpty {
            var index = N-1
            var arrIndex = 0
            while arrIndex < insert.count - 1 {
                if insert[arrIndex] == insert[arrIndex+1] {
                    board[i][index] = insert[arrIndex] * 2
                    arrIndex += 2
                } else {
                    board[i][index] = insert[arrIndex]
                    arrIndex += 1
                }
                index -= 1
            }
            if arrIndex < insert.count {
                board[i][index] = insert[arrIndex]
            }
        }
    }
    // 오른쪽으로 이동 후 다른방향으로 이동
    move(count: count+1, arr: board)
}

move(count: 0, arr: arr)
print(result)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-15683 감시 Swift  (1) 2024.04.12
BOJ-10819 차이를 최대로 Swift  (0) 2024.04.12
BOJ-2003 수들의 합 2 Swift  (0) 2024.04.11
BOJ-1107 리모컨 Swift  (0) 2024.04.11
BOJ-1476 날짜 계산 Swift  (0) 2024.04.11

문제

N개의 수로 된 수열 A[1], A[2], …, A[N] 이 있다. 이 수열의 i번째 수부터 j번째 수까지의 합 A[i] + A[i+1] + … + A[j-1] + A[j]가 M이 되는 경우의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 10,000), M(1 ≤ M ≤ 300,000,000)이 주어진다. 다음 줄에는 A[1], A[2], …, A[N]이 공백으로 분리되어 주어진다. 각각의 A[x]는 30,000을 넘지 않는 자연수이다.

출력

첫째 줄에 경우의 수를 출력한다.

내가 푼 풀이

- 수열은 자연수로 이루어져있고, 연속된 수열의 합이 M이 되는 경우의 수를 구한다.

- 재귀호출로 수열의 첫번째부터 마지막번째까지 연속나열된 값들과 더하여 구한다.

 

import Foundation

// 입력받기
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], M = input[1]
let A = readLine()!.split(separator: " ").map{Int(String($0))!}
var count = 0

// 더하는 함수
func sum(index: Int, total: Int) {
    if total == M {
        count += 1
        return
    }
    if total > M {
        return
    }
    if index+1 < A.count {
        sum(index: index+1, total: total + A[index+1])
    } else {
        return
    }
}

for i in 0..<A.count {
    sum(index: i, total: A[i])
}
print(count)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-10819 차이를 최대로 Swift  (0) 2024.04.12
BOJ-12100 2048(Easy) Swift  (0) 2024.04.12
BOJ-1107 리모컨 Swift  (0) 2024.04.11
BOJ-1476 날짜 계산 Swift  (0) 2024.04.11
BOJ-14500 테트로미노 Swift (Brute-force)  (0) 2024.04.11

+ Recent posts