-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from boostcamp-2020/feature/kmeans/initialize-…
…centroids KMeans 중심값 생성 함수 추가 (BallCut)
- Loading branch information
Showing
12 changed files
with
367 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
...C-Map/InteractiveClusteringMap/Clustering/KMeans/Centroids/BallCutCentroidGenerator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// | ||
// BallCutCentroidGenerator.swift | ||
// InteractiveClusteringMap | ||
// | ||
// Created by Seungeon Kim on 2020/11/28. | ||
// | ||
|
||
import Foundation | ||
|
||
final class BallCutCentroidGenerator: CentroidCreatable { | ||
|
||
private let coverage: Double | ||
private let k: Int | ||
private let points: [Coordinate] | ||
private let mutiplier: Int = 5 | ||
|
||
init(k: Int, coverage: Double, coordinates: [Coordinate]) { | ||
self.k = k | ||
self.coverage = coverage | ||
self.points = coordinates | ||
} | ||
|
||
func centroids() -> [Coordinate] { | ||
return recursiveClassify(k: k, coordinates: points) | ||
} | ||
|
||
private func recursiveClassify(k: Int, coordinates: [Coordinate]) -> [Coordinate] { | ||
var coordinates = coordinates | ||
let container = createContainer(k: k, coordinates: coordinates) | ||
var centroids: [Coordinate] = pickCentroids(k: k, distance: coverage, container: container) | ||
|
||
guard centroids.count == k else { return centroids } | ||
|
||
for coordinate in container { | ||
guard let index = coordinates.firstIndex(of: coordinate) else { | ||
return [] | ||
} | ||
coordinates.remove(at: index) | ||
} | ||
|
||
centroids += recursiveClassify(k: k - centroids.count + 1, coordinates: coordinates) | ||
return centroids | ||
} | ||
|
||
private func createContainer(k: Int, coordinates: [Coordinate]) -> [Coordinate] { | ||
var coordinates = coordinates | ||
var container: [Coordinate] = [] | ||
let count = k * mutiplier | ||
|
||
for _ in 0..<count { | ||
guard let coordinate = coordinates.randomElement(), | ||
let index = coordinates.firstIndex(of: coordinate) | ||
else { | ||
return [] | ||
} | ||
container.append(coordinate) | ||
coordinates.remove(at: index) | ||
} | ||
|
||
return container | ||
} | ||
|
||
private func pickCentroids(k: Int, distance: Double, container: [Coordinate]) -> [Coordinate] { | ||
var centroids: [Coordinate] = [] | ||
var container = container | ||
|
||
for _ in 0..<k { | ||
guard let centroid = container.randomElement() else { | ||
return centroids | ||
} | ||
|
||
container = container.filter { centroid.distanceTo($0) >= distance } | ||
centroids.append(centroid) | ||
} | ||
|
||
return centroids | ||
} | ||
|
||
} |
32 changes: 32 additions & 0 deletions
32
...-C-Map/InteractiveClusteringMap/Clustering/KMeans/Centroids/RandomCentroidGenerator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// RandomCetroidGenerator.swift | ||
// InteractiveClusteringMap | ||
// | ||
// Created by Seungeon Kim on 2020/11/28. | ||
// | ||
|
||
import Foundation | ||
|
||
final class RandomCentroidGenerator: CentroidCreatable { | ||
|
||
private let k: Int | ||
private let lats: ClosedRange<Double> | ||
private let lngs: ClosedRange<Double> | ||
|
||
init(k: Int, rangeOfLat: ClosedRange<Double>, rangeOfLng: ClosedRange<Double>) { | ||
self.k = k | ||
self.lats = rangeOfLat | ||
self.lngs = rangeOfLng | ||
} | ||
|
||
func centroids() -> [Coordinate] { | ||
var centroids: [Coordinate] = [] | ||
for _ in 0..<k { | ||
let cluster = Coordinate.randomGenerate(rangeOfLat: lats, rangeOfLng: lngs) | ||
centroids.append(cluster) | ||
} | ||
|
||
return centroids | ||
} | ||
|
||
} |
62 changes: 62 additions & 0 deletions
62
...-C-Map/InteractiveClusteringMap/Clustering/KMeans/Centroids/ScreenCentroidGenerator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// | ||
// ScreenCentroidGenerator.swift | ||
// InteractiveClusteringMap | ||
// | ||
// Created by Seungeon Kim on 2020/11/28. | ||
// | ||
|
||
import Foundation | ||
|
||
final class ScreenCentroidGenerator: CentroidCreatable { | ||
|
||
private let topLeft: Coordinate | ||
private let bottomRight: Coordinate | ||
private let k: Int | ||
|
||
init(k: Int, topLeft: Coordinate, bottomRight: Coordinate) { | ||
self.k = k | ||
self.topLeft = topLeft | ||
self.bottomRight = bottomRight | ||
} | ||
|
||
func centroids() -> [Coordinate] { | ||
let center = (topLeft + bottomRight) / 2.0 | ||
let boundary = Coordinate(x: bottomRight.x - center.x, y: topLeft.y - center.y) | ||
let pivot = center.findTheta(vertex: Coordinate(x: bottomRight.x, y: topLeft.y)) | ||
|
||
let increase = Degree.turn / Double(k) | ||
var angle = increase | ||
var coords: [Coordinate] = [] | ||
|
||
for _ in 0..<k { | ||
let quadrant = Quadrant.findQuadrant(angle: angle) | ||
let modulus = angle.truncatingRemainder(dividingBy: Degree.right) | ||
|
||
if modulus == 0 { | ||
let distance = boundary / 2 | ||
let coord = quadrant.degree(center: center, boundary: distance) | ||
coords.append(coord) | ||
} else { | ||
let theta = quadrant.theta(angle: modulus) | ||
let radian = theta * .pi / Degree.straight | ||
var x: Double | ||
var y: Double | ||
|
||
if theta > pivot { | ||
y = boundary.y | ||
x = y / tan(radian) | ||
} else { | ||
x = boundary.x | ||
y = tan(radian) * x | ||
} | ||
let distance = Coordinate(x: x, y: y) / 2 | ||
let coord = quadrant.convertToCoordinate(center: center, distance: distance) | ||
coords.append(coord) | ||
} | ||
angle += increase | ||
} | ||
|
||
return coords | ||
} | ||
|
||
} |
Oops, something went wrong.