Skip to content

Commit

Permalink
Merge pull request #37 from boostcamp-2020/feature/kmeans/initialize-…
Browse files Browse the repository at this point in the history
…centroids

KMeans 중심값 생성 함수 추가 (BallCut)
  • Loading branch information
Seungeon-Kim committed Nov 28, 2020
2 parents 65a7c4d + 15fd1b4 commit b62349f
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 90 deletions.
46 changes: 41 additions & 5 deletions Project17-C-Map/InteractiveClusteringMap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
FAC9FD05256E2107009FBB41 /* KCoefficient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9FD04256E2107009FBB41 /* KCoefficient.swift */; };
FAC9FD0A256E23B1009FBB41 /* KDefineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9FD09256E23B1009FBB41 /* KDefineTests.swift */; };
FAC9FD0E256E2495009FBB41 /* KCoefficient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC9FD04256E2107009FBB41 /* KCoefficient.swift */; };
FC2A9D6D25723C6100D524B0 /* RandomCentroidGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2A9D6C25723C6100D524B0 /* RandomCentroidGenerator.swift */; };
FC2A9D7525723C9800D524B0 /* ScreenCentroidGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2A9D7425723C9800D524B0 /* ScreenCentroidGenerator.swift */; };
FC2A9D7D25723D1100D524B0 /* BallCutCentroidGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2A9D7C25723D1100D524B0 /* BallCutCentroidGenerator.swift */; };
FCE422B02570194B0017416F /* Coordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422AF2570194B0017416F /* Coordinate.swift */; };
FCE422B8257019650017416F /* Cluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422B7257019650017416F /* Cluster.swift */; };
FCE422BD25701A200017416F /* Quadrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422BC25701A200017416F /* Quadrant.swift */; };
Expand Down Expand Up @@ -127,6 +130,9 @@
FA81D67725690F360023E123 /* CoreDataStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = "<group>"; };
FAC9FD04256E2107009FBB41 /* KCoefficient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KCoefficient.swift; sourceTree = "<group>"; };
FAC9FD09256E23B1009FBB41 /* KDefineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KDefineTests.swift; sourceTree = "<group>"; };
FC2A9D6C25723C6100D524B0 /* RandomCentroidGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomCentroidGenerator.swift; sourceTree = "<group>"; };
FC2A9D7425723C9800D524B0 /* ScreenCentroidGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenCentroidGenerator.swift; sourceTree = "<group>"; };
FC2A9D7C25723D1100D524B0 /* BallCutCentroidGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BallCutCentroidGenerator.swift; sourceTree = "<group>"; };
FCE422AF2570194B0017416F /* Coordinate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinate.swift; sourceTree = "<group>"; };
FCE422B7257019650017416F /* Cluster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cluster.swift; sourceTree = "<group>"; };
FCE422BC25701A200017416F /* Quadrant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quadrant.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -237,13 +243,11 @@
37EB73412562592900AC44D6 /* InteractiveClusteringMapTests */ = {
isa = PBXGroup;
children = (
FC9F5C30257094710029A53B /* KMeans */,
46A12E3E256CEDD3007BDF3E /* QuadTreeTests */,
37EB73422562592900AC44D6 /* InteractiveClusteringMapTests.swift */,
37279B02256EBC5200EA689A /* KMeansTests.swift */,
3713D3DB25694C3E00E703EC /* CoreDataStackTests.swift */,
FAC9FD09256E23B1009FBB41 /* KDefineTests.swift */,
37EB73442562592900AC44D6 /* Info.plist */,
FCE422C625701B8A0017416F /* KMeansCentroidsTest.swift */,
);
path = InteractiveClusteringMapTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -314,6 +318,35 @@
path = POI;
sourceTree = "<group>";
};
FC2A9D5E25721F5C00D524B0 /* Metrics */ = {
isa = PBXGroup;
children = (
FCE422BC25701A200017416F /* Quadrant.swift */,
FCE422C125701A390017416F /* Degree.swift */,
);
path = Metrics;
sourceTree = "<group>";
};
FC2A9D682572371C00D524B0 /* Centroids */ = {
isa = PBXGroup;
children = (
FC2A9D6C25723C6100D524B0 /* RandomCentroidGenerator.swift */,
FC2A9D7425723C9800D524B0 /* ScreenCentroidGenerator.swift */,
FC2A9D7C25723D1100D524B0 /* BallCutCentroidGenerator.swift */,
);
path = Centroids;
sourceTree = "<group>";
};
FC9F5C30257094710029A53B /* KMeans */ = {
isa = PBXGroup;
children = (
FCE422C625701B8A0017416F /* KMeansCentroidsTest.swift */,
FAC9FD09256E23B1009FBB41 /* KDefineTests.swift */,
37279B02256EBC5200EA689A /* KMeansTests.swift */,
);
path = KMeans;
sourceTree = "<group>";
};
FCE422AD257018E30017416F /* Clustering */ = {
isa = PBXGroup;
children = (
Expand All @@ -325,10 +358,10 @@
FCE422AE257018EA0017416F /* KMeans */ = {
isa = PBXGroup;
children = (
FC2A9D5E25721F5C00D524B0 /* Metrics */,
FC2A9D682572371C00D524B0 /* Centroids */,
FAC9FD04256E2107009FBB41 /* KCoefficient.swift */,
379DC4C5256E1EC60044DA6C /* KMeans.swift */,
FCE422BC25701A200017416F /* Quadrant.swift */,
FCE422C125701A390017416F /* Degree.swift */,
);
path = KMeans;
sourceTree = "<group>";
Expand Down Expand Up @@ -538,10 +571,12 @@
379DC4C6256E1EC60044DA6C /* KMeans.swift in Sources */,
3753DCF0256B4C5B006D26A9 /* POIService.swift in Sources */,
FA81D67825690F360023E123 /* CoreDataStack.swift in Sources */,
FC2A9D7D25723D1100D524B0 /* BallCutCentroidGenerator.swift in Sources */,
FCE422B02570194B0017416F /* Coordinate.swift in Sources */,
37EB732D2562592700AC44D6 /* MapViewController.swift in Sources */,
37EB73332562592700AC44D6 /* Model.xcdatamodeld in Sources */,
FCE422B8257019650017416F /* Cluster.swift in Sources */,
FC2A9D7525723C9800D524B0 /* ScreenCentroidGenerator.swift in Sources */,
192D34AD256E355500861703 /* QuadTree.swift in Sources */,
46A12E1B256CD099007BDF3E /* Extension.swift in Sources */,
FCE422BD25701A200017416F /* Quadrant.swift in Sources */,
Expand All @@ -552,6 +587,7 @@
FCE422C225701A390017416F /* Degree.swift in Sources */,
372216C12569325600231245 /* DataManagable.swift in Sources */,
37352CE025695EE000CAFDFD /* Place.swift in Sources */,
FC2A9D6D25723C6100D524B0 /* RandomCentroidGenerator.swift in Sources */,
37EB732B2562592700AC44D6 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
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
}

}
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
}

}
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
}

}

0 comments on commit b62349f

Please sign in to comment.