배열에서 특정 항목을 선택해 삭제하고자 할 때
extension Canvas {
mutating func deleteSelection() {
for i in 0..<shapes.count {
if shapes[i].isSelected {
shapes.remove(at: i)
}
}
}
}
extension Canvas {
mutating func deleteSelection() {
var i = 0
while i < shapes.count {
if shapes[i].isSelected {
shapes.remove(at: i)
}
i += 1
}
}
}
extension Canvas {
mutating func deleteSelection() {
var i = 0
while i < shapes.count {
if shapes[i].isSelected {
shapes.remove(at: i)
}
else {
i += 1
}
}
}
}
extension Canvas {
mutating func deleteSelection() {
for i in (0..<shapes.count).reversed() {
if shapes[i].isSelected {
shapes.remove(at: i)
}
}
}
}
요소를 제거하려면 배열 길이에 비례하는 단계가 필요 => O(n)
- 각 요소에 대해 한 번씩 n단계를 수행
- n 개의 요소를 선택할 수 있음
=> 총 단계 수는 n 제곱의 비례
어떻게 해결할까? -> 알고리즘을 사용하자
extension Canvas {
mutating func deleteSelection() {
shapes.removeAll(where: { $0.isSelected })
}
}
removeAll()
- O(n)교훈 = Swift 표준 라이브러리 내용에 익숙해지자
- 문서화된 내용과 의미있는 알고리즘 모음이 포함됨
- 공식 문서는 라이브러리를 효과적으로 사용하기 위해 알아야 할 것들을 알려줌
=> 알고리즘을 적용한 쪽이 훨씬 명백함을 알 수 있음
Shawn Perin이 제한한 코드의 지침
- Loop를 작성할 때마다 알고리즘 호출로 대체해라
- 알고리즘을 찾을 수 없다면 직접 만들어 대체해라
도형들을 그룹화하고 앞으로 가져오는 메서드
이외에도 배열을 반복하면서 삽입 및 삭제 동작을 하기 때문에 이와 비슷한 역할의 메서드 모두 O(n^2)을 가짐
무슨 동작을 하나? "선택한 도형을 상대적 순서를 유지하면서 앞으로 이동하기"
=>stablePartition(isSuffixElement: (Shape) -> Bool)
=> nlogn
O(nlogn)
선택한 도형 중 가장 앞쪽에서 하나 앞으로 이동한 후 모두 가져오기
stablePartition
을 적용하면 동일한 작업을 할 수 있음하지만 Slice는 배열 타입이 아님
어떻게 테스트할건데? 일반적으로 만들어라!
extension Canvas
-->
extension Array where Element == Shape
Shape를 가지는 모든 배열(Array)에서 가능하도록 변경
extension Array where Element == Shape {
mutating func bringForward() {
if let i = firstIndex(where: { $0.isSelected }) {
if i == 0 { return }
let predecessor = i - 1
self[predecessor...].stablePartition(isSuffixElement: { !$0.isSelected })
}
}
}
--> where 절 제거
extension Array {
mutating func bringForward(elementsSatisfying predicate: (Element) -> Bool) {
if let i = firstIndex(where: predicate) {
if i == 0 { return }
let predecessor = i - 1
self[predecessor...].stablePartition(isSuffixElement: { !predicate($0) })
}
}
}
$0.isSelected
에 해당하는 조건을 매개변수로 받아 Shape와 종속성을 떼어낼 수 잇음 -> 모든 Array에서 가능!
Array이여만 해? 아.. 그것도 아님
가변 컬렉션으로 변경 -> 하지만 Index가 Int가 아니라서 문제
-> where Index == Int
? 🚫
startIndex
로 변경indexBeforeFirst
알고리즘 적용extension MutableCollection {
mutating func bringForward(elementsSatisfying predicate: (Element) -> Bool) {
if let predecessor = indexBeforeFirst(where: predicate) {
self[predecessor...].stablePartition(isSuffixElement: { !predicate($0) })
}
}
}
extension Collection {
func indexBeforeFirst(where predicate: (Element) -> Bool) -> Index? {
return indices.first {
let successor = index(after: $0)
return successor != endIndex && predicate(self[successor])
}
}
}
➡️ Shape, Selections, Array, Int와 관련 없는 디테일을 제거하면 문제의 핵심만 전달되어 코드가 명확해짐
문서화의 역할
- 우리는 추상화의 탑을 짓고 있다
- 우리가 아래 레이어를 지속적으로 검사하지 않고 만들 수 있는 이유는 우리가 만드는 부분이 문서화되어 있기 때문이다
- 우리는 개발자로서 하드웨어까지 뻗어있는 탑 꼭대기에서 일하고 있다
컬렉션 수를 가져오고 분할 정복 전략을 사용하는 메서드로 전달
파티션 지점이 컬렉션 시작인지 끝인지만 파악하면 됨
왼쪽과 오른쪽으로 분할 후 작업
중앙 섹션은 교환해야 함 -> ratate 알고리즘
Shape은 도형 목록에서 드래그를 구현하며 복잡하고 버그가 많은 작업이었음
- 임시 버퍼를 할당
- 삽입점 앞의 도형을 반복하여 선택한 도형을 추출하고 삽입점 조정
- 않고 나머지 도형을 반복하여 선택한 도형을 추출
- 삽입점으로 선택한 도형을 다시 삽입
코드를 두 줄만에 끝내게 해줌 = 재사용 가능하고 효율적인 문서화 범용 알고리즘을 얻음
애플리케이션 도메인에 세부 정보를 파악하고 기반적으로 수행하는 작업을 확인하고 재사용 가능한 일반 코드를 추출하는 기술
=> 타입 및 응용 프로그램 아키텍처의 권리와 의미를 가진 일급 시민으로 계산을 처리해라
식별하고, 이름을 지정하고, 단위 테스트하고, 의미 체계와 성능을 문서화
“If you want to improve the code quality in your organization, replace all of your coding guidelines with one goal:
No Raw Loops”Sean Parent, C++ Seasoning