폰으로 음악을 자주 듣지않고있기에 간단히 만든 앱을 사용하여 가끔 음악을 듣는다

유튜브영상을 URL을 입력하면 mp3파일로 만들어 다운로드하게 해주는 사이트에서 파일을 받아 재생하는 용도이다

솔직히 이러면 그냥 기본 파일앱에 연동되어있는걸로 재생하는게 편한데 어쩌다 3편글까지 왔는지 기억이 가물가물...

아마 공부를 겸해서 그런거겠거늘한다 

아무튼 기본앱에는 있는 기능은 잠금화면에서의 컨트롤바가 내 앱에는 없다

그래서 이번엔 이걸 추가하려고하였으나....

 

며칠 고생했으나 문제를 찾지 못했고 그렇게 그냥 묵혀두다 오늘 다시 시작했다

아마 설정문제이겠거늘 싶긴한데 예제로 구한 앱과 다른 설정부분을 찾지 못했고 나는 이참에 아예 소스를 다 들어내고 처음부터 배치를 다시할까 한다

우선 관련된 클래스는

MPRemoteCommandCenter

https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter

 

MPRemoteCommandCenter | Apple Developer Documentation

An object that responds to remote control events sent by external accessories and system controls.

developer.apple.com

그래고 메서드는

beginReceivingRemoteControlEvents()

 

 

 

 

https://developer.apple.com/documentation/uikit/uiapplication/1623126-beginreceivingremotecontrolevent

 

beginReceivingRemoteControlEvents() | Apple Developer Documentation

Tells the app to begin receiving remote-control events.

developer.apple.com

 

 

추가로

공식예제는 아래의 링크이다.

https://developer.apple.com/documentation/mediaplayer/becoming_a_now_playable_app

 

Becoming a now playable app | Apple Developer Documentation

Ensure your app is eligible to become the Now Playing app by adopting best practices for providing Now Playing info and registering for remote command center actions.

developer.apple.com

 

그런데 솔직히 공식 예제는 분리가 너무 많이 되어있어서 읽기 귀찮다

 

뭐 아무튼 그래서 우선 모든 코드들을 동작하지 않게 처리하고

아래와 같은 순서로 앱의 리빌드할꺼다

 

1. 백그라운드 오디오 재생에 필요한 권한 처리 info.plist

2. 플레이어 생성 -> 기존 avplyaer에서 avQueuePlayer로 대체

3. MPRemoteCommandCenter 설정 및 설정

4. ui코드 재위치

5. 각 이벤트 연결 및 테스트

 

아 귀찮아

리멤버에 뮤직카우라는 회사에서 네이티브 엔지니어를 구한다는 공고문이 올라왔다

회사 위치나 규모나 하는일이나 맘에 들어서 지원할까! 라고 공고문을 읽는데 요새 네이티브를 안잡은지 오래되고 스위프트5부터 추가된 rx개념과 swiftui의 경험이 없는거나 다름없어서 자격요건이 미묘해서 지원은 아직 안누름

해서 이왕 네이티브 엔지니어로 재취업하고싶으면 해당 자격요건들을 공부하는게 좋을듯하여 해당 내용으로 글을 써보자

 

 

https://career.musicow.com/o/129878

[자격요건]

  • 컴퓨터공학 전공 혹은 이에 상응하는 실무 경험이 있으신 분
  • 3년 이상 또는 이에 준하는 iOS Native 개발 경험이 있으신 분
  • Swift 언어 및 iOS Framework 에 대한 이해가 있으신 분
  • Rest API 의 이해 및 사용 경험이 있으신 분
  • iOS 개발 트렌드에 관심이 많으신 분
  • 문제를 정의하고 적극적으로 해결하고자 하는 의지가 있으신 분

 

