안녕하세요 요즘 블로그 활동이 뜸했는데요...^^ 그동안 이것저것 프로젝트 하면서 살았답니다 ㅎㅎ
제가 스위프 앱 1기에서 진행한 near 앱을 소개하겠습니다!
바쁜 현대 사회에서 소중한 친구들과의 관계를 놓치지 않고 더 가깝게 이어갈 수 있도록 돕는 것을 목표로 iOS 앱 'near'를 개발했습니다.
이번 포스팅에서는 'near' 앱 개발에 사용된 핵심 기술 스택, 아키텍처 결정 과정, 그리고 개발 중 겪었던 도전과 배움의 순간들을 공유하고자 합니다.
1. 왜 'near'인가? : 앱 개발 배경 및 목표
"연락해야 하는데..." 생각만 하고 놓쳐버린 친구, 있지 않으신가요? 'near'는 이런 아쉬움을 해결하기 위해 탄생했습니다. 저희는 사용자가 친구들의 생일, 기념일, 설정한 연락 주기 등을 놓치지 않고 관리하며, 꾸준하고 의미 있는 관계를 유지할 수 있도록 돕는 것을 목표로 삼았습니다.
주요 기능은 다음과 같습니다.
연락처 및 카카오톡 친구 연동: 손쉽게 친구 목록을 가져와 관리합니다.
맞춤 연락 주기 설정: 친구별로 원하는 연락 주기를 설정하고 리마인더를 받습니다.
챙김 기록 및 관리: 친구를 챙긴 기록을 남기고, 관계 유지 현황을 파악합니다.
홈 화면 대시보드: 이번 달 챙겨야 할 친구와 전체 친구 목록을 한눈에 확인합니다.
2. 기술 스택 Deep Dive: 'near'를 구성하는 핵심 기술들
안정적이면서도 확장 가능하고, 효율적인 개발 경험을 위해 신중하게 기술 스택을 선택했습니다.
2.1. 프로젝트 관리 및 구조화: Tuist
선택 이유: Xcode 프로젝트 파일(.xcodeproj, .xcworkspace)의 복잡성과 충돌 가능성을 줄이고, 프로젝트 설정을 코드로 명확하게 관리하기 위해 Tuist를 도입했습니다.
Project.swift 파일을 통해 앱 타겟, 테스트 타겟, 설정, 종속성, 리소스 등을 정의했습니다.
모듈화된 구조를 쉽게 구현하고, 팀원 간 설정 일관성을 유지하며 빌드 시간을 단축하는 효과를 얻었습니다.
.xcconfig 파일을 활용하여 Debug/Release 환경별 설정을 분리하고, KAKAO_APP_KEY, DEV_BASE_URL 등 민감 정보를 안전하게 관리했습니다.
선택 이유: View의 역할(UI 표시 및 사용자 입력)과 Business Logic(데이터 처리, 상태 관리)을 명확히 분리하여 코드의 가독성, 테스트 용이성, 유지보수성을 높이기 위해 MVVM 패턴을 채택했습니다.
구현: 각 View에 대응하는 ViewModel을 만들어 데이터 흐름과 상태 변화를 관리했습니다. SwiftUI와의 조합을 통해 데이터 바인딩을 효과적으로 활용했습니다.
2.3. UI 프레임워크: SwiftUI
선택 이유: iOS 17.0을 타겟으로 하고 MVVM 패턴을 사용하는 점, 그리고 선언적이고 현대적인 UI 개발 방식의 이점을 고려하여 SwiftUI를 주력 UI 프레임워크로 사용했습니다.
장점: 코드의 양을 줄이고, 실시간 프리뷰를 통해 UI 개발 생산성을 크게 향상시킬 수 있었습니다. MVVM 패턴과의 자연스러운 통합도 장점입니다.
2.4. 네트워킹: Alamofire & Backend API
Alamofire: 백엔드 서버와의 안정적이고 편리한 HTTP 통신을 위해 검증된 라이브러리인 Alamofire를 사용했습니다. (dependencies: [.external(name: "Alamofire")])
Backend API 연동: 자체 백엔드 서버와 RESTful API 통신을 통해 로그인, 친구 목록 조회/추가/수정/삭제, 프로필 관리, 챙김 기록 등 앱의 핵심 기능을 구현했습니다.
2.5. 사용자 인증: Kakao SDK, Sign in with Apple, Token Management
Kakao SDK: 핵심 로그인 방식으로 카카오 로그인을 사용하며, 사용자 정보 및 친구 목록(동의 시)을 가져오는 데 활용했습니다. (KakaoSDKCommon, Auth, User, Friend 등 다수 모듈 사용)
Sign in with Apple: Apple 사용자를 위한 간편 로그인 옵션을 제공합니다. (entitlements: "Tuist/SignInWithApple.entitlements")
Token 기반 인증: 백엔드와의 안전한 통신을 위해 Access Token과 Refresh Token을 관리하고, Keychain에 안전하게 저장하며 자동 로그인 및 토큰 갱신 로직을 구현했습니다.
2.6. 비동기 이미지 처리: Kingfisher & Presigned URL
Kingfisher: 서버로부터 받은 친구 프로필 이미지 URL 등을 비동기적으로 다운로드하고 캐싱하여 UI에 효율적으로 표시하기 위해 Kingfisher를 사용했습니다. (dependencies: [.external(name: "Kingfisher")])
Presigned URL: 친구 프로필 이미지 업로드/다운로드 시 AWS S3 Presigned URL 방식을 사용하여 서버 부하를 줄이고 효율성을 높였습니다.
2.7. 로컬 데이터 관리: Core Data
활용: 알림(Notification) 관련 데이터를 로컬에 저장하고 관리하는 데 Core Data를 사용했습니다. (coreDataModels 설정: NotificationContainer.xcdatamodeld) 향후 오프라인 지원 강화 등 로컬 데이터 활용 범위를 넓힐 가능성을 염두에 두었습니다.
2.8. 시스템 연동: Contacts Framework
사용자의 주소록에 접근하여 친구를 가져오는 기능을 구현하기 위해 Contacts Framework를 사용했습니다. 사용자에게 명확한 권한 요청 안내를 제공합니다.
2.9. UI/UX 커스터마이징
Custom Fonts (Pretendard): 앱 전체 디자인의 일관성을 위해 Pretendard 폰트를 적용했습니다.
Light Mode 고정: 초기 버전에서는 디자인 리소스 및 개발 집중도를 고려하여 Light 모드만 지원하도록 설정했습니다.
LaunchScreen: 앱 시작 시 브랜드 로고 등을 보여주는 LaunchScreen 스토리보드를 사용합니다.
3. 주요 기능 구현 하이라이트
기술 스택을 바탕으로 다음과 같은 주요 기능들을 구현했습니다.
로그인 및 온보딩: 카카오/애플 SDK를 이용한 간편 로그인 후, 서비스 이용 약관 동의, 연락처/카카오 친구 가져오기, 연락 주기 설정으로 이어지는 자연스러운 온보딩 플로우를 설계했습니다.
친구 관리: 연락처 및 카카오톡에서 가져온 친구를 '챙길 친구'로 등록하고, 맞춤 연락 주기를 설정합니다. Alamofire를 통해 백엔드 API와 연동하여 친구 정보를 생성, 조회, 수정, 삭제합니다.
홈 화면: '이번 달 챙길 친구'와 '내 사람들' 목록을 보여주며, 친구를 챙겼음을 기록하는 기능을 제공합니다.
프로필 상세 및 수정: 친구의 상세 정보(연락처, 기념일, 메모 등)를 확인하고 수정하는 기능을 구현했습니다.
4. 개발 과정에서의 도전과 배움 (Challenges & Learnings)
개발 과정은 순탄하지만은 않았습니다. 몇 가지 기술적 어려움에 직면했고, 이를 해결하며 값진 경험을 얻었습니다.
Tuist와 Kakao SDK 리소스 번들: Kakao SDK 중 친구 목록 관련 기능을 사용할 때, SDK 내부 리소스 번들을 Tuist 프로젝트에 올바르게 포함시키는 과정에서 어려움을 겪었습니다. Tuist의 리소스 처리 방식과 Kakao SDK 문서 검토를 통해 해결했습니다.
데이터 동기화 및 상태 관리: 친구 추가/수정 후 홈 화면 등 다른 View에 변경 사항이 즉시 반영되지 않는 문제가 있었습니다. SwiftUI의 @State, @ObservedObject, @EnvironmentObject 등을 활용한 상태 관리 로직을 개선하고, 데이터 흐름을 명확히 하여 해결했습니다.
비동기 처리: 여러 친구의 프로필 이미지를 동시에 업로드하거나, 다수의 API 호출 후 UI를 업데이트하는 과정에서 DispatchGroup 등을 활용하여 비동기 코드를 안정적이고 효율적으로 관리했습니다.
Tuist와 Core Data 연동: 초기 설정 시 Core Data 모델 파일(.xcdatamodeld) 경로를 Tuist 프로젝트 설정(Project.swift)에 정확히 명시하여 인식시키는 데 약간의 시행착오가 있었습니다.
API 명세 변경 대응: 개발 중 백엔드 API 명세가 변경되는 경우가 있었고, 이에 맞춰 모델과 네트워킹 코드를 신속하게 수정하며 백엔드 팀과의 긴밀한 소통의 중요성을 다시 한번 느꼈습니다.
5. 마무리하며
'near' 앱은 Tuist를 통한 효율적인 프로젝트 관리, MVVM 아키텍처 기반의 구조화된 코드, SwiftUI를 활용한 현대적인 UI 구현, 그리고 Alamofire, Kingfisher, Kakao SDK 등 검증된 라이브러리 및 Core Data, Contacts 등 시스템 프레임워크를 적극적으로 활용하여 개발되었습니다.
개발 과정에서의 도전들은 저희 팀을 더욱 성장시키는 계기가 되었습니다. 앞으로도 사용자분들이 친구들과의 소중한 관계를 'near'를 통해 더 쉽고 의미 있게 가꿔나갈 수 있도록, 꾸준히 기능을 개선하고 안정화해 나갈 예정입니다.
긴 글 읽어주셔서 감사합니다! 'near' 앱 개발 과정이나 사용된 기술에 대해 궁금한 점이 있다면 언제든지 댓글로 남겨주세요.
수웅이는매달주어진음식을빨리먹는푸드파이트대회를개최합니다. 이대회에서선수들은 1대 1로대결하며, 매대결마다음식의종류와양이바뀝니다. 대결은준비된음식들을일렬로배치한뒤, 한선수는제일왼쪽에있는음식부터오른쪽으로, 다른선수는제일오른쪽에있는음식부터왼쪽으로순서대로먹는방식으로진행됩니다. 중앙에는물을배치하고, 물을먼저먹는선수가승리하게됩니다.
이때, 대회의공정성을위해두선수가먹는음식의종류와양이같아야하며, 음식을먹는순서도같아야합니다. 또한, 이번대회부터는칼로리가낮은음식을먼저먹을수있게배치하여선수들이음식을더잘먹을수있게하려고합니다. 이번대회를위해수웅이는음식을주문했는데, 대회의조건을고려하지않고음식을주문하여몇개의음식은대회에사용하지못하게되었습니다.
구조체와 클래스의 정의 구문은 비슷합니다.struct키워드로 구조를 소개하고 키워드로 클래스를소개합니다class.둘 다 한 쌍의 중괄호 안에 전체 정의를 배치합니다.
struct SomeStructure {
// structure definition goes here
}
class SomeClass {
// class definition goes here
}
EX)
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
인스턴스를 생성하는 구문은 구조체와 클래스 모두 매우 유사합니다.
let someResolution = Resolution()
let someVideoMode = VideoMode()
모든 구조체에는 자동으로 생성된멤버 단위 초기화가있으며 이를 사용하여 새 구조체 인스턴스의 멤버 속성을 초기화할 수 있습니다.새 인스턴스의 속성에 대한 초기 값은 이름으로 멤버별 이니셜라이저에 전달될 수 있습니다.
let vga = Resolution(width: 640, height: 480)
구조체와 달리 클래스 인스턴스는 기본 멤버 단위 초기화를 받지 않습니다. (초기화 initialization)
클래스는 참조 유형입니다.
값 유형과 달리참조 유형은변수나 상수에 할당되거나 함수에 전달될 때 복사되지않습니다.복사본이 아니라 동일한 기존 인스턴스에 대한 참조가 사용됩니다.
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
frameRate를 30으로 바꿈
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
import UIKit
struct Town{
//프로퍼티
let name = "Angelaland"
var citizens = ["Angela", "Jack Bauer"]
var resources = ["Gain" : 100, "Ore" :42, "Wool" : 75]
//구조체 안의 리턴값 없는 메서드(구조체나 클래스 안에서의 정의)
//독립실행형이고 어디선가 자유롭게 사용된다면 함수라고 정의 따라서 fortify는 메서드
func fortify(){
print ("Defences increased!")
}
}
var myTown = Town() //Town개체를 myTown으로 사용
print(myTown.name) // 구조체의 name 출력
print("\(myTown.name) has \(myTown.resources["Gain"]!) bags of grain.")
myTown.citizens.append("Smith") //moTown의 citizens에 Smith추가
print(myTown.citizens.count) //citizens의 개수 출력
myTown.fortify()
//init사용
struct Town_initCase{
//프로퍼티
let name : String
var citizens : [String]
var resources : [String : Int]
init(name : String, citizens : [String], resources : [String:Int]){
self.name = name
self.citizens = citizens
self.resources = resources
}
//구조체 안의 리턴값 없는 메서드(구조체나 클래스 안에서의 정의)
//독립실행형이고 어디선가 자유롭게 사용된다면 함수라고 정의 따라서 fortify는 메서드
func fortify(){
print ("Defences increased!")
}
}
//init으로 Town_initCase 구조체 초기화
var anotherTown = Town_initCase(name: "Nameless Island", citizens: ["Tom"], resources: ["Coconuts" : 100])
anotherTown.citizens.append("Wilson")
print(anotherTown.citizens)
스택 (stack)은 기본적인 자료구조 중 하나로, 컴퓨터 프로그램을 작성할 때 자주 이용되는 개념이다. 스택은 자료를 넣는 (push) 입구와 자료를 뽑는 (pop) 입구가 같아 제일 나중에 들어간 자료가 제일 먼저 나오는 (LIFO, Last in First out) 특성을 가지고 있다.
1부터 n까지의 수를 스택에 넣었다가 뽑아 늘어놓음으로써, 하나의 수열을 만들 수 있다. 이때, 스택에 push하는 순서는 반드시 오름차순을 지키도록 한다고 하자. 임의의 수열이 주어졌을 때 스택을 이용해 그 수열을 만들 수 있는지 없는지, 있다면 어떤 순서로 push와 pop 연산을 수행해야 하는지를 알아낼 수 있다. 이를 계산하는 프로그램을 작성하라.
입력
첫 줄에 n (1 ≤ n ≤ 100,000)이 주어진다. 둘째 줄부터 n개의 줄에는 수열을 이루는 1이상 n이하의 정수가 하나씩 순서대로 주어진다. 물론 같은 정수가 두 번 나오는 일은 없다.
출력
입력된 수열을 만들기 위해 필요한 연산을 한 줄에 한 개씩 출력한다. push연산은 +로, pop 연산은 -로 표현하도록 한다. 불가능한 경우 NO를 출력한다.
예제 입력 1복사
8
4
3
6
8
7
5
2
1
예제 출력 1복사
+
+
+
+
-
-
+
+
-
+
+
-
-
-
-
-
예제 입력 2복사
5
1
2
5
3
4
예제 출력 2복사
NO
힌트
1부터 n까지에 수에 대해 차례로 [push, push, push, push, pop, pop, push, push, pop, push, push, pop, pop, pop, pop, pop] 연산을 수행하면 수열 [4, 3, 6, 8, 7, 5, 2, 1]을 얻을 수 있다.
import Foundation
public struct Stack<T>{
private var elements = [T]()
public init() {}
public mutating func push(_ element: T){
self.elements.append(element)
}
public mutating func pop() -> T? {
let pop = self.elements.popLast()
self.elements.removeLast()
return pop
}
public mutating func poplast() {
self.elements.removeLast()
}
public func top() -> T? {
return self.elements.last
}
}
var myStack = Stack<Int>()
var ansArr : [String] = []
var cnt = 1
let input = Int(readLine()!)!
for _ in 0 ..< input {
let num = Int(readLine()!)!
while cnt <= num{
myStack.push(cnt)
ansArr.append("+")
cnt += 1
}
if myStack.top() == num{
myStack.poplast()
ansArr.append("-")
}else{
print("NO")
exit(0)
}
}
print(ansArr.joined(separator: "\n"))
// 빈 Int Array 생성
var integers: Array<Int> = Array<Int>()
// 같은 표현
// var integers: Array<Int> = [Int]()
// var integers: Array<Int> = []
// var integers: [Int] = Array<Int>()
// var integers: [Int] = [Int]()
// var integers: [Int] = []
// var integers = [Int]()
Array 활용
integers.append(1)
integers.append(100)
// Int 타입이 아니므로 Array에 추가할 수 없습니다
//integers.append(101.1)
print(integers) // [1, 100]
// 멤버 포함 여부 확인
print(integers.contains(100)) // true
print(integers.contains(99)) // false
// 멤버 교체
integers[0] = 99
// 멤버 삭제
integers.remove(at: 0)
integers.removeLast()
integers.removeAll()
// 멤버 수 확인
print(integers.count)
// 인덱스를 벗어나 접근하려면 익셉션 런타임 오류발생
//integers[0]
let을 이용하여 Array를 선언하면 불변 Array가 됩니다.
let immutableArray = [1, 2, 3]
// 수정이 불가능한 Array이므로 멤버를 추가하거나 삭제할 수 없습니다
//immutableArray.append(4)
//immutableArray.removeAll()
Dictionary
-Dictionary는 키 와 값 의 쌍으로 이루어진 컬렉션 타입입니다.
// Key가 String 타입이고 Value가 Any인 빈 Dictionary 생성
var anyDictionary: Dictionary<String, Any> = [String: Any]()
// 같은 표현
// var anyDictionary: Dictionary <String, Any> = Dictionary<String, Any>()
// var anyDictionary: Dictionary <String, Any> = [:]
// var anyDictionary: [String: Any] = Dictionary<String, Any>()
// var anyDictionary: [String: Any] = [String: Any]()
// var anyDictionary: [String: Any] = [:]
// var anyDictionary = [String: Any]()
숫자 카드는 정수 하나가 적혀져 있는 카드이다. 상근이는 숫자 카드 N개를 가지고 있다. 정수 M개가 주어졌을 때, 이 수가 적혀있는 숫자 카드를 상근이가 몇 개 가지고 있는지 구하는 프로그램을 작성하시오.
입력
첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.
셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 몇 개 가지고 있는 숫자 카드인지 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이 수도 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.
출력
첫째 줄에 입력으로 주어진 M개의 수에 대해서, 각 수가 적힌 숫자 카드를 상근이가 몇 개 가지고 있는지를 공백으로 구분해 출력한다.
func sequencial(_ array: [Int], num: Int) -> Int {
var a = 0
for element in array {
if num == element {
a += 1
}
}
return a
}
var N = Int(readLine()!)!
var input_N = readLine()!.split(separator: " ").map{Int(String($0))!}
var M = Int(readLine()!)!
let input_M = readLine()!.split(separator: " ").map{Int(String($0))!}
input_N.sort()
var arr = [Int]()
for i in 0..<M{
arr.append(sequencial(input_N, num: input_M[i]))
}
print(arr.map{String($0)}.joined(separator: " "))
이진탐색으로 어떻게 해야할지 잘모르겠어서 완전탐색으로 하였다..
-> 당연히 시간초과 ㅋㅋㅌ ㅆ,,
실패코드 2트
func binarySearch(_ left: Int,_ right: Int , _ target: Int) -> Int{
let mid = Int((left + right) / 2)
var count = 0
if target == input_N[mid]{
var midLeft = mid - 1
count += 1
while midLeft >= 0 && target == input_N[midLeft]{
count += 1
midLeft -= 1
}
var midRight = mid + 1
while midRight < input_N.count && target == input_N[midRight]{
count += 1
midRight += 1
}
return count
}
if left > right || target < input_N[left] || target > input_N[right]{
return count
}
if target > input_N[mid]{
return binarySearch(mid + 1, right, target)
}else if target<input_N[mid]{
return binarySearch(left, mid - 1, target)
}
return count
}
var N = Int(readLine()!)!
var input_N = readLine()!.split(separator: " ").map{Int(String($0))!}
var M = Int(readLine()!)!
let input_M = readLine()!.split(separator: " ").map{Int(String($0))!}
input_N.sort()
var arr : [Int] = []
for i in input_M{
arr.append(binarySearch(0 , N-1 , i))
}
print(arr.map{String($0)}.joined(separator: " "))
나름 이분탐색 사용하였지만 다시나는 시간초과,,, 무엇이 문제일까..?
다음에 알아보도록 하자....
실패코드 3트
import Foundation
final class FileIO {
private let buffer:[UInt8]
private var index: Int = 0
init(fileHandle: FileHandle = FileHandle.standardInput) {
buffer = Array(try! fileHandle.readToEnd()!)+[UInt8(0)] // 인덱스 범위 넘어가는 것 방지
}
@inline(__always) private func read() -> UInt8 {
defer { index += 1 }
return buffer[index]
}
@inline(__always) func readInt() -> Int {
var sum = 0
var now = read()
var isPositive = true
while now == 10
|| now == 32 { now = read() } // 공백과 줄바꿈 무시
if now == 45 { isPositive.toggle(); now = read() } // 음수 처리
while now >= 48, now <= 57 {
sum = sum * 10 + Int(now-48)
now = read()
}
return sum * (isPositive ? 1:-1)
}
@inline(__always) func readString() -> String {
var now = read()
while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
let beginIndex = index-1
while now != 10,
now != 32,
now != 0 { now = read() }
return String(bytes: Array(buffer[beginIndex..<(index-1)]), encoding: .ascii)!
}
@inline(__always) func readByteSequenceWithoutSpaceAndLineFeed() -> [UInt8] {
var now = read()
while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
let beginIndex = index-1
while now != 10,
now != 32,
now != 0 { now = read() }
return Array(buffer[beginIndex..<(index-1)])
}
}
func binarySearch(_ left: Int,_ right: Int , _ target: Int) -> Int{
let mid = Int((left + right) / 2)
var count = 0
if target == input_N[mid]{
var midLeft = mid - 1
count += 1
while midLeft >= 0 && target == input_N[midLeft]{
count += 1
midLeft -= 1
}
var midRight = mid + 1
while midRight < input_N.count && target == input_N[midRight]{
count += 1
midRight += 1
}
return count
}
if left > right || target < input_N[left] || target > input_N[right]{
return count
}
if target > input_N[mid]{
return binarySearch(mid + 1, right, target)
}else if target<input_N[mid]{
return binarySearch(left, mid - 1, target)
}
return count
}
let fileIO = FileIO()
var input_N: [Int] = []
var input_M: [Int] = []
let N = fileIO.readInt()
for _ in 0..<N {
input_N.append(fileIO.readInt())
}
let M = fileIO.readInt()
for _ in 0..<M {
input_M.append(fileIO.readInt())
}
input_N.sort(by: <)
var result = ""
for i in input_M{
let count = binarySearch(0 , N-1 , i)
result += "\(count) "
}
print(result)
시간..초,,ㄱ..ㅘ....
딱알았다.. sort때문인거같으 이것만 다시해서 올려보겠습니다..ㅎ
4트ㅎㅎ
//import Foundation
//
//final class FileIO {
// private let buffer:[UInt8]
// private var index: Int = 0
//
// init(fileHandle: FileHandle = FileHandle.standardInput) {
//
// buffer = Array(try! fileHandle.readToEnd()!)+[UInt8(0)] // 인덱스 범위 넘어가는 것 방지
// }
//
// @inline(__always) private func read() -> UInt8 {
// defer { index += 1 }
//
// return buffer[index]
// }
//
// @inline(__always) func readInt() -> Int {
// var sum = 0
// var now = read()
// var isPositive = true
//
// while now == 10
// || now == 32 { now = read() } // 공백과 줄바꿈 무시
// if now == 45 { isPositive.toggle(); now = read() } // 음수 처리
// while now >= 48, now <= 57 {
// sum = sum * 10 + Int(now-48)
// now = read()
// }
//
// return sum * (isPositive ? 1:-1)
// }
//
// @inline(__always) func readString() -> String {
// var now = read()
//
// while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
// let beginIndex = index-1
//
// while now != 10,
// now != 32,
// now != 0 { now = read() }
//
// return String(bytes: Array(buffer[beginIndex..<(index-1)]), encoding: .ascii)!
// }
//
// @inline(__always) func readByteSequenceWithoutSpaceAndLineFeed() -> [UInt8] {
// var now = read()
//
// while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
// let beginIndex = index-1
//
// while now != 10,
// now != 32,
// now != 0 { now = read() }
//
// return Array(buffer[beginIndex..<(index-1)])
// }
//}
func binarySearch(_ left: Int,_ right: Int , _ target: Int) -> Int{
let mid = Int((left + right) / 2)
var count = 0
if target == input_N[mid]{
var midLeft = mid - 1
count += 1
while midLeft >= 0 && target == input_N[midLeft]{
count += 1
midLeft -= 1
}
var midRight = mid + 1
while midRight < input_N.count && target == input_N[midRight]{
count += 1
midRight += 1
}
return count
}
if left > right || target < input_N[left] || target > input_N[right]{
return count
}
if target > input_N[mid]{
return binarySearch(mid + 1, right, target)
}else if target<input_N[mid]{
return binarySearch(left, mid - 1, target)
}
return count
}
func quickSort(numArr : [Int]) -> [Int]{
if numArr.count < 2{
return numArr
}
let pivot = numArr[0]
var left :[Int] = []
var right :[Int] = []
for i in 1..<numArr.count{
if pivot > numArr[i]{
left.append(numArr[i])
}else if pivot < numArr[i] {
right.append(numArr[i])
}else if pivot == numArr[i]{
left.insert(numArr[i], at: 0)
}
}
return quickSort(numArr: left) + [pivot] + quickSort(numArr: right)
}
//let fileIO = FileIO()
//var input_N: [Int] = []
//var input_M: [Int] = []
//let N = fileIO.readInt()
let N = Int(readLine()!)!
//for _ in 0..<N {
// input_N.append(fileIO.readInt())
//}
var input_N = readLine()!.split(separator: " ").map{Int(String($0))!}
//let M = fileIO.readInt()
let M = Int(readLine()!)!
//for _ in 0..<M {
// input_M.append(fileIO.readInt())
//}
var input_M = readLine()!.split(separator: " ").map{Int(String($0))!}
input_N = quickSort(numArr: input_N)
var result = ""
for i in input_M{
let count = binarySearch(0 , N-1 , i)
result += "\(count) "
}
print(result)
이렇게 해보니 1트와 2트는 그냥 틀린것 같네요? ㅎㅎ (말로만 1트 2트지 수많은 트라이들이...)
다른분들은 lower upper 나눠서 하길래 나는 다른길을 걷고자 악바리로 한번에 해보려했지만 안되는것같네요?ㅎㅎ
아직 의지는 꺾이지 않았습니다.
제출코드 5트..
import Foundation
final class FileIO {
private let buffer:[UInt8]
private var index: Int = 0
init(fileHandle: FileHandle = FileHandle.standardInput) {
buffer = Array(try! fileHandle.readToEnd()!)+[UInt8(0)] // 인덱스 범위 넘어가는 것 방지
}
@inline(__always) private func read() -> UInt8 {
defer { index += 1 }
return buffer[index]
}
@inline(__always) func readInt() -> Int {
var sum = 0
var now = read()
var isPositive = true
while now == 10
|| now == 32 { now = read() } // 공백과 줄바꿈 무시
if now == 45 { isPositive.toggle(); now = read() } // 음수 처리
while now >= 48, now <= 57 {
sum = sum * 10 + Int(now-48)
now = read()
}
return sum * (isPositive ? 1:-1)
}
@inline(__always) func readString() -> String {
var now = read()
while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
let beginIndex = index-1
while now != 10,
now != 32,
now != 0 { now = read() }
return String(bytes: Array(buffer[beginIndex..<(index-1)]), encoding: .ascii)!
}
@inline(__always) func readByteSequenceWithoutSpaceAndLineFeed() -> [UInt8] {
var now = read()
while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
let beginIndex = index-1
while now != 10,
now != 32,
now != 0 { now = read() }
return Array(buffer[beginIndex..<(index-1)])
}
}
func lowerBound(_ left: inout Int,_ right: inout Int , _ target: Int) -> Int{
while left < right{
let mid = Int((left + right) / 2)
if input_N[mid] < target {
left = mid + 1
}else if target <= input_N[mid] {
right = mid
}
}
return left
}
func upperBound(_ left: inout Int,_ right: inout Int , _ target: Int) -> Int{
while left < right{
let mid = Int((left + right) / 2)
if input_N[mid] <= target{
left = mid + 1
}else if target < input_N[mid]{
right = mid
}
}
return right
}
func binarySearch(_ left: Int,_ right: Int , _ target: Int) -> Int{
let mid = Int((left + right) / 2)
var count = 0
if target == input_N[mid]{
var midLeft = mid - 1
count += 1
while midLeft >= 0 && target == input_N[midLeft]{
count += 1
midLeft -= 1
}
var midRight = mid + 1
while midRight < input_N.count && target == input_N[midRight]{
count += 1
midRight += 1
}
return count
}
if left > right || target < input_N[left] || target > input_N[right]{
return count
}
if target > input_N[mid]{
return binarySearch(mid + 1, right, target)
}else if target<input_N[mid]{
return binarySearch(left, mid - 1, target)
}
return count
}
func quickSort(numArr : [Int]) -> [Int]{
if numArr.count < 2{
return numArr
}
let pivot = numArr[0]
var left :[Int] = []
var right :[Int] = []
for i in 1..<numArr.count{
if pivot > numArr[i]{
left.append(numArr[i])
}else if pivot < numArr[i] {
right.append(numArr[i])
}else if pivot == numArr[i]{
left.insert(numArr[i], at: 0)
}
}
return quickSort(numArr: left) + [pivot] + quickSort(numArr: right)
}
let fileIO = FileIO()
var input_N: [Int] = []
var input_M: [Int] = []
var N = fileIO.readInt()
//var N = Int(readLine()!)!
for _ in 0..<N {
input_N.append(fileIO.readInt())
}
//var input_N = readLine()!.split(separator: " ").map{Int(String($0))!}
var M = fileIO.readInt()
//var M = Int(readLine()!)!
for _ in 0..<M {
input_M.append(fileIO.readInt())
}
//var input_M = readLine()!.split(separator: " ").map{Int(String($0))!}
//input_N = quickSort(numArr: input_N)
input_N.sort()
var result = ""
var zero = 0
var N_real = N
for i in input_M{
//let count = binarySearch(0 , N-1 , i)
let up = upperBound(&zero, &N, i)
zero = 0
N = N_real
let lo = lowerBound(&zero, &N, i)
zero = 0
N = N_real
let count = up - lo
//print(String((upperBound(&zero, &N, i)) - (lowerBound(&zero, &N, i))), terminator: " ")
result += "\(count) "
}
print(result)