diff --git a/README.md b/README.md index 5edc377..ae0d9cf 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # CS3217 Problem Set 0 -Name: Your name +Name: Chen Hui -Matric No: Your matric no +Matric No: A0187372W ## Instructions for Students diff --git a/xcode/SudokuSolver.xcodeproj/project.pbxproj b/xcode/SudokuSolver.xcodeproj/project.pbxproj index be590df..0ff626c 100644 --- a/xcode/SudokuSolver.xcodeproj/project.pbxproj +++ b/xcode/SudokuSolver.xcodeproj/project.pbxproj @@ -7,13 +7,28 @@ objects = { /* Begin PBXBuildFile section */ + 2E013F0D25A5FF3D006CA064 /* NormalSudokuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E013F0C25A5FF3D006CA064 /* NormalSudokuTests.swift */; }; + 2E013F2625A60F33006CA064 /* KillerSudokuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E013F2525A60F33006CA064 /* KillerSudokuTests.swift */; }; + 2E013F4525A6F4F8006CA064 /* ModelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E013F4425A6F4F7006CA064 /* ModelData.swift */; }; + 2E013F5125A6F5AF006CA064 /* output_3.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4925A6F5AE006CA064 /* output_3.txt */; }; + 2E013F5225A6F5AF006CA064 /* output_1.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4A25A6F5AF006CA064 /* output_1.txt */; }; + 2E013F5325A6F5AF006CA064 /* input_4.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4B25A6F5AF006CA064 /* input_4.txt */; }; + 2E013F5425A6F5AF006CA064 /* input_2.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4C25A6F5AF006CA064 /* input_2.txt */; }; + 2E013F5525A6F5AF006CA064 /* output_2.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4D25A6F5AF006CA064 /* output_2.txt */; }; + 2E013F5625A6F5AF006CA064 /* input_1.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4E25A6F5AF006CA064 /* input_1.txt */; }; + 2E013F5725A6F5AF006CA064 /* input_3.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F4F25A6F5AF006CA064 /* input_3.txt */; }; + 2E013F5825A6F5AF006CA064 /* output_4.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2E013F5025A6F5AF006CA064 /* output_4.txt */; }; + 2E013F6225A85F9A006CA064 /* Cage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E013F6125A85F9A006CA064 /* Cage.swift */; }; + 2E013F6F25A8A1F3006CA064 /* SudokuSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E70BE8225A2CF45002FC7C3 /* SudokuSolver.swift */; }; + 2E013F7025A8A1F3006CA064 /* KillerSudoku.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E013F5B25A85F74006CA064 /* KillerSudoku.swift */; }; + 2E013F7125A8A1F3006CA064 /* Sudoku.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054C871239FB93F0019BCA6 /* Sudoku.swift */; }; + 2E013F7225A8A1F3006CA064 /* NormalSudoku.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E70BE7C25A2C933002FC7C3 /* NormalSudoku.swift */; }; 5054C840239FB9090019BCA6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054C83F239FB9090019BCA6 /* AppDelegate.swift */; }; 5054C842239FB9090019BCA6 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054C841239FB9090019BCA6 /* SceneDelegate.swift */; }; 5054C844239FB9090019BCA6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054C843239FB9090019BCA6 /* ContentView.swift */; }; 5054C846239FB90C0019BCA6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5054C845239FB90C0019BCA6 /* Assets.xcassets */; }; 5054C849239FB90C0019BCA6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5054C848239FB90C0019BCA6 /* Preview Assets.xcassets */; }; 5054C84C239FB90C0019BCA6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5054C84A239FB90C0019BCA6 /* LaunchScreen.storyboard */; }; - 5054C872239FB93F0019BCA6 /* Sudoku.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054C871239FB93F0019BCA6 /* Sudoku.swift */; }; 5054C876239FB95A0019BCA6 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054C875239FB95A0019BCA6 /* Cell.swift */; }; /* End PBXBuildFile section */ @@ -28,6 +43,22 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2E013F0C25A5FF3D006CA064 /* NormalSudokuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NormalSudokuTests.swift; sourceTree = ""; }; + 2E013F2525A60F33006CA064 /* KillerSudokuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KillerSudokuTests.swift; sourceTree = ""; }; + 2E013F4425A6F4F7006CA064 /* ModelData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelData.swift; sourceTree = ""; }; + 2E013F4925A6F5AE006CA064 /* output_3.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = output_3.txt; sourceTree = ""; }; + 2E013F4A25A6F5AF006CA064 /* output_1.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = output_1.txt; sourceTree = ""; }; + 2E013F4B25A6F5AF006CA064 /* input_4.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = input_4.txt; sourceTree = ""; }; + 2E013F4C25A6F5AF006CA064 /* input_2.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = input_2.txt; sourceTree = ""; }; + 2E013F4D25A6F5AF006CA064 /* output_2.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = output_2.txt; sourceTree = ""; }; + 2E013F4E25A6F5AF006CA064 /* input_1.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = input_1.txt; sourceTree = ""; }; + 2E013F4F25A6F5AF006CA064 /* input_3.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = input_3.txt; sourceTree = ""; }; + 2E013F5025A6F5AF006CA064 /* output_4.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = output_4.txt; sourceTree = ""; }; + 2E013F5B25A85F74006CA064 /* KillerSudoku.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KillerSudoku.swift; sourceTree = ""; }; + 2E013F6125A85F9A006CA064 /* Cage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cage.swift; sourceTree = ""; }; + 2E70BE7925A2C910002FC7C3 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MyPlayground.playground; path = "../../../../Library/Autosave Information/MyPlayground.playground"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 2E70BE7C25A2C933002FC7C3 /* NormalSudoku.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NormalSudoku.swift; sourceTree = ""; }; + 2E70BE8225A2CF45002FC7C3 /* SudokuSolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SudokuSolver.swift; sourceTree = ""; }; 5054C83C239FB9090019BCA6 /* SudokuSolver.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SudokuSolver.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5054C83F239FB9090019BCA6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5054C841239FB9090019BCA6 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -60,9 +91,45 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2E013F4825A6F55F006CA064 /* Input */ = { + isa = PBXGroup; + children = ( + 2E013F4E25A6F5AF006CA064 /* input_1.txt */, + 2E013F4C25A6F5AF006CA064 /* input_2.txt */, + 2E013F4F25A6F5AF006CA064 /* input_3.txt */, + 2E013F4B25A6F5AF006CA064 /* input_4.txt */, + 2E013F4A25A6F5AF006CA064 /* output_1.txt */, + 2E013F4D25A6F5AF006CA064 /* output_2.txt */, + 2E013F4925A6F5AE006CA064 /* output_3.txt */, + 2E013F5025A6F5AF006CA064 /* output_4.txt */, + ); + path = Input; + sourceTree = ""; + }; + 2E013F6525A85FF9006CA064 /* Structs */ = { + isa = PBXGroup; + children = ( + 2E013F6125A85F9A006CA064 /* Cage.swift */, + 5054C875239FB95A0019BCA6 /* Cell.swift */, + ); + path = Structs; + sourceTree = ""; + }; + 2E013F6A25A8633A006CA064 /* Sudokus */ = { + isa = PBXGroup; + children = ( + 2E70BE8225A2CF45002FC7C3 /* SudokuSolver.swift */, + 5054C871239FB93F0019BCA6 /* Sudoku.swift */, + 2E70BE7C25A2C933002FC7C3 /* NormalSudoku.swift */, + 2E013F5B25A85F74006CA064 /* KillerSudoku.swift */, + ); + path = Sudokus; + sourceTree = ""; + }; 5054C833239FB9090019BCA6 = { isa = PBXGroup; children = ( + 2E70BE7925A2C910002FC7C3 /* MyPlayground.playground */, 5054C83E239FB9090019BCA6 /* SudokuSolver */, 5054C855239FB90C0019BCA6 /* SudokuSolverTests */, 5054C83D239FB9090019BCA6 /* Products */, @@ -81,14 +148,16 @@ 5054C83E239FB9090019BCA6 /* SudokuSolver */ = { isa = PBXGroup; children = ( - 5054C871239FB93F0019BCA6 /* Sudoku.swift */, - 5054C875239FB95A0019BCA6 /* Cell.swift */, + 2E013F6A25A8633A006CA064 /* Sudokus */, + 2E013F6525A85FF9006CA064 /* Structs */, + 2E013F4825A6F55F006CA064 /* Input */, 5054C83F239FB9090019BCA6 /* AppDelegate.swift */, 5054C841239FB9090019BCA6 /* SceneDelegate.swift */, 5054C843239FB9090019BCA6 /* ContentView.swift */, 5054C845239FB90C0019BCA6 /* Assets.xcassets */, 5054C84A239FB90C0019BCA6 /* LaunchScreen.storyboard */, 5054C84D239FB90C0019BCA6 /* Info.plist */, + 2E013F4425A6F4F7006CA064 /* ModelData.swift */, 5054C847239FB90C0019BCA6 /* Preview Content */, ); path = SudokuSolver; @@ -105,6 +174,8 @@ 5054C855239FB90C0019BCA6 /* SudokuSolverTests */ = { isa = PBXGroup; children = ( + 2E013F2525A60F33006CA064 /* KillerSudokuTests.swift */, + 2E013F0C25A5FF3D006CA064 /* NormalSudokuTests.swift */, 5054C858239FB90C0019BCA6 /* Info.plist */, ); path = SudokuSolverTests; @@ -156,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1120; - LastUpgradeCheck = 1120; + LastUpgradeCheck = 1230; ORGANIZATIONNAME = H; TargetAttributes = { 5054C83B239FB9090019BCA6 = { @@ -164,6 +235,7 @@ }; 5054C851239FB90C0019BCA6 = { CreatedOnToolsVersion = 11.2.1; + LastSwiftMigration = 1230; TestTargetID = 5054C83B239FB9090019BCA6; }; }; @@ -193,7 +265,15 @@ buildActionMask = 2147483647; files = ( 5054C84C239FB90C0019BCA6 /* LaunchScreen.storyboard in Resources */, + 2E013F5125A6F5AF006CA064 /* output_3.txt in Resources */, 5054C849239FB90C0019BCA6 /* Preview Assets.xcassets in Resources */, + 2E013F5225A6F5AF006CA064 /* output_1.txt in Resources */, + 2E013F5525A6F5AF006CA064 /* output_2.txt in Resources */, + 2E013F5725A6F5AF006CA064 /* input_3.txt in Resources */, + 2E013F5325A6F5AF006CA064 /* input_4.txt in Resources */, + 2E013F5425A6F5AF006CA064 /* input_2.txt in Resources */, + 2E013F5625A6F5AF006CA064 /* input_1.txt in Resources */, + 2E013F5825A6F5AF006CA064 /* output_4.txt in Resources */, 5054C846239FB90C0019BCA6 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -235,8 +315,13 @@ 5054C840239FB9090019BCA6 /* AppDelegate.swift in Sources */, 5054C876239FB95A0019BCA6 /* Cell.swift in Sources */, 5054C842239FB9090019BCA6 /* SceneDelegate.swift in Sources */, + 2E013F7225A8A1F3006CA064 /* NormalSudoku.swift in Sources */, + 2E013F7025A8A1F3006CA064 /* KillerSudoku.swift in Sources */, + 2E013F4525A6F4F8006CA064 /* ModelData.swift in Sources */, + 2E013F7125A8A1F3006CA064 /* Sudoku.swift in Sources */, + 2E013F6F25A8A1F3006CA064 /* SudokuSolver.swift in Sources */, + 2E013F6225A85F9A006CA064 /* Cage.swift in Sources */, 5054C844239FB9090019BCA6 /* ContentView.swift in Sources */, - 5054C872239FB93F0019BCA6 /* Sudoku.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -244,6 +329,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E013F0D25A5FF3D006CA064 /* NormalSudokuTests.swift in Sources */, + 2E013F2625A60F33006CA064 /* KillerSudokuTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -295,6 +382,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -355,6 +443,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -428,6 +517,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 5QVR84ECHQ; INFOPLIST_FILE = SudokuSolverTests/Info.plist; @@ -439,6 +529,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = cs3217.a0144892w.SudokuSolverTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SudokuSolver.app/SudokuSolver"; @@ -450,6 +541,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 5QVR84ECHQ; INFOPLIST_FILE = SudokuSolverTests/Info.plist; diff --git a/xcode/SudokuSolver.xcodeproj/xcshareddata/xcbaselines/5054C851239FB90C0019BCA6.xcbaseline/3E456181-7A01-4854-8E54-E451E60F64FA.plist b/xcode/SudokuSolver.xcodeproj/xcshareddata/xcbaselines/5054C851239FB90C0019BCA6.xcbaseline/3E456181-7A01-4854-8E54-E451E60F64FA.plist new file mode 100644 index 0000000..afe571e --- /dev/null +++ b/xcode/SudokuSolver.xcodeproj/xcshareddata/xcbaselines/5054C851239FB90C0019BCA6.xcbaseline/3E456181-7A01-4854-8E54-E451E60F64FA.plist @@ -0,0 +1,22 @@ + + + + + classNames + + KillerSudokuTests + + testKillerSudokuPerformance1() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.0843 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/xcode/SudokuSolver.xcodeproj/xcshareddata/xcbaselines/5054C851239FB90C0019BCA6.xcbaseline/Info.plist b/xcode/SudokuSolver.xcodeproj/xcshareddata/xcbaselines/5054C851239FB90C0019BCA6.xcbaseline/Info.plist new file mode 100644 index 0000000..b222f67 --- /dev/null +++ b/xcode/SudokuSolver.xcodeproj/xcshareddata/xcbaselines/5054C851239FB90C0019BCA6.xcbaseline/Info.plist @@ -0,0 +1,40 @@ + + + + + runDestinationsByUUID + + 3E456181-7A01-4854-8E54-E451E60F64FA + + localComputer + + busSpeedInMHz + 400 + cpuCount + 1 + cpuKind + Quad-Core Intel Core i7 + cpuSpeedInMHz + 1700 + logicalCPUCoresPerPackage + 8 + modelCode + MacBookPro15,4 + physicalCPUCoresPerPackage + 4 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + targetDevice + + modelCode + iPhone12,3 + platformIdentifier + com.apple.platform.iphonesimulator + + + + + diff --git a/xcode/SudokuSolver/Assets.xcassets/Contents.json b/xcode/SudokuSolver/Assets.xcassets/Contents.json index da4a164..73c0059 100644 --- a/xcode/SudokuSolver/Assets.xcassets/Contents.json +++ b/xcode/SudokuSolver/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/xcode/SudokuSolver/Assets.xcassets/image1.imageset/Contents.json b/xcode/SudokuSolver/Assets.xcassets/image1.imageset/Contents.json new file mode 100644 index 0000000..a583449 --- /dev/null +++ b/xcode/SudokuSolver/Assets.xcassets/image1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xcode/SudokuSolver/Assets.xcassets/image1.imageset/image1.png b/xcode/SudokuSolver/Assets.xcassets/image1.imageset/image1.png new file mode 100644 index 0000000..6955e04 Binary files /dev/null and b/xcode/SudokuSolver/Assets.xcassets/image1.imageset/image1.png differ diff --git a/xcode/SudokuSolver/ContentView.swift b/xcode/SudokuSolver/ContentView.swift index 2664e83..34bf079 100644 --- a/xcode/SudokuSolver/ContentView.swift +++ b/xcode/SudokuSolver/ContentView.swift @@ -7,15 +7,24 @@ // import SwiftUI +import UIKit struct ContentView: View { + @EnvironmentObject var modelData: ModelData + var body: some View { - Text("Hello, World!") + VStack { + Text(modelData.sudokuSolver.gridify()) + } } + } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView() + Group { + ContentView() + .environmentObject(ModelData()) + } } } diff --git a/xcode/SudokuSolver/Input/input_1.txt b/xcode/SudokuSolver/Input/input_1.txt new file mode 100755 index 0000000..ff7524a --- /dev/null +++ b/xcode/SudokuSolver/Input/input_1.txt @@ -0,0 +1,9 @@ +8 0 0 0 0 0 0 0 0 +0 0 3 6 0 0 0 0 0 +0 7 0 0 9 0 2 0 0 +0 5 0 0 0 7 0 0 0 +0 0 0 0 4 5 7 0 0 +0 0 0 1 0 0 0 3 0 +0 0 1 0 0 0 0 6 8 +0 0 8 5 0 0 0 1 0 +0 9 0 0 0 0 4 0 0 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/input_2.txt b/xcode/SudokuSolver/Input/input_2.txt new file mode 100755 index 0000000..eb03b9d --- /dev/null +++ b/xcode/SudokuSolver/Input/input_2.txt @@ -0,0 +1,9 @@ +0 0 0 0 0 9 0 8 0 +0 0 2 0 0 4 5 3 0 +0 0 8 0 0 0 1 6 0 +6 0 0 1 0 0 3 0 0 +3 0 0 2 0 8 0 0 0 +0 0 0 0 0 7 9 5 0 +4 0 9 0 5 0 0 0 0 +0 0 0 3 0 6 0 0 0 +0 8 0 0 0 0 4 0 0 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/input_3.txt b/xcode/SudokuSolver/Input/input_3.txt new file mode 100755 index 0000000..0d44464 --- /dev/null +++ b/xcode/SudokuSolver/Input/input_3.txt @@ -0,0 +1,9 @@ +7 0 0 1 5 2 3 0 0 +0 0 0 0 0 0 9 2 0 +0 0 0 3 0 0 0 0 0 +1 0 0 0 0 4 7 0 8 +0 0 0 0 0 0 0 6 0 +0 0 0 0 0 0 0 0 0 +0 0 9 0 0 0 5 0 6 +0 4 0 9 0 7 0 0 0 +8 0 0 0 0 6 0 1 0 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/input_4.txt b/xcode/SudokuSolver/Input/input_4.txt new file mode 100755 index 0000000..9b02119 --- /dev/null +++ b/xcode/SudokuSolver/Input/input_4.txt @@ -0,0 +1,9 @@ +6 0 2 0 5 0 0 0 0 +0 0 0 0 0 3 0 4 0 +0 0 0 0 0 0 0 0 0 +4 3 0 0 0 8 0 0 0 +0 1 0 0 0 0 2 0 0 +0 0 0 0 0 0 7 0 0 +5 0 0 2 7 0 0 0 0 +0 0 0 0 0 0 0 8 1 +0 0 0 6 0 0 0 0 0 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/output_1.txt b/xcode/SudokuSolver/Input/output_1.txt new file mode 100755 index 0000000..90aff11 --- /dev/null +++ b/xcode/SudokuSolver/Input/output_1.txt @@ -0,0 +1,9 @@ +8 1 2 7 5 3 6 4 9 +9 4 3 6 8 2 1 7 5 +6 7 5 4 9 1 2 8 3 +1 5 4 2 3 7 8 9 6 +3 6 9 8 4 5 7 2 1 +2 8 7 1 6 9 5 3 4 +5 2 1 9 7 4 3 6 8 +4 3 8 5 2 6 9 1 7 +7 9 6 3 1 8 4 5 2 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/output_2.txt b/xcode/SudokuSolver/Input/output_2.txt new file mode 100755 index 0000000..40f34be --- /dev/null +++ b/xcode/SudokuSolver/Input/output_2.txt @@ -0,0 +1,9 @@ +7 5 3 6 1 9 2 8 4 +1 6 2 7 8 4 5 3 9 +9 4 8 5 2 3 1 6 7 +6 7 4 1 9 5 3 2 8 +3 9 5 2 6 8 7 4 1 +8 2 1 4 3 7 9 5 6 +4 3 9 8 5 1 6 7 2 +2 1 7 3 4 6 8 9 5 +5 8 6 9 7 2 4 1 3 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/output_3.txt b/xcode/SudokuSolver/Input/output_3.txt new file mode 100755 index 0000000..e133428 --- /dev/null +++ b/xcode/SudokuSolver/Input/output_3.txt @@ -0,0 +1,9 @@ +7 9 6 1 5 2 3 8 4 +5 3 1 4 6 8 9 2 7 +4 2 8 3 7 9 6 5 1 +1 5 2 6 3 4 7 9 8 +3 8 4 7 9 1 2 6 5 +9 6 7 2 8 5 1 4 3 +2 1 9 8 4 3 5 7 6 +6 4 5 9 1 7 8 3 2 +8 7 3 5 2 6 4 1 9 \ No newline at end of file diff --git a/xcode/SudokuSolver/Input/output_4.txt b/xcode/SudokuSolver/Input/output_4.txt new file mode 100755 index 0000000..a994d82 --- /dev/null +++ b/xcode/SudokuSolver/Input/output_4.txt @@ -0,0 +1,9 @@ +6 8 2 1 5 4 3 7 9 +9 5 1 7 6 3 8 4 2 +3 7 4 8 9 2 1 6 5 +4 3 7 5 2 8 9 1 6 +8 1 6 9 3 7 2 5 4 +2 9 5 4 1 6 7 3 8 +5 6 8 2 7 1 4 9 3 +7 2 9 3 4 5 6 8 1 +1 4 3 6 8 9 5 2 7 \ No newline at end of file diff --git a/xcode/SudokuSolver/ModelData.swift b/xcode/SudokuSolver/ModelData.swift new file mode 100644 index 0000000..2b693c6 --- /dev/null +++ b/xcode/SudokuSolver/ModelData.swift @@ -0,0 +1,31 @@ +// +// ModelData.swift +// SudokuSolver +// +// Created by Chen Hui on 7/1/21. +// Copyright © 2021 H. All rights reserved. +// + +import Foundation +import Combine + +final class ModelData: ObservableObject { + @Published var sudokuSolver = load("input_1.txt") + @Published var str = "hello" +} + +func load(_ filename: String) -> SudokuSolver { + guard let file = Bundle.main.url(forResource: filename, withExtension: nil) + else { + fatalError("Couldn't find \(filename) in main bundle.") + } + + do { + let input = try String(contentsOf: file, encoding: .utf8) + let sudoku = NormalSudoku(input: input) + let sudokuSolver = SudokuSolver(sudoku: sudoku) + return sudokuSolver + } catch { + fatalError("Couldn't parse \(filename) as \(SudokuSolver.self):\n\(error)") + } +} diff --git a/xcode/SudokuSolver/SceneDelegate.swift b/xcode/SudokuSolver/SceneDelegate.swift index d28e3c2..88eb0a5 100644 --- a/xcode/SudokuSolver/SceneDelegate.swift +++ b/xcode/SudokuSolver/SceneDelegate.swift @@ -9,7 +9,9 @@ import UIKit import SwiftUI +@available(iOS 14.0, *) class SceneDelegate: UIResponder, UIWindowSceneDelegate { + @StateObject private var modelData = ModelData() var window: UIWindow? @@ -23,7 +25,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // (see `application:configurationForConnectingSceneSession` instead). // Create the SwiftUI view that provides the window contents. - let contentView = ContentView() + let contentView = ContentView().environmentObject(modelData) // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { diff --git a/xcode/SudokuSolver/Structs/Cage.swift b/xcode/SudokuSolver/Structs/Cage.swift new file mode 100644 index 0000000..77bb08e --- /dev/null +++ b/xcode/SudokuSolver/Structs/Cage.swift @@ -0,0 +1,14 @@ +// +// Cage.swift +// SudokuSolver +// +// Created by Chen Hui on 8/1/21. +// Copyright © 2021 H. All rights reserved. +// + +struct Cage { + let cageID: Int + var cells: [Cell] + let val: Int + var combi: [String] +} diff --git a/xcode/SudokuSolver/Cell.swift b/xcode/SudokuSolver/Structs/Cell.swift similarity index 100% rename from xcode/SudokuSolver/Cell.swift rename to xcode/SudokuSolver/Structs/Cell.swift diff --git a/xcode/SudokuSolver/Sudoku.swift b/xcode/SudokuSolver/Sudoku.swift deleted file mode 100644 index 18e3264..0000000 --- a/xcode/SudokuSolver/Sudoku.swift +++ /dev/null @@ -1,9 +0,0 @@ -protocol Sudoku { - mutating func set(cell: Cell, to value: Int?) - - func options(in cell: Cell) -> Set - - var emptyCells: Set { get } - - var isSolved: Bool { get } -} diff --git a/xcode/SudokuSolver/Sudokus/KillerSudoku.swift b/xcode/SudokuSolver/Sudokus/KillerSudoku.swift new file mode 100644 index 0000000..bf478ce --- /dev/null +++ b/xcode/SudokuSolver/Sudokus/KillerSudoku.swift @@ -0,0 +1,390 @@ +// +// KillerSudoku.swift +// SudokuSolver +// +// Created by Chen Hui on 8/1/21. +// Copyright © 2021 H. All rights reserved. +// + +import Foundation + +let cheatSheet: [Int: [Int: String]] = [ + 2: [3: "12", + 4: "13", + 5: "14 23", + 6: "15 24", + 7: "16 25 34", + 8: "17 26 35", + 9: "18 27 36 45", + 10: "19 28 37 46", + 11: "29 38 47 56", + 12: "39 48 57", + 13: "49 58 67", + 14: "59 68", + 15: "69 78", + 16: "79", + 17: "89"], + + 3: [6: "123", + 7: "124", + 8: "125 134", + 9: "126 135 234", + 10: "127 136 145 235", + 11: "128 137 146 236 245", + 12: "129 138 147 156 237 246 345", + 13: "139 148 157 238 247 256 346", + 14: "149 158 167 239 248 257 347 356", + 15: "159 168 249 258 267 348 357 456", + 16: "169 178 259 268 349 358 367 457", + 17: "179 269 278 359 368 458 467", + 18: "189 279 369 378 459 468 567", + 19: "289 379 469 478 568", + 20: "389 479 569 578", + 21: "489 579 678", + 22: "589 679", + 23: "689", + 24: "789"], + + 4: [10: "1234", + 11: "1235", + 12: "1236 1245", + 13: "1237 1246 1345", + 14: "1238 1247 1256 1346 2345", + 15: "1239 1248 1257 1347 1356 2346", + 16: "1249 1258 1267 1348 1357 1456 2347 2356", + 17: "1259 1268 1349 1358 1367 1457 2348 2357 2456", + 18: "1269 1278 1359 1368 1458 1467 2349 2358 2367 2457 3456", + 19: "1279 1369 1378 1459 1468 1567 2359 2368 2458 2467 3457", + 20: "1289 1379 1469 1478 1568 2369 2378 2459 2468 2567 3458 3467", + 21: "1389 1479 1569 1578 2379 2469 2478 2568 3459 3468 3567", + 22: "1489 1579 1678 2389 2479 2569 2578 3469 3478 3568 4567", + 23: "1589 1679 2489 2579 2678 3479 3569 3578 4568", + 24: "1689 2589 2679 3489 3579 3678 4569 4578", + 25: "1789 2689 3589 3679 4579 4678", + 26: "2789 3689 4589 4679 5678", + 27: "3789 4689 5679", + 28: "4789 5689", + 29: "5789", + 30: "6789"], + + 5: [15: "12345", + 16: "12346", + 17: "12347 12356", + 18: "12348 12357 12456", + 19: "12349 12358 12367 12457 13456", + 20: "12359 12368 12458 12467 13457 23456", + 21: "12369 12378 12459 12468 12567 13458 13467 23457", + 22: "12379 12469 12478 12568 13459 13468 13567 23458 23467", + 23: "12389 12479 12569 12578 13469 13478 13568 14567 23459 23468 23567", + 24: "12489 12579 12678 13479 13569 13578 14568 23469 23478 23568 24567", + 25: "12589 12679 13489 13579 13678 14569 14578 23479 23569 23578 24568 34567", + 26: "12689 13589 13679 14579 14678 23489 23579 23678 24569 24578 34568", + 27: "12789 13689 14589 14679 15678 23589 23679 24579 24678 34569 34578", + 28: "13789 14689 15679 23689 24589 24679 25678 34579 34678", + 29: "14789 15689 23789 24689 25679 34589 34679 35678", + 30: "15789 24789 25689 34689 35679 45678", + 31: "16789 25789 34789 35689 45679", + 32: "26789 35789 45689", + 33: "36789 45789", + 34: "46789", + 35: "56789"], + + 6: [21: "123456", + 22: "123457", + 23: "123458 123467", + 24: "123459 123468 123567", + 25: "123469 123478 123568 124567", + 26: "123479 123569 123578 124568 134567", + 27: "123489 123579 123678 124569 124578 134568 234567", + 28: "123589 123679 124579 124678 134569 134578 234568", + 29: "123689 124589 124679 125678 134579 134678 234569 234578", + 30: "123789 124689 125679 134589 134679 135678 234579 234678", + 31: "124789 125689 134689 135679 145678 234589 234679 235678", + 32: "125789 134789 135689 145679 234689 235679 245678", + 33: "126789 135789 145689 234789 235689 245679 345678", + 34: "136789 145789 235789 245689 345679", + 35: "146789 236789 245789 345689", + 36: "156789 246789 345789", + 37: "256789 346789", + 38: "356789", + 39: "456789"], + + 7: [28: "1234567", + 29: "1234568", + 30: "1234569 1234578", + 31: "1234579 1234678", + 32: "1234589 1234679 1235678", + 33: "1234689 1235679 1245678", + 34: "1234789 1235689 1245679 1345678", + 35: "1235789 1245689 1345679 2345678", + 36: "1236789 1245789 1345689 2345679", + 37: "1246789 1345789 2345689", + 38: "1256789 1346789 2345789", + 39: "1356789 2346789", + 40: "1456789 2356789", + 41: "2456789", + 42: "3456789"], + + 8: [36: "12345678", + 37: "12345679", + 38: "12345689", + 39: "12345789", + 40: "12346789", + 41: "12356789", + 42: "12456789", + 43: "13456789", + 44: "23456789"], + + 9: [45: "123456789"] +] + +class KillerSudoku: Sudoku { + var assignedVariables: [Cell: Int] = [:] + var domain: [Cell: Set] = [:] + var neighbours: [Cell: [Cell]] = [:] + var cages: [Cage] = [] + var cellToCageID: [Cell: Int] = [:] + + init(input: String, cageValues: String) { + self.readInput(input: input, cageValues: cageValues) + self.generate_neighbours() + self.preprocess() + } + + private func readInput(input: String, cageValues: String) { + let row = input.split(separator: "\n") + let valueArray = cageValues.split(separator: " ") + + for iIndex in 0...8 { + let col = row[iIndex].split(separator: " ") + for jIndex in 0...8 { + let cageID = Int(col[jIndex])! + let newCell = Cell(row: iIndex, col: jIndex) + let len = cages.count + if len > cageID { + cages[cageID].cells.append(newCell) + } else { + let val = Int(valueArray[cageID])! + let newCage = Cage(cageID: cageID, cells: [newCell], val: val, combi: []) + cages.append(newCage) + + } + self.cellToCageID[newCell] = cageID + self.domain[newCell] = Set() + } + } + + } + + private func printCages() { + for cage in cages { + print("Cage id and val: ", cage.cageID, cage.val) + print("Cell") + for cell in cage.cells { + print(cell.row, cell.col) + + } + print("Combi:", cage.combi) + print() + } + } + + private func printNeighbours() { + for (cell, neighbours) in self.neighbours { + print("Cell row and col: ", cell.row, cell.col) + for neighbour in neighbours { + print(neighbour.row, neighbour.col, terminator: ", ") + } + print() + } + } + + private func generate_neighbours() { + for rIndex in 0...8 { + for cIndex in 0...8 { + self.neighbours[Cell(row: rIndex, col: cIndex)] = [] + } + } + for rIndex in 0...8 { + for cIndex in 0...8 { + for xIndex in 0...8 where xIndex != rIndex { + self.neighbours[Cell(row: rIndex, col: cIndex)]!.append(Cell(row: xIndex, col: cIndex)) + } + for yIndex in 0...8 where yIndex != cIndex { + self.neighbours[Cell(row: rIndex, col: cIndex)]!.append(Cell(row: rIndex, col: yIndex)) + } + let xStart = (rIndex / 3) * 3 + let yStart = (cIndex / 3) * 3 + for xIndex in xStart...(xStart + 2) { + for yIndex in yStart...(yStart + 2) { + if xIndex != rIndex || yIndex != cIndex { + self.neighbours[Cell(row: rIndex, col: cIndex)]!.append(Cell(row: xIndex, col: yIndex)) + } + } + } + } + } + } + + private func preprocess() { + for cage in self.cages { + let cells = cage.cells + let strArray = cheatSheet[cells.count]![cage.val]!.split(separator: " ") + for string in strArray { + self.cages[cage.cageID].combi.append(String(string)) + for character in string { + for cell in cells { + self.domain[cell]!.insert(Int(String(character))!) + } + } + } + } + } + + func select_unassigned_variable_and_domain() -> (Cell, Set) { + var minVar = Cell(row: 0, col: 0) + var minLen = 10 + for (cell, _) in self.domain { + let currLen = self.domain[cell]!.count + if minLen > currLen { + minLen = currLen + minVar = cell + } + } + return (minVar, self.domain[minVar]!) + } + + func least_constraining_values(cell: Cell, domain: Set) -> [(value: Int, count: Int)] { + var orderedDomain:[(value: Int, count: Int)] = [] + for value in domain { + // Count collisions for each value, collision occurs when that value appears in neighbour's domain + var count = 0 + for neighbour in self.neighbours[cell]! { + if self.domain[neighbour] != nil && self.domain[neighbour]!.contains(value) { + count += 1 + } + } + orderedDomain.append((value, count)) + } + orderedDomain = orderedDomain.sorted(by: take_min_count) + return orderedDomain + } + + private func take_min_count(first: (Int, Int), second: (Int, Int)) -> Bool { + let (_, count1) = first + let (_, count2) = second + return count1 < count2 + } + + func set(cell: Cell, to value: Int?) -> Set { + self.assignedVariables[cell] = value! + let removedDomain = self.domain[cell] + self.domain.removeValue(forKey: cell) + return removedDomain! + } + + func reset_assignment(cell: Cell, value: Int, removedDomain: Set) { + self.assignedVariables.removeValue(forKey: cell) + self.domain[cell] = removedDomain + } + + func inference(cell: Cell, value: Int) -> (Bool, [(Cell, Int)]) { + var removedValuesFromDomain: [(Cell, Int)] = [] // A list to keep track of discarded values in domain + for neighbour in self.neighbours[cell]! where self.domain[neighbour] != nil { + if self.domain[neighbour]!.contains(value) { + self.domain[neighbour]!.remove(value) + removedValuesFromDomain.append((neighbour, value)) + if self.domain[neighbour]!.isEmpty { + return (false, removedValuesFromDomain) + } + } + } + + var currentCombi = "" + let cage = self.cages[self.cellToCageID[cell]!] + for neighbour in cage.cells where self.assignedVariables[neighbour] != nil { + let val = self.assignedVariables[neighbour]! + currentCombi += String(val) + } + + var acceptableCombi: Set = Set() + for string in cage.combi { + var flag = true + for combiCharacter in currentCombi { + if !string.contains(Character(String(combiCharacter))) { + flag = false + } + } + if flag { + for character in string { + acceptableCombi.insert(character) + } + } + } + + for neighbour in cage.cells { + if cell != neighbour && self.domain[neighbour] != nil { + if self.domain[neighbour]!.contains(value) { + self.domain[neighbour]!.remove(value) + removedValuesFromDomain.append((neighbour, value)) + if self.domain[neighbour]!.isEmpty { + return (false, removedValuesFromDomain) + } + } + for value in self.domain[neighbour]! { + print("Values inside: ", value) + if !acceptableCombi.contains(Character(String(value))) { + self.domain[neighbour]!.remove(value) + print("Removed value: ", value, neighbour) + removedValuesFromDomain.append((neighbour, value)) + if self.domain[neighbour]!.isEmpty { + return (false, removedValuesFromDomain) + } + } + } + } + } + return (true, removedValuesFromDomain) + } + + func reset_domain(domainUpdates: [(Cell, Int)]) { + for (cell, value) in domainUpdates { + self.domain[cell]!.insert(value) + } + } + + func is_consistent_with_value(cell: Cell, value: Int) -> Bool { + for neighbour in self.neighbours[cell]! where self.assignedVariables[cell] != nil { + let neighbourValue = self.assignedVariables[neighbour] + if value == neighbourValue { + return false + } + } + return true + } + + var isSolved: Bool { + self.assignedVariables.count == 81 + } + + func gridify() -> String { + var grid = "" + for iIndex in 0...8 { + for jIndex in 0...8 { + let val = self.assignedVariables[Cell(row: iIndex, col: jIndex)] + if val != nil { + grid += String(val!) + } else { + grid += "0" + } + if jIndex != 8 { + grid += " " + } + } + if iIndex != 8 { + grid += "\n" + } + } + return grid + } +} diff --git a/xcode/SudokuSolver/Sudokus/NormalSudoku.swift b/xcode/SudokuSolver/Sudokus/NormalSudoku.swift new file mode 100644 index 0000000..244b182 --- /dev/null +++ b/xcode/SudokuSolver/Sudokus/NormalSudoku.swift @@ -0,0 +1,190 @@ +// +// NormalSudoku.swift +// SudokuSolver +// +// Created by Chen Hui on 4/1/21. +// Copyright © 2021 H. All rights reserved. +// + +import Foundation + +class NormalSudoku: Sudoku { + var assignedVariables: [Cell: Int] = [:] + var domain: [Cell: Set] = [:] + var neighbours: [Cell: [Cell]] = [:] + + init(input: String) { + self.readInput(input: input) + self.generate_neighbours() + self.preprocess() + } + + private func readInput(input: String) { + let row = input.split(separator: "\n") + for iIndex in 0...8 { + let col = row[iIndex].split(separator: " ") + for jIndex in 0...8 { + let newCell = Cell(row: iIndex, col: jIndex) + if Int(col[jIndex]) == 0 { + self.domain[newCell] = Set([1, 2, 3, 4, 5, 6, 7, 8, 9]) + } else { + self.assignedVariables[newCell] = Int(col[jIndex]) + } + } + } + } + + private func generate_neighbours() { + for rIndex in 0...8 { + for cIndex in 0...8 { + self.neighbours[Cell(row: rIndex, col: cIndex)] = [] + } + } + for rIndex in 0...8 { + for cIndex in 0...8 { + for xIndex in 0...8 where xIndex != rIndex { + self.neighbours[Cell(row: rIndex, col: cIndex)]!.append(Cell(row: xIndex, col: cIndex)) + } + for yIndex in 0...8 where yIndex != cIndex { + self.neighbours[Cell(row: rIndex, col: cIndex)]!.append(Cell(row: rIndex, col: yIndex)) + } + let xStart = (rIndex / 3) * 3 + let yStart = (cIndex / 3) * 3 + for xIndex in xStart...(xStart + 2) { + for yIndex in yStart...(yStart + 2) { + if xIndex != rIndex || yIndex != cIndex { + self.neighbours[Cell(row: rIndex, col: cIndex)]!.append(Cell(row: xIndex, col: yIndex)) + } + } + } + } + } + } + + private func preprocess() { + for (cell, var set) in self.domain { + let neighbourList = self.neighbours[cell] + for neighbourCell in neighbourList! where self.assignedVariables[neighbourCell] != nil { + let val = self.assignedVariables[neighbourCell]! + set.remove(val) + } + self.domain[cell] = set + } + } + + func select_unassigned_variable_and_domain() -> (Cell, Set) { + var minVar = Cell(row: 0, col: 0) + var minLen = 10 + for (cell, _) in self.domain { + let currLen = self.domain[cell]!.count + if minLen > currLen { + minLen = currLen + minVar = cell + } + } + return (minVar, self.domain[minVar]!) + } + + func least_constraining_values(cell: Cell, domain: Set) -> [(value: Int, count: Int)] { + var orderedDomain:[(value: Int, count: Int)] = [] + for value in domain { + // Count collisions for each value, collision occurs when that value appears in neighbour's domain + var count = 0 + for neighbour in self.neighbours[cell]! { + if self.domain[neighbour] != nil && self.domain[neighbour]!.contains(value) { + count += 1 + } + } + orderedDomain.append((value, count)) + } + orderedDomain = orderedDomain.sorted(by: take_min_count) + return orderedDomain + } + + private func take_min_count(first: (Int, Int), second: (Int, Int)) -> Bool { + let (_, count1) = first + let (_, count2) = second + return count1 < count2 + } + + func set(cell: Cell, to value: Int?) -> Set { + self.assignedVariables[cell] = value! + let removedDomain = self.domain[cell] + self.domain.removeValue(forKey: cell) + return removedDomain! + } + + func reset_assignment(cell: Cell, value: Int, removedDomain: Set) { + self.assignedVariables.removeValue(forKey: cell) + self.domain[cell] = removedDomain + } + + func inference(cell: Cell, value: Int) -> (Bool, [(Cell, Int)]) { + var removedValuesFromDomain: [(Cell, Int)] = [] // A list to keep track of discarded values in domain + for neighbour in self.neighbours[cell]! where self.domain[neighbour] != nil { + if self.domain[neighbour]!.contains(value) { + self.domain[neighbour]!.remove(value) + removedValuesFromDomain.append((neighbour, value)) + if self.domain[neighbour]!.isEmpty { + return (false, removedValuesFromDomain) + } + } + } + return (true, removedValuesFromDomain) + } + + func reset_domain(domainUpdates: [(Cell, Int)]) { + for (cell, value) in domainUpdates { + self.domain[cell]!.insert(value) + } + } + + func is_consistent_with_value(cell: Cell, value: Int) -> Bool { + for neighbour in self.neighbours[cell]! where self.assignedVariables[cell] != nil { + let neighbourValue = self.assignedVariables[neighbour] + if value == neighbourValue { + return false + } + } + return true + } + + var isSolved: Bool { + self.assignedVariables.count == 81 + } + + func gridify() -> String { + var grid = "" + for iIndex in 0...8 { + for jIndex in 0...8 { + let val = self.assignedVariables[Cell(row: iIndex, col: jIndex)] + if val != nil { + grid += String(val!) + } else { + grid += "0" + } + if jIndex != 8 { + grid += " " + } + } + if iIndex != 8 { + grid += "\n" + } + } + return grid + } + + func retrieveCells() -> [(Cell, Int)] { + Array(assignedVariables) + } + + private func printNeighbours() { + for (cell, neighbours) in self.neighbours { + print("Cell row and col: ", cell.row, cell.col) + for neighbour in neighbours { + print(neighbour.row, neighbour.col, terminator: ", ") + } + print() + } + } +} diff --git a/xcode/SudokuSolver/Sudokus/Sudoku.swift b/xcode/SudokuSolver/Sudokus/Sudoku.swift new file mode 100644 index 0000000..a64366e --- /dev/null +++ b/xcode/SudokuSolver/Sudokus/Sudoku.swift @@ -0,0 +1,19 @@ +protocol Sudoku { + mutating func set(cell: Cell, to value: Int?) -> Set + + func select_unassigned_variable_and_domain() -> (Cell, Set) + + func least_constraining_values(cell: Cell, domain: Set) -> [(value: Int, count: Int)] + + func reset_assignment(cell: Cell, value: Int, removedDomain: Set) + + func inference(cell: Cell, value: Int) -> (Bool, [(Cell, Int)]) + + func reset_domain(domainUpdates: [(Cell, Int)]) + + func is_consistent_with_value(cell: Cell, value: Int) -> Bool + + func gridify() -> String + + var isSolved: Bool { get } +} diff --git a/xcode/SudokuSolver/Sudokus/SudokuSolver.swift b/xcode/SudokuSolver/Sudokus/SudokuSolver.swift new file mode 100644 index 0000000..698c00e --- /dev/null +++ b/xcode/SudokuSolver/Sudokus/SudokuSolver.swift @@ -0,0 +1,48 @@ +// +// SudokuSolver.swift +// SudokuSolver +// +// Created by Chen Hui on 4/1/21. +// Copyright © 2021 H. All rights reserved. +// + +import Foundation + +class SudokuSolver { + var sudoku: Sudoku + + init(sudoku: Sudoku) { + self.sudoku = sudoku + } + + func solve() { + print("Solved? : ", backtrack(sudoku: sudoku)) + } + + func gridify() -> String { + sudoku.gridify() + } + + func backtrack(sudoku: Sudoku) -> Bool { + if sudoku.isSolved { + return true + } else { + let (cell, domain) = sudoku.select_unassigned_variable_and_domain() + let orderedDomain = sudoku.least_constraining_values(cell: cell, domain: domain) + for (value, _) in orderedDomain { + if sudoku.is_consistent_with_value(cell: cell, value: value) { + let removedDomain = self.sudoku.set(cell: cell, to: value) + let (inferSuccess, domainUpdates) = sudoku.inference(cell: cell, value: value) + if inferSuccess { + if backtrack(sudoku: sudoku) { + return true + } + } + sudoku.reset_domain(domainUpdates: domainUpdates) + sudoku.reset_assignment(cell: cell, value: value, removedDomain: removedDomain) + } + } + return false + } + } +} diff --git a/xcode/SudokuSolverTests/KillerSudokuTests.swift b/xcode/SudokuSolverTests/KillerSudokuTests.swift new file mode 100644 index 0000000..9dc0716 --- /dev/null +++ b/xcode/SudokuSolverTests/KillerSudokuTests.swift @@ -0,0 +1,180 @@ +// +// KillerSudokuTests.swift +// SudokuSolverTests +// +// Created by Chen Hui on 6/1/21. +// Copyright © 2021 H. All rights reserved. +// + +import XCTest +@testable import SudokuSolver + +class KillerSudokuTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + try super.setUpWithError() + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + try super.tearDownWithError() + } + + func testKillerSudoku1() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + let killerSudoku1 = KillerSudoku(input: """ + 00 00 01 02 03 03 04 04 05 + 06 00 01 02 02 07 08 08 05 + 06 09 10 10 11 07 12 12 13 + 09 09 11 11 11 14 15 15 13 + 16 16 16 14 14 14 15 15 15 + 17 16 16 14 18 18 18 19 19 + 17 20 20 21 18 22 22 19 23 + 24 25 25 21 26 26 27 28 23 + 24 29 29 30 30 26 27 28 28 + """, cageValues: "11 16 12 14 14 9 9 6 7 18 5 25 8 9 31 23 22 9 12 24 7 10 9 10 7 13 14 16 9 17 9") + + let solution1 = """ + 4 2 7 3 8 6 5 9 1 + 6 5 9 2 7 1 3 4 8 + 3 8 1 4 9 5 6 2 7 + 9 1 5 7 4 3 8 6 2 + 7 4 2 8 6 9 1 5 3 + 8 6 3 5 1 2 4 7 9 + 1 3 4 9 5 7 2 8 6 + 5 7 6 1 2 8 9 3 4 + 2 9 8 6 3 4 7 1 5 + """ + let solver1 = SudokuSolver(sudoku: killerSudoku1) + solver1.solve() + let ans1 = killerSudoku1.gridify() + XCTAssertEqual(ans1, solution1) + } + + func testKillerSudoku2() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + let killerSudoku2 = KillerSudoku(input: """ + 00 01 01 02 03 04 04 05 05 + 00 06 01 02 03 03 04 04 07 + 08 06 09 09 10 03 11 12 07 + 08 06 09 09 10 11 11 12 13 + 08 08 14 14 10 13 13 13 13 + 08 15 16 16 10 17 17 18 13 + 08 15 16 16 10 19 17 18 20 + 21 15 22 23 19 19 24 24 20 + 21 22 22 23 19 24 24 25 25 + """, cageValues: "13 19 13 19 10 14 7 9 27 16 30 18 13 34 12 16 23 11 6 18 9 10 18 6 24 10") + + let solution2 = """ + 5 9 3 7 1 2 4 8 6 + 8 4 7 6 5 9 1 3 2 + 1 2 6 3 8 4 5 9 7 + 3 1 2 5 9 6 7 4 8 + 9 5 4 8 2 7 3 6 1 + 7 6 8 1 4 3 2 5 9 + 2 3 5 9 7 8 6 1 4 + 6 7 9 4 3 1 8 2 5 + 4 8 1 2 6 5 9 7 3 + """ + let solver2 = SudokuSolver(sudoku: killerSudoku2) + solver2.solve() + let ans2 = killerSudoku2.gridify() + XCTAssertEqual(ans2, solution2) + } + + func testKillerSudoku3() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + let killerSudoku3 = KillerSudoku(input: """ + 00 00 00 01 01 02 02 03 03 + 04 00 00 05 05 06 07 07 03 + 04 00 08 08 08 06 09 07 10 + 04 11 12 12 13 13 09 10 10 + 04 11 14 14 13 09 09 15 16 + 17 17 14 13 13 18 18 15 16 + 17 19 14 20 21 21 21 22 16 + 23 19 19 20 24 24 22 22 16 + 23 23 25 25 26 26 22 22 22 + """, cageValues: "23 14 6 14 23 10 9 17 16 11 18 17 10 24 12 17 17 18 12 19 11 15 31 12 14 5 10") + + let solution3 = """ + 3 1 8 5 9 2 4 7 6 + 7 2 5 4 6 8 9 3 1 + 9 4 6 7 3 1 2 5 8 + 5 8 7 3 2 9 1 6 4 + 2 9 1 6 4 5 3 8 7 + 4 6 3 8 1 7 5 9 2 + 8 7 2 9 5 4 6 1 3 + 1 3 9 2 8 6 7 4 5 + 6 5 4 1 7 3 8 2 9 + """ + let solver3 = SudokuSolver(sudoku: killerSudoku3) + solver3.solve() + let ans3 = killerSudoku3.gridify() + XCTAssertEqual(ans3, solution3) + } + + func testKillerSudokuPerformance1() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + let killerSudoku1 = KillerSudoku(input: """ + 00 00 01 02 03 03 04 04 05 + 06 00 01 02 02 07 08 08 05 + 06 09 10 10 11 07 12 12 13 + 09 09 11 11 11 14 15 15 13 + 16 16 16 14 14 14 15 15 15 + 17 16 16 14 18 18 18 19 19 + 17 20 20 21 18 22 22 19 23 + 24 25 25 21 26 26 27 28 23 + 24 29 29 30 30 26 27 28 28 + """, cageValues: "11 16 12 14 14 9 9 6 7 18 5 25 8 9 31 23 22 9 12 24 7 10 9 10 7 13 14 16 9 17 9") + + let solver1 = SudokuSolver(sudoku: killerSudoku1) + solver1.solve() + } + } + + func testKillerSudokuPerformance2() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + let killerSudoku2 = KillerSudoku(input: """ + 00 01 01 02 03 04 04 05 05 + 00 06 01 02 03 03 04 04 07 + 08 06 09 09 10 03 11 12 07 + 08 06 09 09 10 11 11 12 13 + 08 08 14 14 10 13 13 13 13 + 08 15 16 16 10 17 17 18 13 + 08 15 16 16 10 19 17 18 20 + 21 15 22 23 19 19 24 24 20 + 21 22 22 23 19 24 24 25 25 + """, cageValues: "13 19 13 19 10 14 7 9 27 16 30 18 13 34 12 16 23 11 6 18 9 10 18 6 24 10") + let solver2 = SudokuSolver(sudoku: killerSudoku2) + solver2.solve() + } + } + + func testKillerSudokuPerformance3() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + let killerSudoku3 = KillerSudoku(input: """ + 00 00 00 01 01 02 02 03 03 + 04 00 00 05 05 06 07 07 03 + 04 00 08 08 08 06 09 07 10 + 04 11 12 12 13 13 09 10 10 + 04 11 14 14 13 09 09 15 16 + 17 17 14 13 13 18 18 15 16 + 17 19 14 20 21 21 21 22 16 + 23 19 19 20 24 24 22 22 16 + 23 23 25 25 26 26 22 22 22 + """, cageValues: "23 14 6 14 23 10 9 17 16 11 18 17 10 24 12 17 17 18 12 19 11 15 31 12 14 5 10") + let solver3 = SudokuSolver(sudoku: killerSudoku3) + solver3.solve() + } + } +} diff --git a/xcode/SudokuSolverTests/NormalSudokuTests.swift b/xcode/SudokuSolverTests/NormalSudokuTests.swift new file mode 100644 index 0000000..19c73aa --- /dev/null +++ b/xcode/SudokuSolverTests/NormalSudokuTests.swift @@ -0,0 +1,226 @@ +// +// NormalSudokuTests.swift +// SudokuSolverTests +// +// Created by Chen Hui on 6/1/21. +// Copyright © 2021 H. All rights reserved. +// + +import XCTest +@testable import SudokuSolver + +class NormalSudokuTests: XCTestCase { + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + try super.setUpWithError() + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + try super.tearDownWithError() + } + + func testNormalSudoku1() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + let normalSudoku1 = NormalSudoku(input: """ + 8 0 0 0 0 0 0 0 0 + 0 0 3 6 0 0 0 0 0 + 0 7 0 0 9 0 2 0 0 + 0 5 0 0 0 7 0 0 0 + 0 0 0 0 4 5 7 0 0 + 0 0 0 1 0 0 0 3 0 + 0 0 1 0 0 0 0 6 8 + 0 0 8 5 0 0 0 1 0 + 0 9 0 0 0 0 4 0 0 + """) + let solution1 = """ + 8 1 2 7 5 3 6 4 9 + 9 4 3 6 8 2 1 7 5 + 6 7 5 4 9 1 2 8 3 + 1 5 4 2 3 7 8 9 6 + 3 6 9 8 4 5 7 2 1 + 2 8 7 1 6 9 5 3 4 + 5 2 1 9 7 4 3 6 8 + 4 3 8 5 2 6 9 1 7 + 7 9 6 3 1 8 4 5 2 + """ + let solver1 = SudokuSolver(sudoku: normalSudoku1) + solver1.solve() + let ans1 = normalSudoku1.gridify() + XCTAssertEqual(ans1, solution1) + + } + + func testNormalSudoku2() throws { + let normalSudoku2 = NormalSudoku(input: """ + 0 0 0 0 0 9 0 8 0 + 0 0 2 0 0 4 5 3 0 + 0 0 8 0 0 0 1 6 0 + 6 0 0 1 0 0 3 0 0 + 3 0 0 2 0 8 0 0 0 + 0 0 0 0 0 7 9 5 0 + 4 0 9 0 5 0 0 0 0 + 0 0 0 3 0 6 0 0 0 + 0 8 0 0 0 0 4 0 0 + """) + let solution2 = """ + 7 5 3 6 1 9 2 8 4 + 1 6 2 7 8 4 5 3 9 + 9 4 8 5 2 3 1 6 7 + 6 7 4 1 9 5 3 2 8 + 3 9 5 2 6 8 7 4 1 + 8 2 1 4 3 7 9 5 6 + 4 3 9 8 5 1 6 7 2 + 2 1 7 3 4 6 8 9 5 + 5 8 6 9 7 2 4 1 3 + """ + let solver2 = SudokuSolver(sudoku: normalSudoku2) + solver2.solve() + let ans2 = normalSudoku2.gridify() + XCTAssertEqual(ans2, solution2) + } + + func testNormalSudoku3() throws { + let normalSudoku3 = NormalSudoku(input: """ + 7 0 0 1 5 2 3 0 0 + 0 0 0 0 0 0 9 2 0 + 0 0 0 3 0 0 0 0 0 + 1 0 0 0 0 4 7 0 8 + 0 0 0 0 0 0 0 6 0 + 0 0 0 0 0 0 0 0 0 + 0 0 9 0 0 0 5 0 6 + 0 4 0 9 0 7 0 0 0 + 8 0 0 0 0 6 0 1 0 + """) + let solution3 = """ + 7 9 6 1 5 2 3 8 4 + 5 3 1 4 6 8 9 2 7 + 4 2 8 3 7 9 6 5 1 + 1 5 2 6 3 4 7 9 8 + 3 8 4 7 9 1 2 6 5 + 9 6 7 2 8 5 1 4 3 + 2 1 9 8 4 3 5 7 6 + 6 4 5 9 1 7 8 3 2 + 8 7 3 5 2 6 4 1 9 + """ + let solver3 = SudokuSolver(sudoku: normalSudoku3) + solver3.solve() + let ans3 = normalSudoku3.gridify() + XCTAssertEqual(ans3, solution3) + } + + func testNormalSudoku4() throws { + let normalSudoku4 = NormalSudoku(input: """ + 6 0 2 0 5 0 0 0 0 + 0 0 0 0 0 3 0 4 0 + 0 0 0 0 0 0 0 0 0 + 4 3 0 0 0 8 0 0 0 + 0 1 0 0 0 0 2 0 0 + 0 0 0 0 0 0 7 0 0 + 5 0 0 2 7 0 0 0 0 + 0 0 0 0 0 0 0 8 1 + 0 0 0 6 0 0 0 0 0 + """) + let solution4 = """ + 6 8 2 1 5 4 3 7 9 + 9 5 1 7 6 3 8 4 2 + 3 7 4 8 9 2 1 6 5 + 4 3 7 5 2 8 9 1 6 + 8 1 6 9 3 7 2 5 4 + 2 9 5 4 1 6 7 3 8 + 5 6 8 2 7 1 4 9 3 + 7 2 9 3 4 5 6 8 1 + 1 4 3 6 8 9 5 2 7 + """ + let solver4 = SudokuSolver(sudoku: normalSudoku4) + solver4.solve() + let ans4 = normalSudoku4.gridify() + XCTAssertEqual(ans4, solution4) + } + + func testNormalSudokuPerformance1() throws { + // This is an example of a performance test case. + let sudoku = NormalSudoku(input: """ + 8 0 0 0 0 0 0 0 0 + 0 0 3 6 0 0 0 0 0 + 0 7 0 0 9 0 2 0 0 + 0 5 0 0 0 7 0 0 0 + 0 0 0 0 4 5 7 0 0 + 0 0 0 1 0 0 0 3 0 + 0 0 1 0 0 0 0 6 8 + 0 0 8 5 0 0 0 1 0 + 0 9 0 0 0 0 4 0 0 + """) + + let solver = SudokuSolver(sudoku: sudoku) + self.measure { + // Put the code you want to measure the time of here. + solver.solve() + } + } + + func testNormalSudokuPerformance2() throws { + // This is an example of a performance test case. + let sudoku = NormalSudoku(input: """ + 0 0 0 0 0 9 0 8 0 + 0 0 2 0 0 4 5 3 0 + 0 0 8 0 0 0 1 6 0 + 6 0 0 1 0 0 3 0 0 + 3 0 0 2 0 8 0 0 0 + 0 0 0 0 0 7 9 5 0 + 4 0 9 0 5 0 0 0 0 + 0 0 0 3 0 6 0 0 0 + 0 8 0 0 0 0 4 0 0 + """) + + let solver = SudokuSolver(sudoku: sudoku) + self.measure { + // Put the code you want to measure the time of here. + solver.solve() + } + } + + func testNormalSudokuPerformance3() throws { + // This is an example of a performance test case. + let sudoku = NormalSudoku(input: """ + 7 0 0 1 5 2 3 0 0 + 0 0 0 0 0 0 9 2 0 + 0 0 0 3 0 0 0 0 0 + 1 0 0 0 0 4 7 0 8 + 0 0 0 0 0 0 0 6 0 + 0 0 0 0 0 0 0 0 0 + 0 0 9 0 0 0 5 0 6 + 0 4 0 9 0 7 0 0 0 + 8 0 0 0 0 6 0 1 0 + """) + + let solver = SudokuSolver(sudoku: sudoku) + self.measure { + // Put the code you want to measure the time of here. + solver.solve() + } + } + + func testNormalSudokuPerformance4() throws { + // This is an example of a performance test case. + let sudoku = NormalSudoku(input: """ + 6 0 2 0 5 0 0 0 0 + 0 0 0 0 0 3 0 4 0 + 0 0 0 0 0 0 0 0 0 + 4 3 0 0 0 8 0 0 0 + 0 1 0 0 0 0 2 0 0 + 0 0 0 0 0 0 7 0 0 + 5 0 0 2 7 0 0 0 0 + 0 0 0 0 0 0 0 8 1 + 0 0 0 6 0 0 0 0 0 + """) + + let solver = SudokuSolver(sudoku: sudoku) + self.measure { + // Put the code you want to measure the time of here. + solver.solve() + } + } + +}