https://github.com/wiwi-git/FirstKeyboard

 

GitHub - wiwi-git/FirstKeyboard: 키보드 만들어보기...

키보드 만들어보기... Contribute to wiwi-git/FirstKeyboard development by creating an account on GitHub.

github.com

첫 직장을 그만두고 시작해놨던 키보드가 있다.

내 깃허브에서 유일하게 좋아해줬던 사람이 있어 스타를 받았던 프로젝트인데

당시에는 한글 자모 결합하는게 너무 힘들어서 시간을 끌다가 해당 프로젝트 내용 때문인지 extenstion 앱을 만드려는 스타트업에서 제안이와 계약직으로 근무하다가 해당 업체에서 정규직으로 앱 업무를 해줬었었다. 그렇게 내 재취직까지 연결된 프로젝트라 나름 애정이 있는 프로젝트다.

 

fristKeyboard 프로젝트를 만들고 이후에 좀 정리해보겠다고 뒤집어 엎어놔서 솔직히 기억이 하나도 안난다.

우선 기억을 살릴겸 이전에 작성했던 글들을 모아보려고한다.

Swift 커스텀 키보드 진행과 한글 자음/모음 조합( https://wiwi-pe.tistory.com/62)

 

Swift 커스텀 키보드 진행과 한글 자음/모음 조합

요즘 노는 시간 도중도중 공부를 하고있는데 그 공부하고있는것중 ios용 키보드를 하나 만들고있다. 이곳에 쏟아 붇는시간이 얼마되지않아 진도가 많이 느리고 처음에 제대로 공부하고 계획짜

wiwi-pe.tistory.com

 

한글 자모의 유니코드에 대한 글 https://wiwi-pe.tistory.com/92

 

한글 자모의 유니코드에 대한 글

"ᄀ" 과 "ᆨ"은 다른문자입니다. 이전에 키보드를 만들다 말았었다. https://github.com/wiwi-git/FirstKeyboard wiwi-git/FirstKeyboard 키보드 만들어보기... Contribute to wiwi-git/FirstKeyboard development by creating an account o

wiwi-pe.tistory.com

 

 

 

키보드 글을 몇개 안적었었다

큰일났다

프로젝트 개요나 기획쯤은 있을줄 알았는데 ...

최신 키보드 작성글도좀 찾아볼예정이다 

요즘은 죄다 swiftUI로 만들어나가니 특히 위젯쪽과 관련되어있지 않을가 싶어 최신 빌드에서 안돌아가는건 아닌가 걱정된다

 

 

반응형

model, view, viewmodel 이렇게 세가지로 나누어서 유지 보수 및 테스트에 적합하게 구성한 패턴을 mvvm이라 부른다

이 방식은 기존 uikit을 이용하는 swift에서 소개라 해야하나 권장해야한다해야하나 그런 cocoaMVC방식

- model, view, controller 세부분으로 나누던 mvc방식에서 View-Controller, model 두가지로 나누는 방식 

에서 swiftUI를 나오면서 뷰와 컨트롤러를 나누기 쉽게되어 많이 쓰이고있다

 

솔직히 무슨 패턴이니 하면서 하는걸 대부분 혼자 프로젝트 생성부터 배포까지 해왔던 나에겐 크게 다가오지 않는 방식이다

이런거 없이 개발해오던 내 느낌으로는 어떤 특정 스킬을 전체적인 규칙으로 특화해둔 느낌이라 뭔가 예의차리는 느낌이다

 

감상은 그만 말하고 다시 mvvm에 대해 이야기하자

ui 부분과 실 서비스 컨트롤부분 그리고 데이터 부분을 나눈방식이라 이해가 가는데 영 감이 안잡힌다 그래서 일단 해봤지만 잘 분리가 안됐을수도 있다.

 

아 참고로 이렇게 코드를 분리한 이유는 맨위에서 말했듯이 SwiftUI때문이다 이걸 공부하고싶은김에 이 프로젝트의 ui를 uikit에서 swiftui로 변경하고싶어 코드를 세부분으로 변경했다.

라곤 했지만 솔직히 기존 프로젝트에서 크게 다르지않다. 메인vc에서 뮤직플레이어로 서비스부분을 분할했다.

 

 

나눈 부분의 이야기를 하자

(일단 변명을 미리 하자면 내가 이해하기로는 model은 데이터구조, view는 ui코드, viewmodel은 비지니스코드로 이해했다)

model 은 다를 음악 파일의 데이터가 될 MusicInfo

view는 기존 NewMainVC, PlayListCell

viewmodel은 PlayList, MusicPlayer

이렇게 분리되었다.

뭐 추가된부분은 musicplayer부분만이지만

 

view부분에서 연결된 부분은

@objc func reloadButtonAction() {
    PlayList.shared.loadList()
    self.playListView.reloadData()
  }
  
  @objc func playButtonAction(){
    _ = player.play()
    setPlayIcon(isPlay: player.isPlaying)
  }
  
  @objc func preButtonAction() {
    _ = player.previousMusic()
    setPlayIcon(isPlay: player.isPlaying)
  }
  
  @objc func nextButtonAction() {
    _ = player.nextMusic()
    setPlayIcon(isPlay: player.isPlaying)
  }

기존 같이 있던 플레이어 기능을 전부 MusicPlayer.swift 파일로 빼서 이렇게 한단계 거치게됐다

참고로 찾아보니 테이블뷰에 Datasource 부분도 따로 빼서 연결해둔사람이 있었는데

cellForRowAt 부분에 셀의 ui변경코드를 넣어둬서 따로빼면 오히려 더 알아보기 힘들어질것같아서 냅뒀다

플레이어부분은 아래처럼 한번 감싸서 한번 생성되어 사용되도록해서 여러 부분에서 불러도 지장없이 해놨다

class MusicPlayer : NSObject {
  static var shared: MusicPlayer = .init()
  
  var avPlayer: AVAudioPlayer = .init()
  var playList: PlayList = .shared
  var paused: Bool = false
  var isPlaying: Bool {
    get {
      return self.avPlayer.isPlaying
    }
  }
  
  private override init() {
    super.init()
    initPlayer()
    remoteCommandCenterSetting()
  }
  
  private func initPlayer() {
    let audioSession = AVAudioSession.sharedInstance()
    do {
      /// 이유를 모르겠다 옵션즈에 값을 넣으면 락스크린에 컨트롤바가 안생김....
//      try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
      try audioSession.setCategory(.playback, mode: .default, options: [])
    } catch let error as NSError {
      print("audioSession 설정 오류 : \(error.localizedDescription)")
    }
  }
  
  ~~~

 

전체 부분을 보고싶으신분은 github을 봐달라.

https://github.com/wiwi-git/proj_ypl

 

GitHub - wiwi-git/proj_ypl: ios에서 간단하게 쓸 백그라운드 플레이어가 필요하다

ios에서 간단하게 쓸 백그라운드 플레이어가 필요하다. Contribute to wiwi-git/proj_ypl development by creating an account on GitHub.

github.com

 

이거로 3편에서 계획한 5단계에서 큐플레이어로 대체하는것을 제외하고 끝났다

막상하려니 고쳐야하는 부분이 많아보여서 그냥 이부분은 넘어가도록했다 기능적인 문제에 별거없었고...

 

다음은 swiftUI로 변경해보는김에 디자인을 좀 꾸며볼 생각이다

솔직히 너무 없어보여서;;;;

원래는 나 혼자 그냥 공부겸 만들거라 기능만 있으면 되겠지 싶었는데 이렇게 블로그에 글을쓰다보니 조금 부끄러워졌다

마침 디자인해주는 공짜ai서비스에 대해 듣기도했고 이걸 이용해 볼 생각이다

 

 

반응형

 

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

유튜브영상을 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. 각 이벤트 연결 및 테스트

 

 

 

 

 

- 계획만 짜두고 한동안 잡지않다가 다시 시작

기존 코드들을 죄다 old폴더에 넣고 새로운 vc에서 통합하여 사용하여 문제되는 라인을 찾았다

전부 다 통합하여 재 구축해도 되지않아서 당황하다가 얼떨결에 찾은거라 이유를 모르겠다 누가좀 알려줬으면 좋겠네

문제되는 라인은 바로

//      try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
      try audioSession.setCategory(.playback, mode: .default, options: [])

카테고리 설정하는 부분

타 앱 소리가 나도 같이 들리도록 옵션을 추가해줬었는데

이 설정을 하다가 깜빡 옵션을 안넣어줬었는데 락스크린에 컨트롤바가 생겼다.

너무 어처구니없는 상황에 웃음만 나온다

 

일단 이 포스트가 아마도 락스크린의 컨트롤바를 설정하는거라 해당 부분 소스를 올리자면

  func remoteCommandCenterSetting() {
    // remote control event 받기 시작
    UIApplication.shared.beginReceivingRemoteControlEvents()
    let center = MPRemoteCommandCenter.shared()
    center.playCommand.removeTarget(nil)
    center.pauseCommand.removeTarget(nil)
    
    // 제어 센터 재생버튼 누르면 발생할 이벤트를 정의합니다.
    center.playCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
      self.avPlayer.play()
      MPNowPlayingInfoCenter.default()
        .nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: self.avPlayer.currentTime)
      // 재생 할 땐 now playing item의 rate를 1로 설정하여 시간이 흐르도록 합니다.
      MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1
      return .success
    }
    
    // 제어 센터 pause 버튼 누르면 발생할 이벤트를 정의합니다.
    center.pauseCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
      self.avPlayer.pause()
      MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: self.avPlayer.currentTime)
      // 일시정지 할 땐 now playing item의 rate를 0으로 설정하여 시간이 흐르지 않도록 합니다.
      MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
      return .success
    }
    
    center.nextTrackCommand.addTarget { event in
      let nextResult = self.nextAction()
      if !nextResult {
        return .noSuchContent
      }
      
      return .success
    }
    
    center.previousTrackCommand.addTarget { event in
      _ = self.previousAction()
      
      return .success
    }
    
    center.playCommand.isEnabled = true
    center.pauseCommand.isEnabled = true
  }
  
  func remoteCommandInfoCenterSetting() {
    let center = MPNowPlayingInfoCenter.default()
    var nowPlayingInfo = center.nowPlayingInfo ?? [String: Any]()
    
    guard let currentMusic: MusicInfo = playList.currentMusic else {
      print("리스트에 곡이 없음")
      return
    }
    
    nowPlayingInfo[MPMediaItemPropertyTitle] = currentMusic.title
    nowPlayingInfo[MPMediaItemPropertyArtist] = currentMusic.artist
    
    if let albumCoverPage = currentMusic.artworkImage {
      nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: albumCoverPage.size, requestHandler: { size in
        return albumCoverPage
      })
    }
    
    // 콘텐츠 총 길이
    nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = self.avPlayer.duration
    // 콘텐츠 재생 시간에 따른 progressBar 초기화
    nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1
    // 콘텐츠 현재 재생시간
    nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: self.avPlayer.currentTime)
    
    center.nowPlayingInfo = nowPlayingInfo
  }