[우대사항]

  • SwiftUI 개발 경험이 있으신 분
  • Code based UIKit 개발이 가능한 분
  • Network 와 Concurrency 에 대한 이해가 깊으신 분
  • App Architecture (MVVM, TCA 등) 적용 경험이 있으신 분
  • Reactive Programming(Combine) 에 대한 이해가 있으신 분
  • Unit Test 및 UI Test 작성 및 CI / CD 경험이 있으신 분
  • 음악을 좋아하시고 음악 시장과 문화에 관심이 있으신 분

'글 쓸 애들' 카테고리의 다른 글

기초 자료구조에 대한 내용  (0) 2023.01.19
Array 조작  (0) 2022.05.18
URL.isDirectory  (0) 2022.05.16
애플 공홈 튜톨  (0) 2022.05.13
네비게이션의 스와이프 백제스쳐가 동작하지 않는다.  (0) 2022.05.04

0. 이글을 쓰기시작한 이유.

예~전에 한참 스위프트 공부에 재미들여서 했을적에 서버드리븐 유아이라는 개념을 듣고 언젠간 공부해봐야지 하고 메모해놨던 일이 있다.

지금이 24년이고, 내가 앱에 입문했을때가 18년이니까 대충 20년쯤이지 아니였을까하는데 뭐 하나 정리를 제대로 안해두는 내 성격상에 여태 까맣게 잊고있다가 최근에 flutter를 공부하다 다시 생각이 났다.

개념자체는 엄청 신기하거나 그러진 않고 앱의 화면에 대한 정보를 서버에서 저장해놓고 앱은 해당 정보를 받아와서 그려는식의 웹뷰를 네이티브 형식으로 구현한 느낌이다.

아 물론 이 설명은 내가 슬쩍 검색해보고 나오는 게시글들의 감상이다

이런식이면 이게 웹앱인지 네이티브앱인지 아리송해지긴하는데 솔직히 요새 서버에서 데이터 받아와서 테이블이나 리스트에 뿌려주는거 보면 어처피 이게 그거 아닌가 싶어서 오히려 왜 방법이 스타트업 사이에서 굴러지고 있는 사람들이 주로 쓰고있지않은건가 의문이든다

요즘은 애플의 앱심사가 정말 빨라졌다지만 그래도 aws에서 파이프라인만들어놔서 빌드하면 자동으로 게시가 되는거 웹페이지에 비교하기에는 너무 천지차이이고 이제막 시작하는 앱이면 이곳저곳 버그가 산재해있거나 바뀌는 아이템으로 앱업데이트를 자주해야하는 상황에선 정말 답답하게된다

이런 상황으로 이전에는 앱시작시 팝업 정보가 있는지 서버에 저장해놨다가 팝업정보가 있으면 앱시작시 띄여주고 해당 팝업 선택시 웹페이지로 연결해주는 방식으로 했었는데 (내 이전직장들이 죄다 망해버려서 오랫동안 쓰이진 않았다) 이런것보단 차라리 server driven ui가 맞지않을까 싶다

UI는 네이티브로 고정해두고 내부 아이템들의 서버변화를 줘서하는거의 크게 다르지 않아서 굳이 기존앱의 변경하는 메리트는 없어보인다

그런데도 이 서버드리븐방식의 글을쓰게된이유는...

블로그에 글을 너무 안올리고있고 거의 반년째 일이 없어 날백수생활을 하고있기때문이다.

뭐라도 해야지....

그렇게 난 flutter로 server driven ui의 구현에 대한 테스트앱을 만들어보기로 한다.

 

 

1. 아이디어 정리

원래는 하루동안 잠깐만 하려고 했던게 조금 쓰고 이제서야 다시 잡았다.

현 시점이 0번항을 적고 나서 시일이 좀 지난 시점이라 당시 어떤 생각을 했는지 좀 잊어버렸다

살짝 짜둔 코드를 보며 다시 아이디어를 정리하고 가야겠다.

내 앱은 우선

전체 구성은 변경하지 않도록 했다 이것도 잘 생각해보면 앱 구성자체를 코드형태로 서버에 저장했다가

