From d5bbe9162ba53a46f0ebb1f6e1c1ba039863df45 Mon Sep 17 00:00:00 2001 From: ilyakooo0 Date: Sun, 11 Mar 2018 16:25:16 +0300 Subject: [PATCH] Initial commit --- .gitignore | 68 ++++ Galmin.xcodeproj/project.pbxproj | 292 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + Galmin/Graph.swift | 263 ++++++++++++++++ Galmin/Printer.swift | 213 +++++++++++++ Galmin/Solver.swift | 185 +++++++++++ Galmin/main.swift | 63 ++++ 8 files changed, 1099 insertions(+) create mode 100644 .gitignore create mode 100644 Galmin.xcodeproj/project.pbxproj create mode 100644 Galmin.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Galmin.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 Galmin/Graph.swift create mode 100644 Galmin/Printer.swift create mode 100644 Galmin/Solver.swift create mode 100644 Galmin/main.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..161179b --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output diff --git a/Galmin.xcodeproj/project.pbxproj b/Galmin.xcodeproj/project.pbxproj new file mode 100644 index 0000000..623af05 --- /dev/null +++ b/Galmin.xcodeproj/project.pbxproj @@ -0,0 +1,292 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 0977ACA5203F351500618E62 /* Printer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0977ACA4203F351500618E62 /* Printer.swift */; }; + 09C2834F2038A19D00E11545 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C2834E2038A19D00E11545 /* main.swift */; }; + 09C283562038A1BB00E11545 /* Graph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C283552038A1BB00E11545 /* Graph.swift */; }; + 09C283582038C79800E11545 /* Solver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C283572038C79800E11545 /* Solver.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 09C283492038A19D00E11545 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0977ACA4203F351500618E62 /* Printer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Printer.swift; sourceTree = ""; }; + 09C2834B2038A19D00E11545 /* Galmin */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Galmin; sourceTree = BUILT_PRODUCTS_DIR; }; + 09C2834E2038A19D00E11545 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 09C283552038A1BB00E11545 /* Graph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Graph.swift; sourceTree = ""; }; + 09C283572038C79800E11545 /* Solver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Solver.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 09C283482038A19D00E11545 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 09C283422038A19D00E11545 = { + isa = PBXGroup; + children = ( + 09C2834D2038A19D00E11545 /* Galmin */, + 09C2834C2038A19D00E11545 /* Products */, + ); + sourceTree = ""; + }; + 09C2834C2038A19D00E11545 /* Products */ = { + isa = PBXGroup; + children = ( + 09C2834B2038A19D00E11545 /* Galmin */, + ); + name = Products; + sourceTree = ""; + }; + 09C2834D2038A19D00E11545 /* Galmin */ = { + isa = PBXGroup; + children = ( + 09C2834E2038A19D00E11545 /* main.swift */, + 09C283552038A1BB00E11545 /* Graph.swift */, + 09C283572038C79800E11545 /* Solver.swift */, + 0977ACA4203F351500618E62 /* Printer.swift */, + ); + path = Galmin; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 09C2834A2038A19D00E11545 /* Galmin */ = { + isa = PBXNativeTarget; + buildConfigurationList = 09C283522038A19D00E11545 /* Build configuration list for PBXNativeTarget "Galmin" */; + buildPhases = ( + 09C283472038A19D00E11545 /* Sources */, + 09C283482038A19D00E11545 /* Frameworks */, + 09C283492038A19D00E11545 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Galmin; + productName = Galmin; + productReference = 09C2834B2038A19D00E11545 /* Galmin */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 09C283432038A19D00E11545 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = "Ilya Kos"; + TargetAttributes = { + 09C2834A2038A19D00E11545 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 09C283462038A19D00E11545 /* Build configuration list for PBXProject "Galmin" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 09C283422038A19D00E11545; + productRefGroup = 09C2834C2038A19D00E11545 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 09C2834A2038A19D00E11545 /* Galmin */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 09C283472038A19D00E11545 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0977ACA5203F351500618E62 /* Printer.swift in Sources */, + 09C283582038C79800E11545 /* Solver.swift in Sources */, + 09C283562038A1BB00E11545 /* Graph.swift in Sources */, + 09C2834F2038A19D00E11545 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 09C283502038A19D00E11545 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 09C283512038A19D00E11545 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 09C283532038A19D00E11545 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 8J63JZYYHZ; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 09C283542038A19D00E11545 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 8J63JZYYHZ; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 09C283462038A19D00E11545 /* Build configuration list for PBXProject "Galmin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 09C283502038A19D00E11545 /* Debug */, + 09C283512038A19D00E11545 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 09C283522038A19D00E11545 /* Build configuration list for PBXNativeTarget "Galmin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 09C283532038A19D00E11545 /* Debug */, + 09C283542038A19D00E11545 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 09C283432038A19D00E11545 /* Project object */; +} diff --git a/Galmin.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Galmin.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..542eb32 --- /dev/null +++ b/Galmin.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Galmin.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Galmin.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..3ddf867 --- /dev/null +++ b/Galmin.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + BuildSystemType + Latest + + diff --git a/Galmin/Graph.swift b/Galmin/Graph.swift new file mode 100644 index 0000000..8dae857 --- /dev/null +++ b/Galmin/Graph.swift @@ -0,0 +1,263 @@ +// +// Graph.swift +// Galmin +// +// Created by Ilya Kos on 2/17/18. +// Copyright © 2018 Ilya Kos. All rights reserved. +// + +import Foundation + + +/// Full graph +struct Graph where Vertex: Hashable & Comparable { + private var weights: [Vertex: [Vertex: Weight]] = [:] + private var usedVertices: Set + var usedEdges: [(start: Vertex, end: Vertex)] = [] + /// Not actual min weight. It's the minimal possible weight. + var minWeight = 0 + + var graphWeights: [Vertex: [Vertex: Weight]] { + return weights + } + + var components: [Set] { + var out: [Set] = [] + for edge in usedEdges { + var first: Set! + for (i, component) in out.enumerated() { + if component.contains(edge.start) { + first = out.remove(at: i) + break // Since only one component can contain a vertex + } + } + if first == nil { + first = Set([edge.start]) + } + var second: Set! + for (i, component) in out.enumerated() { + if component.contains(edge.end) { + second = out.remove(at: i) + break // Since only one component can contain a vertex + } + } + if second == nil { + second = Set([edge.end]) + } + out.append(first.union(second)) + } + return out + } + + private mutating func deleteEdge(from start: Vertex, to end: Vertex) { + weights[start] = nil + weights = weights.mapValues { row in + var row = row + row[end] = nil + return row + } + weights[end]?[start]? = .infinity + } + + typealias Minimization = (before: [Vertex: [Vertex: Weight]], after: [Vertex: [Vertex: Weight]], reductions: (starts: [Vertex: Int], ends: [Vertex: Int])) + + mutating func minimize() -> Minimization { + assert(!weights.isEmpty) + // Minimize rows + let before = weights + var starts: [Vertex: Int] = [:] + for (key, row) in weights { + let offset = row.values.min()! + if offset.numValue == 0 { + continue + } + starts[key] = offset.numValue + minWeight += offset.numValue + weights[key] = row.mapValues {$0 - offset} + } + + // Minimize columns + var ends: [Vertex: Int] = [:] + for key in weights.first!.value.keys { + let offset = weights.values.map({$0[key]!}).min()! + if offset.numValue == 0 { // Just optimization + continue + } + ends[key] = offset.numValue + minWeight += offset.numValue + weights = weights.mapValues { row in + var row = row + row[key]! -= offset + return row + } + } + let after = weights + return (before: before, after: after, reductions: (starts: starts, ends: ends)) + } + + mutating func step() -> (start: Vertex, end: Vertex)? { + if weights.isEmpty { + return nil + } + +// let reductions = minimize() // Should minimize on creation + + var start: Vertex! + var end: Vertex! + var maxCoef = -1 + + for row in weights { + for weight in row.value { + if weight.value == .value(0) { + let coef = coeficient(for: weights, start: row.key, end: weight.key) + if coef > maxCoef { + start = row.key + end = weight.key + maxCoef = coef + } + } + } + } + + usedVertices.insert(start) + usedVertices.insert(end) + + deleteEdge(from: start, to: end) + + usedEdges.append((start, end)) + + for component in components { + for start in component { + for end in component { + weights[start]?[end]? = .infinity + } + } + } + + return (start, end) + } + + func validate() -> Bool { + var ends: [Vertex: Bool] = weights.mapValues {_ in false} // Valid? + for row in weights.values { + if !row.values.reduce(false, {$1 != .infinity ? true : $0}) { + return false + } + ends.filter({!$0.value}).keys.forEach { key in + if row[key] != .infinity { + ends[key] = true + } + } + } + if ends.values.contains(false) { + return false + } + return true + } + + func finalSolution() -> [(start: Vertex, end: Vertex)] { + assert(weights.count == 2) + var found: (start: Vertex, end: Vertex)? + for (start, row) in weights { + for (end, weight) in row { + if weight == .value(0) { + if let found = found { + return [found, (start, end)] + } else { + found = (start, end) + } + } + } + } + fatalError("finalSolution fell through. Most likeley: didn't minimize.") + } + + var power: Int { + return weights.count + (weights.first?.value.count ?? 0) - + weights.values.reduce(0) {$0 + $1.values.filter({$0 == .infinity}).count} + } + + mutating func exclude(edge: (start: Vertex, end: Vertex)) { + weights[edge.start]![edge.end] = .infinity + } + + init(with weights: [Vertex: [Vertex: Weight]], used usedVertices: Set) { + self.weights = weights + self.usedVertices = usedVertices + } +} + +enum Weight: Comparable, CustomStringConvertible { + static func <(lhs: Weight, rhs: Weight) -> Bool { + switch lhs { + case .infinity: + return false + case .value(let lv): + switch rhs { + case .infinity: + return true + case .value(let rv): + return lv < rv + } + } + } + + static func ==(lhs: Weight, rhs: Weight) -> Bool { + switch lhs { + case .infinity: + if case .infinity = rhs { + return true + } else { + return false + } + case .value(let lv): + if case let .value(rv) = rhs { + return lv == rv + } else { + return false + } + } + } + + case infinity + case value(Int) + + var numValue: Int { + switch self { + case .infinity: + return 0 + case .value(let v): + return v + } + } + + static func -(lhs: Weight, rhs: Weight) -> Weight { + return lhs - rhs.numValue + } + static func -(lhs: Weight, rhs: Int) -> Weight { + if case let .value(v) = lhs { + return .value(v - rhs) + } else { + return .infinity + } + } + static func -=(lhs: inout Weight, rhs: Weight) { + lhs = lhs - rhs + } + + var description: String { + switch self { + case .value(let v): + return "\(v)" + case .infinity: + return "∞" + } + } +} + +func coeficient(`for` weights: [Vertex: [Vertex: Weight]], start: Vertex, end: Vertex) -> Int { + return weights[start]!.filter({$0.key != end}).values.min()!.numValue + + weights.filter({$0.key != start}).reduce(Weight.infinity) {min($0, $1.value[end]!)}.numValue + +} + diff --git a/Galmin/Printer.swift b/Galmin/Printer.swift new file mode 100644 index 0000000..4609931 --- /dev/null +++ b/Galmin/Printer.swift @@ -0,0 +1,213 @@ +// +// Printer.swift +// Galmin +// +// Created by Ilya Kos on 2/22/18. +// Copyright © 2018 Ilya Kos. All rights reserved. +// + +import Foundation + +private extension String { + var sub: String { + return self.map { c in + switch c { + case "0": + return "₀" + case "1": + return "₁" + case "2": + return "₂" + case "3": + return "₃" + case "4": + return "₄" + case "5": + return "₅" + case "6": + return "₆" + case "7": + return "₇" + case "8": + return "₈" + case "9": + return "₉" + case "(": + return "₍" + case ")": + return "₎" + default: + return String(c) + } + }.joined() + } + var sup: String { + return self.map { c in + switch c { + case "0": + return "⁰" + case "1": + return "¹" + case "2": + return "²" + case "3": + return "³" + case "4": + return "⁴" + case "5": + return "⁵" + case "6": + return "⁶" + case "7": + return "⁷" + case "8": + return "⁸" + case "9": + return "⁹" + case "(": + return "⁽" + case ")": + return "⁾" + default: + return String(c) + } + }.joined() + } +} + +class Printer where Vertex: Hashable & Comparable { + + private func table(`for` matrix: [Vertex: [Vertex: Weight]], with reductions: (starts: [Vertex: Int], ends: [Vertex: Int]), prefix: String, coef: Bool) -> String { + var out: [[String]] = [] + let ends = matrix.values.first!.keys.sorted() + let sEnds = ends.map({"\($0)"}) + let row: [String] = ["S" + prefix.sub] + sEnds + (reductions.starts.count > 0 ? ["min"] : []) + out.append(row) + for start in matrix.keys.sorted() { + var nextRow = ["\(start)"] + ends.map {"\(matrix[start]![$0]!)" + (coef && matrix[start]![$0]! == .value(0) ? "\(coeficient(for: matrix, start: start, end: $0))".sup : "")} + if let reduction = reductions.starts[start] { + nextRow.append("\(reduction)") + } + out.append(nextRow) + } + if reductions.ends.count > 0 { + out.append(["min"] + ends.map {reductions.ends[$0] == nil ? "" : "\(reductions.ends[$0]!)"}) + } + return out.reduce("") {$0 + $1.reduce("", {"\($0)\($1), "}).dropLast(2) + "\n"} + } + + func processMinimizations(_ minimizations: (initial: [Vertex: [Vertex: Weight]], includingTables: Graph.Minimization, excludingTables: Graph.Minimization), prefix: String, counter: Int) -> () { + do { + try table(for: minimizations.initial, with: ([:], [:]), prefix: prefix, coef: true).write(with: "\(counter)_1.csv") + try table(for: minimizations.includingTables.before, with: minimizations.includingTables.reductions, prefix: prefix + "1", coef: false).write(with: "\(counter)_2.csv") + try table(for: minimizations.includingTables.after, with: ([:], [:]), prefix: prefix + "1", coef: false).write(with: "\(counter)_3.csv") + try table(for: minimizations.excludingTables.before, with: minimizations.excludingTables.reductions, prefix: prefix + "0", coef: false).write(with: "\(counter)_4.csv") + try table(for: minimizations.excludingTables.after, with: ([:], [:]), prefix: prefix + "0", coef: false).write(with: "\(counter)_5.csv") + } catch { + print("Couldn't write files ¯\\_(ツ)_/¯") + } + } + + private var solutions: FileHandle! + + func processInitialMinimization(minimization: Graph.Minimization, counter: Int) -> () { + do { + try FileManager.default.createDirectory(atPath: "diskraDz3", withIntermediateDirectories: true) + FileManager.default.createFile(atPath: "diskraDz3/solutions.txt", contents: nil) + solutions = FileHandle(forWritingAtPath: "diskraDz3/solutions.txt") + try table(for: minimization.before, with: minimization.reductions, prefix: "", coef: false).write(with: "\(counter)_1.csv") + try table(for: minimization.after, with: ([:], [:]), prefix: "", coef: false).write(with: "\(counter)_2.csv") + } catch { + print("Couldn't write files ¯\\_(ツ)_/¯") + } + } + + func processTree(tree: Tree, selecting: Tree?, counter: Int) -> () { + var nodeCounter = 0 + func process(tree: Tree) -> (declarations: [String], graph: [String], node: Int) { + let node = nodeCounter + nodeCounter += 1 + var declarations: [String] = [] + var graph: [String] = [] + var edge = "" + if let selected = tree.chosenEdge { + edge = "(\(selected.start), \(selected.end))" + } + var solution = "" + if let s = tree.solution { + solution = "\(s.map({String(describing: $0)}).joined())" + } + declarations.append("\(node) [label=\(tree.minWeight)\(edge)\(solution)>\( tree === selecting ? " shape=ellipse" : " shape=box" )]") +// declarations.append("\(node) [label=\(tree.minWeight)\(edge)>\( tree === selecting ? " color=\"red\"" : "" )]") +//// declarations.append("\(node) [label=\(tree.prefix.count == 0 ? " " : tree.prefix)\(tree.minWeight)\(edge)>\( tree === selecting ? " color=\"red\"" : "" )]") +////// declarations.append("\(node) [label=\"S\(tree.prefix.count == 0 ? "" : tree.prefix.sub)\(String(tree.minWeight).sup)\(edge.sub)\"\( tree === selecting ? " color=\"red\"" : "" )]") + if let (including, excluding) = tree.nodes { + let excludingResult = process(tree: excluding) + declarations.append(contentsOf: excludingResult.declarations) + graph.append("\(node) -> \(excludingResult.node)") + graph.append(contentsOf: excludingResult.graph) + let includingResult = process(tree: including) + declarations.append(contentsOf: includingResult.declarations) + graph.append("\(node) -> \(includingResult.node)") + graph.append(contentsOf: includingResult.graph) + } + return (declarations, graph, node) + } + let (declaration, graph, _) = process(tree: tree) + let out = """ + digraph { + { + \(declaration.joined(separator: "\n\t")) + } + \(graph.joined(separator: "\n")) + } + """ + do { + try out.write(with: "\(counter).gv") + } catch { + print("Couldn't write files ¯\\_(ツ)_/¯") + } + } + + func processResult(tree: Tree, counter: Int) -> () { + solutions.seekToEndOfFile() + let text = "Step: \(counter) \tweight = \(tree.minWeight) \tsolution: (\(tree.solution!.map({String(describing: $0)}).joined(separator: ", ")))\n" +// print(text) + solutions.write(text.data(using: .utf8)!) + } + + func flushResults() { + solutions.truncateFile(atOffset: 0) + } + +} + +extension Tree { + var path: [Vertex] { + let graph = self.value + var edges = graph.usedEdges + graph.finalSolution() + var vertices: Set = [] + for (start, end) in edges { + vertices.insert(start) + vertices.insert(end) + } + var out: [Vertex] = [vertices.min()!] + edgesLoop: while edges.count > 0 { + for (i, edge) in edges.enumerated() { + if edge.start == out.last! { + out.append(edge.end) + edges.remove(at: i) + continue edgesLoop + } + } + fatalError() + } + return Array(out.dropLast()) + } +} + +private extension String { + func write(with name: String) throws { + try self.write(toFile: "diskraDz3/\(name)", atomically: true, encoding: .utf8) + } +} diff --git a/Galmin/Solver.swift b/Galmin/Solver.swift new file mode 100644 index 0000000..cdba61f --- /dev/null +++ b/Galmin/Solver.swift @@ -0,0 +1,185 @@ +// +// Tree.swift +// Galmin +// +// Created by Ilya Kos on 2/17/18. +// Copyright © 2018 Ilya Kos. All rights reserved. +// + +import Foundation + +class Solver where Vertex: Hashable & Comparable { + + private var tree: Tree + + init(weights: [Vertex: [Vertex: Int]]) { + var weights = weights.mapValues {$0.mapValues {Weight.value($0)}} + for key in weights.keys { + weights[key]![key] = .infinity + } + tree = Tree(with: Graph(with: weights, used: [])) // TODO: Need to minimize initial tree + + } + + var counter = 1 + + var processInitialMinimization: ((Graph.Minimization, _ counter: Int) -> ())? + var processMinimizations: (((initial: [Vertex: [Vertex: Weight]], includingTables: Graph.Minimization, excludingTables: Graph.Minimization), _ prefix: String ,_ counter: Int) -> ())? + var processTree: ((Tree, _ selecting: Tree?, _ counter: Int) -> ())? + var processResult: ((Tree, _ counter: Int) -> ())? + var flushResults: (() -> ())? + + private var solutions: [Tree] = [] + + + func start() { + solutions = [] + + var root = tree.value + + let minimization = root.minimize() + + tree = Tree(with: root) + + processInitialMinimization?(minimization, counter) +// processTree?(tree, tree, counter) + var minResult: Int? + + largeLoop: while true { + counter += 1 + + let next = tree.leaves.lazy.filter({!self.solutions.contains($0)}).min()! +// print(next.minWeight) + processTree?(tree, next, counter) + let (minimizations, result, excludeResults) = next.step() + processMinimizations?(minimizations, next.prefix, counter) + + solutions.append(contentsOf: excludeResults) + + if let result = result { +// print("\t\t\tFOUND: \(result.minWeight)") + if minResult == nil { + minResult = result.minWeight + } else { + if minResult! > result.minWeight { + flushResults?() + minResult = result.minWeight + } + } + if result.minWeight > minResult! { + solutions.append(result) + continue + } + processResult?(result, counter) + solutions.append(result) + } + if let minResult = minResult { + for leaf in tree.leaves.lazy.filter({!self.solutions.contains($0)}) { +// print("\t\(leaf.minWeight)") + if leaf.minWeight <= minResult { + continue largeLoop + } + } + processTree?(tree, nil, counter+1) + break + } + } + } + +// private func step() { +// let next = tree.leaves.min()! +// let minimizations = next.step() +// processMinimizations?(minimizations) +// processTree?(tree) +// } +} + + +class Tree: Comparable where Vertex: Hashable & Comparable { + let value: Graph + var nodes: (including: Tree, excluding: Tree)? + var chosenEdge: (start: Vertex, end: Vertex)? + var prefix = "" + var solution: [Vertex]? + + /// Steps the graph + init(with graph: Graph) { + self.value = graph + } + + var leaves: [Tree] { + if let (including, excluding) = nodes { + return including.leaves + excluding.leaves + } else { + return [self] + } + } + + var minWeight: Int { + return value.minWeight + } + + var power: Int { + return value.power + } + + + func step() -> ((initial: [Vertex: [Vertex: Weight]], + includingTables: Graph.Minimization, + excludingTables: Graph.Minimization), + result: Tree?, + excludeResults: [Tree]) { + assert(nodes == nil) + var including = value + chosenEdge = including.step() + var excluding = value + excluding.exclude(edge: chosenEdge!) + let includingTables = including.minimize() + let excludingTables = excluding.minimize() + + + let includingTree = Tree(with: including) + includingTree.prefix = prefix + "1" + let excludingTree = Tree(with: excluding) + excludingTree.prefix = prefix + "0" + + var excludeResults: [Tree] = [] + + if !including.validate() { + excludeResults.append(includingTree) + } + if !excluding.validate() { + excludeResults.append(excludingTree) + } + + nodes = (including: includingTree, excluding: excludingTree) + + var includingResult: Tree? + + if including.graphWeights.count == 2 && including.validate() { + includingResult = nodes?.including + includingResult?.solution = includingResult!.path + } + + return ((value.graphWeights, includingTables, excludingTables), includingResult, excludeResults) + } + + // MARK: Comparable + static func <(lhs: Tree, rhs: Tree) -> Bool { + if lhs.minWeight < rhs.minWeight { + return true + } else if lhs.minWeight > rhs.minWeight { + return false + } else { + if lhs.power < rhs.power { + return true + } else { + return false + } + } + } + + static func ==(lhs: Tree, rhs: Tree) -> Bool { + return lhs.minWeight == rhs.minWeight && lhs.power == rhs.power + } +} diff --git a/Galmin/main.swift b/Galmin/main.swift new file mode 100644 index 0000000..dd5be60 --- /dev/null +++ b/Galmin/main.swift @@ -0,0 +1,63 @@ +// +// main.swift +// Galmin +// +// Created by Ilya Kos on 2/17/18. +// Copyright © 2018 Ilya Kos. All rights reserved. +// + +import Foundation + +let printer = Printer() + +print("Enter your values in the folowing format:") +print("(x0, y0): 1 2") + +var inputs: [Int: (x: Int, y: Int)] = [:] + +func iter(i: Int) { + if let next = readLine(strippingNewline: true)?.split(separator: " ", omittingEmptySubsequences: true).flatMap({Int($0)}), + next.count == 2, + let x = next.first, + let y = next.last { + inputs[i] = (x, y) + } else { + print("nope.") + iter(i: i) + } +} + +for i in 1...6 { + print("(x\(i), y\(i)): ", terminator: "") + iter(i: i) +} + +//inputs = [1: (4, 1), 2: (4, 3), 3: (2, 7), 4: (9, 6), 5: (10, 7), 6: (6, 10)] + +var weights: [Int: [Int: Int]] = [:] + +for (start, sVal) in inputs { + weights[start] = [:] + for (end, eVal) in inputs { + weights[start]![end] = abs(sVal.x - eVal.x) + abs(sVal.y - eVal.y) + } +} + +let solver = Solver(weights: weights) + +solver.processInitialMinimization = printer.processInitialMinimization +solver.processMinimizations = printer.processMinimizations +solver.processResult = printer.processResult +solver.processTree = printer.processTree +solver.flushResults = printer.flushResults + +solver.start() + +print(""" + ++-------+ +| i l y | +| a k o | +| o o 0 | ++-------+ +""")