이곳저곳에서 퍼오면서 테스트하고 기우고 이래서 출처가 기억이 안남.

 

전체 소스는 내 깃에 있다.

https://github.com/wiwi-git/proj_ypl

 

GitHub - wiwi-git/proj_ypl: ios에서 간단하게 쓸 백그라운드 플레이어가 필요하다

ios에서 간단하게 쓸 백그라운드 플레이어가 필요하다. Contribute to wiwi-git/proj_ypl development by creating an account on GitHub.

github.com

 

todo로 잡아뒀던 기능들이 몇개 있는데 이건 그냥 포기하고 이후에 다시 글을 쓰게된다면 통합시켜놨던걸 mvvm으로 분리시키고

uikit으로 작성된 ui를 swiftui로 변경해볼까 한다

swiftui는 그냥 몇번 훑어보고 말아서 이참에 공부해야지

반응형

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 걸고 우리 앱이 소리를 독차지 (*라디오, 팟캐스트 등을 생각하면 됨)

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

 

/---

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

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

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

 

반응형

드디어 마지막 회차.

남은 문제는 세문제

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

 

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주나 걸려버린거 생각하면 누군가 나에게 프로젝트 일정은 너가 생각한거의 두배를 말하면된다라는게 다시금 떠오른다

 

반응형