스플래시화면에서 받아서 렌더링하는 방법으로 해도 될듯하긴한데 이렇게 하면 차라리 웹앱으로 하는게 맞지않나 싶어서 이것은 포기했다

그렇다면 어느정도 틀만 만들어두고 해당 틀내용만 받아오는식으로 할 생각인데

 

대강 각 탭 페이지는 라우트로 만들어두고 

각 페이지는 카드로 구성되어 있고 각 카드는 몇 종류로 나눠있으며 해당 카드를 미리 만들어두어( 스위프트 uikit의 테이블셀 처럼 ) 

페이지 첫 로드시 각 카드의 순서, 종류, 종류에 따른 내부 컨텐츠 값을 받아와서 화면에 그려주는 방식으로 하려고 한다

 

생각이 깊어질수록 플러터 기본 제공 위젯을 이용하면되는게 아닌가 싶어지기도하는데 이래서 따로 안만들어두고 차라리 웹앱으로 가던가 하는건가....

특정 디자인이 이미 구현되어 있고 꽤나 다른 앱과 다른 유니크한 디자인이라면 일종의 UIkit을 만들어서 제공하는게 있을지도 모르겠다 

대기업 앱엔 있을지도?

 

뭐 아무튼 이번 프로젝트는 방법이 구현이 되는가가 중요하기에 범위자체는 크게 하지 않을거다

많은 앱에서 사용하는 배너용 카드, 단순 글 알림카드?, 사진들어간 좀 큰카드 이렇게 세가지 정도만 화면에서 보여지고

이걸 보여주기위한 넓~직한 카드 읽기용 글카드(사진 포함) -> 단순 글카드와 사진들어간 큰카드 공용으로 쓰고

보통 구현한다는 crud에서 r만 해서 간단히 보여주기 자세히보여주기 만하자

그렇지만 너무 내용이 없어보이면 추가로 이것저것 추가해야겠다

 

그래서 생각나는데로 적은 글들을 정리하자면

1. 메인 페이지 겉부분은 코드로 작성

2. 페이지에 들어가는 내용은 '카드'단위로 나누어서 각 카드별 디자인코드 작성

3. 페이지 로드시 페이지에 들어갈 카드내용을 불러와서 랜더링

4. 카드 내용은 종류, 본문 + 연결될 링크(앱 페이지~ 또는 웹링크~)

 

간단하게 할거라 디자인도 기본으로 제공해주는 위젯을 살짝 래핑하는 식으로 할거다.

일단 다음에 해야지....

 

https://wiwi-pe.tistory.com/254

 

background audio play - 1

유튜브 프리미엄 해지후 몇 달이 흘르고 흘러 밖으로 돌아다닐일이 생겼으나 나의 유튜브 뮤직은 더이상 사용할 수 없었다.그렇다고 다시 결제하자니 초반 유튜브 레드 유저에게 약속했던 변하

wiwi-pe.tistory.com

background audio play - 1 에 이어 글을 쓴다.

 

그땐 아이폰이 무음상태일때 재생이 되지 않는 문제가 있어서 고친거로 기억이 나는데

그후에 오디오 세션에 대한 글을 읽다보니 이게 앱사용중엔 항상 세션을 사용상태로 두는게 옳은건가 의문이 들어 위치를 변경했다

또 겸사겸사 AvAudioPlayer 변수를 재사용할 수 있도록 변경하려다 보니 일시정지 상태를 구분하기가 미묘해져 따로 변수로 관리해주려고 코드를 좀 분할했다

 

아래 대로 세션 사용은 플레이 함수 하나로 통일한곳이 실행될때마다 엑티브 상태를 요청하게 했고

