Skip to content

Commit

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

KMeans 중심값 생성 함수 추가 및 코드 리팩토링
  • Loading branch information
donggeonoh committed Nov 27, 2020
2 parents 1b07182 + 3782379 commit 6439caf
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 90 deletions.
67 changes: 51 additions & 16 deletions Project17-C-Map/InteractiveClusteringMap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,13 @@
372216C12569325600231245 /* DataManagable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372216C02569325600231245 /* DataManagable.swift */; };
372216CD256943D300231245 /* POI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372216CC256943D300231245 /* POI.swift */; };
37279B03256EBC5200EA689A /* KMeansTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37279B02256EBC5200EA689A /* KMeansTests.swift */; };
37279B07256EBC6300EA689A /* BoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A12E22256CD4DB007BDF3E /* BoundingBox.swift */; };
37279B0B256EBC6E00EA689A /* KMeansClustering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC4C5256E1EC60044DA6C /* KMeansClustering.swift */; };
37311088256F641400350D55 /* MockPOIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37311087256F641400350D55 /* MockPOIService.swift */; };
37279B0B256EBC6E00EA689A /* KMeans.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC4C5256E1EC60044DA6C /* KMeans.swift */; };
3731108C256F641F00350D55 /* POIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3753DCEF256B4C5B006D26A9 /* POIService.swift */; };
37352CD125695B8A00CAFDFD /* restuarant-list-for-test.json in Resources */ = {isa = PBXBuildFile; fileRef = 37352CD025695B8A00CAFDFD /* restuarant-list-for-test.json */; };
37352CDB25695E7400CAFDFD /* JSONReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37352CDA25695E7400CAFDFD /* JSONReader.swift */; };
37352CE025695EE000CAFDFD /* Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37352CDF25695EE000CAFDFD /* Place.swift */; };
3753DCF0256B4C5B006D26A9 /* POIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3753DCEF256B4C5B006D26A9 /* POIService.swift */; };
376E43BA256F9811002D3EE9 /* MockPOIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37311087256F641400350D55 /* MockPOIService.swift */; };
379DC4C6256E1EC60044DA6C /* KMeansClustering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC4C5256E1EC60044DA6C /* KMeansClustering.swift */; };
379DC4C6256E1EC60044DA6C /* KMeans.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC4C5256E1EC60044DA6C /* KMeans.swift */; };
37EB73292562592700AC44D6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EB73282562592700AC44D6 /* AppDelegate.swift */; };
37EB732B2562592700AC44D6 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EB732A2562592700AC44D6 /* SceneDelegate.swift */; };
37EB732D2562592700AC44D6 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EB732C2562592700AC44D6 /* MapViewController.swift */; };
Expand All @@ -52,10 +49,18 @@
46B444582568F095001B79D4 /* InteractiveMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B444572568F095001B79D4 /* InteractiveMapView.swift */; };
C9FF187A077822AA8BD00410 /* Pods_InteractiveClusteringMap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A551109FA7E11823B10D084F /* Pods_InteractiveClusteringMap.framework */; };
FA81D67825690F360023E123 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA81D67725690F360023E123 /* CoreDataStack.swift */; };
FA84D560256F796700ECDD44 /* BoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A12E22256CD4DB007BDF3E /* BoundingBox.swift */; };
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 */; };
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 */; };
FCE422C225701A390017416F /* Degree.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422C125701A390017416F /* Degree.swift */; };
FCE422C725701B8A0017416F /* KMeansCentroidsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422C625701B8A0017416F /* KMeansCentroidsTest.swift */; };
FCE422CE25701C760017416F /* Coordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422AF2570194B0017416F /* Coordinate.swift */; };
FCE422D225701C790017416F /* Cluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422B7257019650017416F /* Cluster.swift */; };
FCE422D625701CBD0017416F /* Quadrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422BC25701A200017416F /* Quadrant.swift */; };
FCE422DA25701CC00017416F /* Degree.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE422C125701A390017416F /* Degree.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -84,12 +89,11 @@
372216C02569325600231245 /* DataManagable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManagable.swift; sourceTree = "<group>"; };
372216CC256943D300231245 /* POI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POI.swift; sourceTree = "<group>"; };
37279B02256EBC5200EA689A /* KMeansTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMeansTests.swift; sourceTree = "<group>"; };
37311087256F641400350D55 /* MockPOIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPOIService.swift; sourceTree = "<group>"; };
37352CD025695B8A00CAFDFD /* restuarant-list-for-test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "restuarant-list-for-test.json"; sourceTree = "<group>"; };
37352CDA25695E7400CAFDFD /* JSONReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONReader.swift; sourceTree = "<group>"; };
37352CDF25695EE000CAFDFD /* Place.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Place.swift; sourceTree = "<group>"; };
3753DCEF256B4C5B006D26A9 /* POIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POIService.swift; sourceTree = "<group>"; };
379DC4C5256E1EC60044DA6C /* KMeansClustering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMeansClustering.swift; sourceTree = "<group>"; };
379DC4C5256E1EC60044DA6C /* KMeans.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMeans.swift; sourceTree = "<group>"; };
37EB73252562592700AC44D6 /* InteractiveClusteringMap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InteractiveClusteringMap.app; sourceTree = BUILT_PRODUCTS_DIR; };
37EB73282562592700AC44D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
37EB732A2562592700AC44D6 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand All @@ -116,6 +120,11 @@
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>"; };
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>"; };
FCE422C125701A390017416F /* Degree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Degree.swift; sourceTree = "<group>"; };
FCE422C625701B8A0017416F /* KMeansCentroidsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMeansCentroidsTest.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -149,7 +158,8 @@
children = (
37352CDF25695EE000CAFDFD /* Place.swift */,
372216CC256943D300231245 /* POI.swift */,
379DC4C5256E1EC60044DA6C /* KMeansClustering.swift */,
FCE422AF2570194B0017416F /* Coordinate.swift */,
FCE422B7257019650017416F /* Cluster.swift */,
);
path = VO;
sourceTree = "<group>";
Expand Down Expand Up @@ -196,13 +206,13 @@
37EB73272562592700AC44D6 /* InteractiveClusteringMap */ = {
isa = PBXGroup;
children = (
FCE422AD257018E30017416F /* Clustering */,
37352CD925695E4D00CAFDFD /* Util */,
37352CCF25695AA500CAFDFD /* Resource */,
372216CB256943C800231245 /* VO */,
FA81D66E2568FEC50023E123 /* CoreData */,
37EB73282562592700AC44D6 /* AppDelegate.swift */,
37EB732A2562592700AC44D6 /* SceneDelegate.swift */,
FAC9FD04256E2107009FBB41 /* KCoefficient.swift */,
37EB732C2562592700AC44D6 /* MapViewController.swift */,
4623BD1225694A1400940B12 /* MapController.swift */,
46153145256BDDD700BAE674 /* QuadTree */,
Expand All @@ -222,10 +232,10 @@
46A12E3E256CEDD3007BDF3E /* QuadTreeTests */,
37EB73422562592900AC44D6 /* InteractiveClusteringMapTests.swift */,
37279B02256EBC5200EA689A /* KMeansTests.swift */,
37311087256F641400350D55 /* MockPOIService.swift */,
3713D3DB25694C3E00E703EC /* CoreDataStackTests.swift */,
FAC9FD09256E23B1009FBB41 /* KDefineTests.swift */,
37EB73442562592900AC44D6 /* Info.plist */,
FCE422C625701B8A0017416F /* KMeansCentroidsTest.swift */,
);
path = InteractiveClusteringMapTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -294,6 +304,25 @@
path = POI;
sourceTree = "<group>";
};
FCE422AD257018E30017416F /* Clustering */ = {
isa = PBXGroup;
children = (
FCE422AE257018EA0017416F /* KMeans */,
);
path = Clustering;
sourceTree = "<group>";
};
FCE422AE257018EA0017416F /* KMeans */ = {
isa = PBXGroup;
children = (
FAC9FD04256E2107009FBB41 /* KCoefficient.swift */,
379DC4C5256E1EC60044DA6C /* KMeans.swift */,
FCE422BC25701A200017416F /* Quadrant.swift */,
FCE422C125701A390017416F /* Degree.swift */,
);
path = KMeans;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -495,18 +524,21 @@
37EB73292562592700AC44D6 /* AppDelegate.swift in Sources */,
4623BD1325694A1400940B12 /* MapController.swift in Sources */,
372216CD256943D300231245 /* POI.swift in Sources */,
379DC4C6256E1EC60044DA6C /* KMeansClustering.swift in Sources */,
379DC4C6256E1EC60044DA6C /* KMeans.swift in Sources */,
3753DCF0256B4C5B006D26A9 /* POIService.swift in Sources */,
FA81D67825690F360023E123 /* CoreDataStack.swift in Sources */,
FCE422B02570194B0017416F /* Coordinate.swift in Sources */,
37EB732D2562592700AC44D6 /* MapViewController.swift in Sources */,
37EB73332562592700AC44D6 /* Model.xcdatamodeld in Sources */,
FCE422B8257019650017416F /* Cluster.swift in Sources */,
192D34AD256E355500861703 /* QuadTree.swift in Sources */,
46A12E1B256CD099007BDF3E /* Extension.swift in Sources */,
FCE422BD25701A200017416F /* Quadrant.swift in Sources */,
37352CDB25695E7400CAFDFD /* JSONReader.swift in Sources */,
46A12E23256CD4DB007BDF3E /* BoundingBox.swift in Sources */,
FAC9FD05256E2107009FBB41 /* KCoefficient.swift in Sources */,
37EB73292562592700AC44D6 /* AppDelegate.swift in Sources */,
376E43BA256F9811002D3EE9 /* MockPOIService.swift in Sources */,
FCE422C225701A390017416F /* Degree.swift in Sources */,
372216C12569325600231245 /* DataManagable.swift in Sources */,
37352CE025695EE000CAFDFD /* Place.swift in Sources */,
37EB732B2562592700AC44D6 /* SceneDelegate.swift in Sources */,
Expand All @@ -517,24 +549,27 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FCE422D225701C790017416F /* Cluster.swift in Sources */,
FAC9FD0E256E2495009FBB41 /* KCoefficient.swift in Sources */,
192D34BE256E36B900861703 /* Extension.swift in Sources */,
3713D3E425694CD300E703EC /* POI.swift in Sources */,
46A12E2B256CED1B007BDF3E /* POIEntity+CoreDataProperties.swift in Sources */,
37279B0B256EBC6E00EA689A /* KMeansClustering.swift in Sources */,
37279B0B256EBC6E00EA689A /* KMeans.swift in Sources */,
FCE422DA25701CC00017416F /* Degree.swift in Sources */,
FCE422CE25701C760017416F /* Coordinate.swift in Sources */,
3713D3E025694C6B00E703EC /* CoreDataStack.swift in Sources */,
3731108C256F641F00350D55 /* POIService.swift in Sources */,
FCE422D625701CBD0017416F /* Quadrant.swift in Sources */,
46A12E40256CEDF2007BDF3E /* BoundingBoxTests.swift in Sources */,
192D34B6256E36A000861703 /* QuadTree.swift in Sources */,
46A12E2F256CED3A007BDF3E /* AppDelegate.swift in Sources */,
46A12E27256CED18007BDF3E /* POIEntity+CoreDataClass.swift in Sources */,
37EB73432562592900AC44D6 /* InteractiveClusteringMapTests.swift in Sources */,
37311088256F641400350D55 /* MockPOIService.swift in Sources */,
46A12E3A256CED57007BDF3E /* JSONReader.swift in Sources */,

192D34BA256E36A200861703 /* BoundingBox.swift in Sources */,
192D34B2256E368D00861703 /* QuadTreeTests.swift in Sources */,
199DDFF6256B9C8E0065B94E /* Place.swift in Sources */,
FCE422C725701B8A0017416F /* KMeansCentroidsTest.swift in Sources */,
3713D3FA25694D9C00E703EC /* DataManagable.swift in Sources */,
37279B03256EBC5200EA689A /* KMeansTests.swift in Sources */,
FAC9FD0A256E23B1009FBB41 /* KDefineTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Degree.swift
// InteractiveClusteringMap
//
// Created by Seungeon Kim on 2020/11/27.
//

import Foundation

enum Degree {
static let right = 90.0
static let straight = 180.0
static let turn = 360.0
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,73 @@
//
// KMeansClustering.swift
// KMeans.swift
// InteractiveClusteringMap
//
// Created by Oh Donggeon on 2020/11/25.
//

import Foundation

class KMeansClustering {
class KMeans {

private let k: Int

init(k: Int) {
self.k = k
}

func randomCentroids(rangeOfLat lats: ClosedRange<Double>,
rangeOfLng lngs: ClosedRange<Double>) -> [Coordinate] {
var centroids: [Coordinate] = []
for _ in 0..<k {
let lat = Double.random(in: lats)
let lng = Double.random(in: lngs)
let cluster = Coordinate(x: lng, y: lat)
centroids.append(cluster)
}

return centroids
}

func screenCentroids(topLeft: Coordinate, bottomRight: Coordinate) -> [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
}

/// points에 대한 centroid를 계속 계산하여 centroid가 이동한 총 거리가
/// convergeDistance 이하로 움직일 경우 cluster를 반환합니다.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Quadrant.swift
// InteractiveClusteringMap
//
// Created by Seungeon Kim on 2020/11/27.
//

import Foundation

enum Quadrant: Int {
case first = 0, second = 1, third = 2, fourth = 3

static func findQuadrant(angle: Double) -> Quadrant {
guard angle != .zero else { return Quadrant.first }

let number = Int(angle / Degree.right)
if angle.truncatingRemainder(dividingBy: Degree.right) == 0 {
return Quadrant(rawValue: number - 1) ?? Quadrant.first
} else {
return Quadrant(rawValue: number) ?? Quadrant.first
}
}

func theta(angle: Double) -> Double {
if self == .first || self == .third {
return Degree.right - angle
} else {
return angle
}
}

func degree(center: Coordinate, boundary: Coordinate) -> Coordinate {
switch self {
case .first:
return Coordinate(x: center.x + boundary.x, y: center.y)
case .second:
return Coordinate(x: center.x, y: center.y - boundary.y)
case .third:
return Coordinate(x: center.x - boundary.x, y: center.y)
case .fourth:
return Coordinate(x: center.x, y: center.y + boundary.y)
}
}

func convertToCoordinate(center: Coordinate, distance: Coordinate) -> Coordinate {
switch self {
case .first:
return Coordinate(x: center.x + distance.x, y: center.y + distance.y)
case .second:
return Coordinate(x: center.x + distance.x, y: center.y - distance.y)
case .third:
return Coordinate(x: center.x - distance.x, y: center.y - distance.y)
case .fourth:
return Coordinate(x: center.x - distance.x, y: center.y + distance.y)
}
}
}
Loading

0 comments on commit 6439caf

Please sign in to comment.