비가 계속해서 내려서 눅눅함이 사라지지 않는 밤, 사라지지 않는 빗소리 때문인지 머리가 아파서 잊고있었다

자기전에 생각해내어서 다행.

오늘치는 19,20일치인데 20일은 이미 끝나있고 19일치에 두문제가 남았다

어서 두문제만 끝내고 게임이나 해야지

 

1. 배열 만들기 6

배열 순환해서 조건에 맞는 요소만 뽑아내는 문제

  // i의 초기값을 0으로 설정하고 i가 arr의 길이보다 작으면 다음을 반복
  var i = 0
  var stk: [Int] = []
  
  while i < arr.count {
    if stk.isEmpty {
      stk.append(arr[i])
      i += 1
      continue
    }
    
    if stk.last! == arr[i] {
      _ = stk.popLast()
      i += 1
      continue
    }
    
    stk.append(arr[i])
    i += 1
  }
  
  guard !stk.isEmpty else { return [-1]}
          
  return stk
 

눅눅해서 찝찝하고 머리가 지끈지끈 거려서 아무 생각없이 그냥 문제 그대로를 코드로 바꿨더니 하는일에 비해 좀 길어보인다.

그냥 forEach로 돌리거나 filter로 돌려도 될거같기도하고?

 

2. 무작위로 K개의 수 뽑기

제목은 무작위지만 무작위가 아니라 뭐가 랜덤으로 나왔는지 주어진다. 랜덤도 아니였겠지만...

//  var copyArr: [Int] = Set(arr).map {$0}
  var copyArr: [Int] = []
  for num in arr {
    if copyArr.contains(num) {
      continue
    }
    copyArr.append(num)
    if copyArr.count >= k {
      break
    }
  }
  
  while copyArr.count < k {
    copyArr.append(-1)
  }
  
  return copyArr[..<k].map { $0 }

처음엔 그냥 Set으로 중복제거한후 k개수만큼 보내면 되겠다 싶었는데

이게 처음 주어진 배열에서 순서가 변경되면 아웃처리시켜버린다 "저장된 순서대로 주어질 예정이라고 했을 때," 라는 정의를 또 제대로 안읽고 넘겨버려서 그만 또 틀렸다

레벨0문제인데 뭐이리 자주 틀리는지

아무튼 그렇게 오늘치도 끝!

이제 남은 문제는 6문제!

물론 미리 문제를 풀지 않을거기에 이번주내내 할거같다

반응형

+ Recent posts