요청이 에러 없이 된다면 바로 paused를 false로해서 일시정지중이든 처음시작이든 항상 false가 기본이 되도록 하였다

  private func play(url: URL?) {
    defer {
      contBarView?.setPlayIcon(isPlay: avPlayer.isPlaying)
      NotificationCenter.default.post(name: .changedContInfo, object: nil)
    }
    
    do {
      try AVAudioSession.sharedInstance().setActive(true)
      paused = false
      
      if avPlayer.isPlaying == false, paused {
        avPlayer.play()
        return
      }
      
      guard url != nil else {
        return
      }
      // TODO: avplayer를 재사용하려고했더니 딱히 방법이없다 AVQueuePlayer로 변경해야할거같다 그냥 처음부터 이거만 있지 왜 오디오플레이어가 따로 존재할까
      avPlayer = try AVAudioPlayer(contentsOf: url!)
      avPlayer.delegate = self
      avPlayer.prepareToPlay()
      avPlayer.play()
      
    } catch  {
      NSLog(error.localizedDescription)
    }
  }

중간에 보면 항상 player의 delegate를 이클래스에서 한다고 계속 지정해주고있는데....

매우 이상해보인다 그런데 다음곡으로 넘어가기위해

audioPlayerDidFinishPlaying 라는 AVAudioPlayerDelegate에 있는 녀석을 사용하기위해서는 avPlayer가 초기화가 되버리면 애를 사용한다는 설정이 날라가버려서 어쩔 수 없었다.

곡을 선택할때 항상 다시 생성해주는 이유는 url을 변경하는 방법이 보이지 않아서 그랬다.

 

이러한 사태를 해결 하기위해 열심히 검색을해 보았으나.... avqueuePlayer라는걸로 사용해야하는것같으나 

개인적으로만 사용하고 말 소스라 굳이 변경해야하나 싶어 매 곡마다 avPlayer를 새로 만들어주고 delegate설정도 해주는걸로 땜빵

 

아 재생 상태 변경후에 Notification으로 알려서 타 클래스여도 옵저버로 알 수 있도록한건 꼼수다

굳이 이렇게 할 필요없이 delegate를 만들어서 전달해줘도 될듯싶으나 구조를 변경하기 귀찮아서 그렇게 했다

 

유튜브 프리미엄 해지후 몇 달이 흘르고 흘러 밖으로 돌아다닐일이 생겼으나 나의 유튜브 뮤직은 더이상 사용할 수 없었다.

그렇다고 다시 결제하자니 초반 유튜브 레드 유저에게 약속했던 변하지않는 가격으로~~ 라는 광고 문구를 속인게 괴심하다 생각해 도저히 못하겠고

그냥 파일로 다운받아서 돌리자 생각했다

해서 백그라운드 재생 및 플레이 리스트가 있는게 필요한데 오랜만에 네이티브 만지는김에 필요한것만 만들어보려고 했고 깃허브에 업로드시켰다 

아래는 일부분...

 

우선 음악을 담을 폴더 내부를 읽어서 url을 저장하는 부분...

    
    do {
      let directoryContents: [String] = try fileManager.contentsOfDirectory(atPath: filePath.path)
      print(directoryContents)
      if directoryContents.isEmpty {
        urls = []
        return
      }
      
      var _urls: [URL] = []
      
      for item in directoryContents {
        let fileURL = filePath.appendingPathComponent(item)

        if fileURL.pathExtension != "mp3" {
          continue
        }
        _urls.append(fileURL)
      }
      self.urls = _urls
    } catch  {
      NSLog(error.localizedDescription)
    }

다른앱이였다면 여러가지 확장자를 지원했겠지만 난 오로지 mp3파일만을 골랐다

 

그렇게 url을 싱글톤 클래스로 관리하고 따로 컨트롤바에서 플레이해주기로 결정

아래는 url을 플레이~

  private func playUrl(url: URL) {
    do {
      avPlayer = try AVAudioPlayer(contentsOf: url)
      avPlayer?.prepareToPlay()
      avPlayer?.play()
    } catch  {
      NSLog(error.localizedDescription)
    }
  }

 

테스트로 돌려봤는데 시뮬레이터에서는 재생이 되나 왠지 기기에서는 들리지않는다.

열심히 검색해보니 오디오 세션으로 플레이백 모드로 해줘야한다고 한다

해서 appdelegate에 해당 부분을 넣어줬다

try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
try AVAudioSession.sharedInstance().setActive(true)

해당 부분의 설명은 

https://wlaxhrl.tistory.com/92 를 봤다 감사할따름

일단 위글의 일부분을 가져와봤다

  • mixWithOthers: 다른 앱의 소리와 우리 앱 소리가 함께 들릴 수 있게 하는 옵션.
  • duckOthers: mixWithOthers 를 기본으로 채택하면서 +우리 앱에서 소리가 날 때는 다른 앱 소리를 살짝 죽이는 옵션.
  • interruptSpokenAudioAndMixWithOthers: mixWithOthers를 기본으로 채택하면서 + 다른 앱에서 spokenAudio 가 출력될 때에만 interrupt 걸고 우리 앱이 소리를 독차지 (*라디오, 팟캐스트 등을 생각하면 됨)

뭔가 많으니 한번 읽어보면 좋을거같음

 

/---

읽다보니 인터럽트 부분도 생각해줘야하고

아예 잊고있었던 곡하나가 끝나면 다음곡으로 바로 재생되는것도 있고...

이 두부분은 우선 나중에 해줘야겠다

 

https://devtalk.kakao.com/t/flutter-misconfigured-error-description-invalid-android-key-hash-or-ios-bundle-id-or-web-site-url/129394

 

Flutter 카카오 로그인 misconfigured, error_description: invalid android_key_hash or ios_bundle_id or web_site_url 에러

kakao_flutter_sdk: ^1.4.2 kakao_flutter_sdk_user: ^1.4.1 사용중입니다. 디버그 모드로 카카오 로그인 기능을 구현해보고자 하는데, 계속 misconfigured, error_description: invalid android_key_hash or ios_bundle_id or web_site_url

devtalk.kakao.com

 

flutter로 앱을 배포이후 안드로이드에서 카카오톡 로그인이 안된다는 리뷰들이 달려있었다.

외주로 만든 앱이기에 요청이 있기전에 뭘 하기에 애매한 상황에서 오랜만에 관련 수정 요청이 들어온상황이라 바로 로그를 확인했으나

invalid android_key_hash or ios_bundle_id or web_site_url 라고 뜨며 카카오톡에서 정보를 받아오지 못하고있었다.

ios에서는 정상적으로 동작하는중이라 android는 또 무슨문젠가 싶은데 여차여차 살펴보니 위 링크의 문제로 보인다

아마 앱번들을 만들면서 해시키가 달라졌던모양

소셜로그인쪽은 앱 초창기에 만들어서 중간에 개발자 계정자체가 달라졌던 상황부터 이 문제가 발생했었던거같다

초기에 검사했던거고 내 개인폰은 ios 이기에 눈치채지못했었나보다

그리고  카카오톡에서 시키는대로 (https://developers.kakao.com/docs/latest/ko/android/getting-started#before-you-begin-add-key-hash-using-keytool) 했는데...

 

동작하지않는다

여기서 알려주는 방법과 내가 디버그로 돌리는 키스토어가 다른모양

빌드후에 알 수 있는 방법이 없다 찾다가 맨위에 링크를 찾었다.

 

KakaoSdk.init( ~~~ )
runApp~~

이후

...
print(await KakaoSdk.origin); // 확인

origin에 대한 주석은 아래와같다

// Origin value in KA header.
//
// Bundle id and Android keyhash for iOS and Android platform, respectively.

카카오톡은 안드로이드의 코틀린으로 작성시만 아래와같이 안내하고 flutter에서는 알려주지 않았던 그 기능과 동일해보인다.

var keyHash = Utility.getKeyHash(this)

 

뭐 그렇게 origin정보를 카카오톡 개발자 페이지의 내 애플리케이션 > 앱설정 > 플랫폼 란의 키해시를 추가해주고

잠시 티타임을 가진후 디버그 모드에서 돌아가는걸 확인

혹시 모르니 추가로 구글 콘솔에서의 SHA-1 인증서 지문을 

echo "${PRINTCERT}" | xxd -r -p | openssl base64 

위 처럼base64 처리해서 해당 키해시값도 추가해줬다.

내 테스트용 안드로이드폰은 최신갤럭시가 아니라 걱정이 되긴하는데 이거로 마무리하고 앱 심사를 맡긴후 회사에 처리완료 메시지를 보냈다.

끝!

 

 

드디어 마지막 회차.

남은 문제는 세문제

기초트레이닝 문제가 끝나면 어떤 도전과제가 생길지 기대된다

 

1. 그림 확대

10보다 작은 양의 정수 k만큼 2차원배열을 늘리는 문제

  var newPicture: [String] = []
  for i in 0 ..< picture.count {
    for _ in 0 ..< k {
      newPicture.append(picture[i])
    }
  }
  
  for line in 0 ..< newPicture.count {
    let arr = Array(newPicture[line])
    var newLine: [String.Element] = []
    for i in 0 ..< arr.count {
      for _ in 0 ..< k {
        newLine.append(arr[i])
      }
    }
    newPicture[line] = newLine.map({ String($0) }).joined()
  }
  
  return newPicture

세로로 늘리고 가로로늘리고를 크게 두가지로 나눠서 처리했는데 이걸 한번에 묶어서 처리를 하는게 인기가 좋나보다.... 다른답들 다 그렇게 처리가 되어있다

2. 정수를 나선형으로 배치하기

이중배열을 시계방향으로 돌아가면서 방문하는 문제이다.

class Position {
  var x: Int
  var y: Int
  init(x: Int, y: Int) {
    self.x = x
    self.y = y
  }
  init(_ copy: Position) {
    self.x = copy.x
    self.y = copy.y
  }
}
enum Direction {
  case right, left, down, up
}
func solution(_ n:Int) -> [[Int]] {
  var map: [[Int]] = Array(repeating: Array(repeating: 0, count: n), count: n)
  // 캐릭터 현재 x,y 좌표
  let position: Position = .init(x: 0, y: 0)
  var checkNumber: Int = 1
  
  func isMoveRight() -> Bool {
    let startX = position.x
    let startY = position.y
    let nextX = startX + 1
    // 맵 범위밖
    if nextX >= n {
      return false
    }
    // 이미 탐사한 지역
    if map[startY][nextX] > 0 {
      return false
    }
    return true
  }
  
  func isMoveLeft() -> Bool {
    let startX = position.x
    let startY = position.y
    let nextX = startX - 1
    if nextX < 0 {
      return false
    }
    if map[startY][nextX] > 0 {
      return false
    }
    return true
  }
  
  func isMoveDown() -> Bool {
    let startX = position.x
    let startY = position.y
    let nextY = startY + 1
    if nextY >= n {
      return false
    }
    if map[nextY][startX] > 0 {
      return false
    }
    return true
  }
  
  func isMoveUp() -> Bool {
    let startX = position.x
    let startY = position.y
    let nextY = startY - 1
    if nextY < 0 {
      return false
    }
    if map[nextY][startX] > 0 {
      return false
    }
    return true
  }
  
  func checkMap(position: Position) -> Bool {
    if map[position.y][position.x] > 0 {
      print("ERROR!!!!!")
      return false
    }
    map[position.y][position.x] = checkNumber
    checkNumber += 1
    return true
  }
  
  // 시작 위치
  _ = checkMap(position: position)
  
  // 순서 right down left up
  var direction: Direction = .right
  while true {
    
    let beforPosition: Position = .init(position)
    
    // 이동 가능 체크
    if !(isMoveRight() || isMoveLeft() || isMoveUp() || isMoveDown()) {
      break
    }
    
    // 이동
    switch direction {
    case .right:
      if isMoveRight() {
        position.x += 1
      } else {
        direction = .down
      }
    case .down:
      if isMoveDown() {
        position.y += 1
      } else {
        direction = .left
      }
    case .left:
      if isMoveLeft() {
        position.x -= 1
      } else {
        direction = .up
      }
    case .up:
      if isMoveUp() {
        position.y -= 1
      } else {
        direction = .right
      }
    }
    
    if beforPosition.x == position.x && beforPosition.y == position.y {
      continue
    }
    // 맵 수정
    _ = checkMap(position: position)
  }
  
  return map
}

이동하는걸 각 기능으로 나눠서 방향지정까지 해놨는데 

솔직히 너무 과했다 쓸모없다

이렇게 해두면 그야 탐색 순서가 달라져도 써먹을 부분이 많겠지만 너무 오버했다

게임을 구현한다고 생각하니 뭔가 필받아서 머리가 나빠졌나보다 

중간에 이동가능체크 라고 넣어둔부분은 

추가로 루프 탈출지점을 깜빡해서 나중에 넣은 부분이라 저런다만 저런건 미리 계산해두고 사용하는게 좋을듯하다 

아니 아예 인덱스부분만 골라서 숫자 올리는게 더 코드가 짧을지도 모르겠다

 

 

3. 정사각형으로 만들기

이중배열을 n형태의 배열로 만드는 문제, 줄수든 줄내의 요소수든 가장 큰값을 기준으로 0을 채워야한다

  var maxECount: Int = 0
  for e in arr {
    maxECount = max(maxECount, e.count)
  }
  
  let targetCount: Int = max(arr.count, maxECount)
  
  var resultArr: [[Int]] = Array(repeating: Array(repeating: 0, count: targetCount), count: targetCount)
  for line in 0 ..< resultArr.count {
    if line >= arr.count {
      continue
    }
    for i in 0 ..< resultArr[line].count {
      if i >= arr[line].count {
        continue
      }
      resultArr[line][i] = arr[line][i]
    }
  }
  
  return resultArr

이번에도 그냥 문제 내용대로 

가장 큰수를 구하고 해당 큰수대로 정사각형 배열을 만들어줬다

문제는 기존 배열을 늘리는거지만 새롭게 만들어준거에 복사해주는것으로 대체.

 

 

그렇게 기초 트레이닝 문제가 끝!

마지막일자꺼는 왜 5문제가 아닐까....

24일날 시작하여 대부분은 25일까지 완료를 짖고  흥미가 떨어져 도전과제로 풀리는것만 풀다보니

일자로만 11일이 걸렸다.

하루에 day가 두개씩 열리는거보면 사이트에서 노린 기초 트레이닝 일자는 대략 14일 정도 2주가 아니였을까?

"사이트의 노림수대로 일자가 끝난거야 " 라며 자신을 위로하며 기초트레이닝 문제를 마무리한다.

한 이틀 정도만 꽤나 열심히 진행했고 그후로는 그냥 한두문제씩 풀고있는데 끝이보이는것도 신기하다

1. 전국대회선발고사

모든 사람의 등수와 이 사람들의 참가 가능여부를 담은 배열 두개를 넣어 마지막 계산식을 반환하는문제

  var trueIndex: [Int] = []
  for( i,b) in attendance.enumerated() {
    if !b { continue }
    trueIndex.append(i)
  }
  var playerRanks: [Int] = []
  for i in trueIndex {
    playerRanks.append(rank[i])
  }
  playerRanks.sort()
  let top3PlayerRanks = playerRanks[0..<3]
  var top3PlayerIndex: [Int] = []
  for tRank in top3PlayerRanks {
    let index = rank.firstIndex { rank in
      rank == tRank
    }
    guard index != nil else {
      return -1
    }
    top3PlayerIndex.append(index!)
  }
  guard top3PlayerIndex.count == 3 else { return -2 }
  let a = top3PlayerIndex[0], b = top3PlayerIndex[1], c = top3PlayerIndex[2]
  return 10000 * a + 100 * b + c

문제를 읽으면서 순서대로 코드로 옴겼더니 코드가 엄청 길어졌다.

일단 답은 맞아서 타인의 답을 볼수 있게 되었는데 zip을 이용해 원본인덱스와 등수를 저장한후 정렬,필터할 생각은 못했구나

엄청 짧게 가능한거 보면 대단들하다

 

2. 두 수의 합

대학시절 1학년때 가산기 만들던거를 코드로 만들어봐라 하는 문제이다

  /**
   어떻게 할까.
   그냥 더해버리면 Int최대값을 넘어버린다, uint로 하면되지 않을까 했지만 안되고, longlongint로 하면되지않나? uint64도 넘어버리나보다.
   문자열을 잘라서 일부분씩 더하고 넘어가는 자리만 따로 구해서 더하는 방식으로 가야겠다.
   0. 네자리수의 문자열을 int형으로 변환후 더하고 자리수가 넘어가는 수와 분리해 넘어가는수, 합쳐진 네자리숫자를 반환하는 함수를 만든다.
   1. 문자열의 뒤 네자리씩 자른 배열로만든다
   2. 0에서 만든 함수를 돌려서 계산
   3. 각 자리수 위치에 맞게 정렬해서 반환
   - 한자리로 만들어 테스트해볼까?
   */
//  return "\(UInt64(a)! + UInt64(b)!)"
  // return (오버, 더해진값)
  enum cError: Error {
    case outOverRange
  }
  // 한자리 덧셈
  func add(over: Int, a: Int, b: Int) throws -> (Int, String) {
    if a > 9 || b > 9 || over > 9 {
      throw cError.outOverRange
    }
    
    let total: Int = over + a + b
    if total < 10 {
      return (0, String(total))
    }
    
    let totalArr = Array(String(total))
//    let over: Int = .init(String(totalArr[..<(totalArr.count - 1)]))!
    let befor = String(totalArr[..<(totalArr.count - 1)])
    let over = Int(befor)!
    
    return (over, String(totalArr.last!))
  }
  
  let aArr = Array(a).map({ String($0) }).reversed().map { $0 }
  let bArr = Array(b).map({ String($0) }).reversed().map { $0 }
  let maxIndex = max(aArr.count, bArr.count)
  
  var over: Int = 0
  var result: [String] = []
  for i in 0 ..< maxIndex {
    var aNum: Int = 0, bNum: Int = 0
    if i < aArr.count {
      let str = aArr[i]
      aNum = Int(str)!
    }
    if i < bArr.count {
      let str = bArr[i]
      bNum = Int(str)!
    }
    guard let calc = try? add(over: over, a: aNum, b: bNum) else {
      return "error"
    }
    over = calc.0
    result.append(calc.1)
  }
  if over > 0 {
    result.append("\(over)")
  }
  return result.reversed().joined()

만들다 보니 또 엄청길어졌다..........

또 "나는 4자리씩 끊어서 해야지" 라고 마음먹고 시작했으나 막상 1자리가 성공적으로 되니 귀찮아져서 포기

가산기를 만들다보니 디지털논리회로 강의때가 생각나 또 과거회상에 빠져 현재와의 비교되는 과거에 우울해졌다

그때는 이것저것 해보면서 재밌었는데 안타깝다

 

아무튼 그렇게 day25로 구성된 문제집이  day24,25 밖에 안남아서  내일로 끝이날거다 

처음 시작할때 넉넉잡아 일주일정도면 끝나겠지 했던 기초문제가 2주나 걸려버린거 생각하면 누군가 나에게 프로젝트 일정은 너가 생각한거의 두배를 말하면된다라는게 다시금 떠오른다

 

+ Recent posts