├── .github └── workflows │ └── swift.yml ├── .gitignore ├── CHANGELOG.md ├── Demo ├── AppDelegate.swift ├── Base.lproj │ └── MainMenu.xib ├── CircuitBoard.swift ├── CircuitBoardConstraint.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── LayoutView.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── SwiftCSP │ ├── Backtrack.swift │ ├── CSP.swift │ ├── Constraint.swift │ └── MinConflicts.swift ├── SwiftCSP.png ├── SwiftCSP.podspec ├── SwiftCSP.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── Tests ├── Info.plist ├── LinuxMain.swift └── SwiftCSPTests ├── AustralianMapColoringTest.swift ├── EightQueensTest.swift ├── SendMoreMoneyTest.swift └── SudokuTest.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | .DS_Store 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.9.9 2 | - Added Sudoku Example Tests 3 | - Added Initial Version of a MinConflicts solver 4 | 5 | ### 0.9.8 6 | - LCV Heuristic 7 | - Changed to Apache License 8 | - Fixed a critical bug—`isSatisfied()` is now `open` thanks @Mighto360 9 | 10 | ### 0.9.7 11 | - Swift 5 Support 12 | 13 | ### 0.9.6 14 | - Swift 4 Support 15 | - Linux Testing Support 16 | - Reorganized Project Directory 17 | 18 | ### 0.9.5 19 | - Swift 3 Support 20 | 21 | ### 0.9.4 22 | - SPM support 23 | - Made example constraints non-generic 24 | - Small code fixes to be more Swifty 25 | - Removed Swift 2.2 deprecations 26 | 27 | ### 0.9.3 28 | 29 | ### 0.9.2 30 | Swift 2 support 31 | 32 | ### 0.9.1 33 | - Access control bug fix 34 | - Last release with Swift 1.2 support 35 | 36 | ### 0.9.0 37 | Initial Stable Release 38 | -------------------------------------------------------------------------------- /Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Cocoa 20 | 21 | @NSApplicationMain 22 | class AppDelegate: NSObject, NSApplicationDelegate { 23 | @IBOutlet weak var window: NSWindow! 24 | @IBOutlet weak var layoutView: LayoutView! 25 | @objc var circuitBoards: [CircuitBoard] = [] 26 | let boardWidth: Int = 20 27 | let boardHeight: Int = 20 28 | 29 | func applicationDidFinishLaunching(_ aNotification: Notification) { 30 | // Insert code here to initialize your application 31 | } 32 | 33 | func applicationWillTerminate(_ aNotification: Notification) { 34 | // Insert code here to tear down your application 35 | } 36 | 37 | @IBAction func solve(_ sender: AnyObject) { 38 | //create the CSP 39 | let variables = circuitBoards 40 | var domains: Dictionary = Dictionary() 41 | for variable in variables { 42 | domains[variable] = variable.generateDomain(boardWidth: boardWidth, boardHeight: boardHeight) 43 | } 44 | 45 | var cb_csp: CSP = CSP(variables: variables, domains: domains) 46 | 47 | //add constraints 48 | for i in 0.. Void in 69 | return 70 | }) 71 | 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Demo/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | Default 547 | 548 | 549 | 550 | 551 | 552 | 553 | Left to Right 554 | 555 | 556 | 557 | 558 | 559 | 560 | Right to Left 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | Default 572 | 573 | 574 | 575 | 576 | 577 | 578 | Left to Right 579 | 580 | 581 | 582 | 583 | 584 | 585 | Right to Left 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 830 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 863 | 880 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | -------------------------------------------------------------------------------- /Demo/CircuitBoard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircuitBoard.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Cocoa 20 | 21 | class CircuitBoard: NSObject { //get hashable for free and dynamic access 22 | @objc var height: Int = 1 23 | @objc var width: Int = 1 24 | @objc var color: NSColor = NSColor.red 25 | var location: (Int, Int)? 26 | 27 | //generate the domain as a list of tuples of bottom left corners 28 | func generateDomain(boardWidth: Int, boardHeight: Int) -> [(Int, Int)] { 29 | var domain: [(Int, Int)] = [] 30 | for x in 0..<(boardWidth - width + 1) { 31 | for y in 0..<(boardHeight - height + 1) { 32 | let temp = (x, y) 33 | domain.append(temp) 34 | } 35 | } 36 | return domain 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/CircuitBoardConstraint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircuitBoardConstraint.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Foundation 20 | 21 | //A binary constraint that makes sure two Chip variables do not overlap. 22 | class CircuitBoardConstraint: BinaryConstraint { 23 | 24 | override init(variable1: CircuitBoard, variable2: CircuitBoard) { 25 | super.init(variable1: variable1, variable2: variable2) 26 | //println(self.variable1.width) 27 | //println(self.variable2.width) 28 | } 29 | 30 | override func isSatisfied(assignment: Dictionary) -> Bool { 31 | //if either variable is not in the assignment then it must be consistent 32 | //since they still have their domain 33 | if assignment[variable1] == nil || assignment[variable2] == nil { 34 | return true 35 | } 36 | //check that var1 does not overlap var2 37 | let rect1 = CGRect(x: assignment[variable1]!.0, y: assignment[variable1]!.1, width: variable1.width, height: variable1.height) 38 | let rect2 = CGRect(x: assignment[variable2]!.0, y: assignment[variable2]!.1, width: variable2.width, height: variable2.height) 39 | return !rect1.intersects(rect2) 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2015 Oak Snow Consulting. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Demo/LayoutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LayoutView.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import Cocoa 20 | 21 | class LayoutView: NSView { 22 | let boxDimension: Int = 20 23 | var circuitBoards: [CircuitBoard] = [] 24 | 25 | override func draw(_ dirtyRect: NSRect) { 26 | super.draw(dirtyRect) 27 | 28 | // Drawing code here. 29 | let width = Int(self.frame.size.width) 30 | let height = Int(self.frame.size.height) 31 | 32 | //draw boards 33 | for board in circuitBoards { 34 | if let loc = board.location { 35 | board.color.set() 36 | let rect = CGRect(x: Int(loc.0 * (width/boxDimension)), y: Int(loc.1 * (height/boxDimension)), width: Int(board.width * (width/boxDimension)), height: Int(board.height * (height/boxDimension))) 37 | rect.fill() 38 | } 39 | } 40 | 41 | //draw grid 42 | let bPath:NSBezierPath = NSBezierPath() 43 | 44 | for i in 0...boxDimension { 45 | bPath.move(to: NSMakePoint(CGFloat((width/boxDimension) * i), CGFloat(0))) 46 | bPath.line(to: NSMakePoint(CGFloat((width/boxDimension) * i), CGFloat(height))) 47 | } 48 | for i in 0...boxDimension { 49 | bPath.move(to: NSMakePoint(CGFloat(0), CGFloat((height/boxDimension) * i))) 50 | bPath.line(to: NSMakePoint(CGFloat(width), CGFloat((height/boxDimension) * i))) 51 | } 52 | 53 | NSColor.black.set() 54 | bPath.stroke() 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftCSP", 7 | products: [ 8 | .library( 9 | name: "SwiftCSP", 10 | targets: ["SwiftCSP"]), 11 | ], 12 | dependencies: [], 13 | targets: [ 14 | .target( 15 | name: "SwiftCSP", 16 | dependencies: []), 17 | .testTarget( 18 | name: "SwiftCSPTests", 19 | dependencies: ["SwiftCSP"]), 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftCSP 2 | 3 | [![Swift Versions](https://img.shields.io/badge/Swift-1%2C2%2C3%2C4%2C5-green.svg)](https://swift.org) 4 | [![CocoaPods Version](https://img.shields.io/cocoapods/v/SwiftCSP.svg)](https://cocoapods.org/pods/SwiftCSP) 5 | [![SPM Compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/) 6 | [![CocoaPods Platforms](https://img.shields.io/cocoapods/p/SwiftCSP.svg)](https://cocoapods.org/pods/SwiftCSP) 7 | [![Linux Compatible](https://img.shields.io/badge/Linux-compatible-4BC51D.svg?style=flat)](https://swift.org) 8 | [![Twitter Contact](https://img.shields.io/badge/contact-@davekopec-blue.svg?style=flat)](https://twitter.com/davekopec) 9 | 10 | SwiftCSP is a constraint satisfaction problem solver written in pure Swift (no Cocoa). It utilizes a simple backtracking algorithm with optional standard heuristics to improve performance on some problems. At this stage of development, it's fairly slow but it includes examples of solving actual problems. It should run on all Swift platforms (iOS, OS X, Linux, tvOS, etc.). 11 | 12 | A [constraint satisfaction problem](https://en.wikipedia.org/wiki/Constraint_satisfaction_problem) is a problem composed of *variables* that have possible values (*domains*) and *constraints* on what those values can be. A solver finds a potential solution to that problem by selecting values from the domains of each variable that fit the constraints. For more information you should checkout Chapter 6 of Artificial Intelligence: A Modern Approach (Third Edition) by Norvig and Russell. 13 | 14 | ## Installation 15 | Use the cocoapod `SwiftCSP` or include the files in the Sources directory (`CSP.swift`, `Constraint.swift`, and `Backtrack.swift`) in your project. Alternatively, you can also install SwiftCSP through the Swift Package Manager (SPM) by pointing to this repository. Release 0.9.7 and above requires Swift 5. Use release 0.9.6 for Swift 4 support. Use release 0.9.5 for Swift 3 support. Use release 0.9.4 for Swift 2 support. For Swift 1.2 support use release 0.9 on CocoaPods or 0.9.1 on GitHub. 16 | 17 | ## Examples/Unit Tests 18 | The unit tests included with the project are also well known toy problems including: 19 | - [The Australian Map Coloring Problem](https://en.wikipedia.org/wiki/Four_color_theorem) 20 | - [Send + More = Money](https://en.wikipedia.org/wiki/Verbal_arithmetic) 21 | - [Eight Queens Problem](https://en.wikipedia.org/wiki/Eight_queens_puzzle) 22 | - [Sudoku](https://en.wikipedia.org/wiki/Sudoku) 23 | 24 | Looking at them should give you a good idea about how to use the library. In addition, the program included in the main project is a nice graphical example of the circuit board layout problem (it's also a great example of Cocoa Bindings on macOS). 25 | 26 | ## Usage 27 | You will need to create an instance of `CSP` and set its `variables` and `domains` at initialization. You will also need to subclass one of `Constraint`'s canonical subclasses: `UnaryConstraint`, `BinaryConstraint`, or `ListConstraint` and implement the `isSatisfied()` method. Then you will need to add instances of your `Constraint` subclass to the `CSP`. All of these classes make use of generics - specifically you should specify the type of the variables and the type of the domains. 28 | 29 | To solve your `CSP` you will call the function `backtrackingSearch()`. If your CSP is of any significant size, you will probably want to do this in an asynchronous block or background thread. You can also try the `minConflicts()` solver which is not as mature. 30 | 31 | ### Example 32 | Once again, I suggest looking at the unit tests, but here's a quick overview of what it's like to setup the eight queens problem: 33 | ``` 34 | let variables: [Int] = [0, 1, 2, 3, 4, 5, 6, 7] // create the variables, just Ints in this case 35 | var domains = Dictionary() // create the domain (also of Int type) 36 | for variable in variables { 37 | domains[variable] = [] 38 | for i in variable.stride(to: 64, by: 8) { 39 | domains[variable]?.append(i) 40 | } 41 | } 42 | 43 | csp = CSP(variables: variables, domains: domains) // initialize the previously defined CSP 44 | // Note that we specified through generics that its variables and domain are of type Int 45 | let smmc = EightQueensConstraint(variables: variables) // create a custom constraint 46 | // note that once again we specified both the variables and domain are of type Int 47 | csp?.addConstraint(smmc) // add the constraint 48 | ``` 49 | 50 | When subclassing a `Constraint` subclass, you will want to specify types for the superclass's generics as so: 51 | ``` 52 | final class EightQueensConstraint: ListConstraint 53 | ``` 54 | We therefore have a non-generic subclass of a generic superclass. 55 | 56 | ## Performance 57 | Performance is currently not great for problems with a medium size domain space. Profiling has shown a large portion of this may be attributable to the performance of Swift's native Dictionary type. Improved heuristics such as MAC3 are planned (spaces in the source code are left for them and contributions are welcome!) and should improve the situation. You can turn on the MRV or LCV heuristics (which are already implemented) when calling `backtrackingSearch()` to improve performance in many instances. In my testing MRV improves many searches, whereas the LCV implementation still leaves something to be desired, but may be useful in very specific problems. Note that these heuristics can also *decrease* performance for some problems. 58 | 59 | ## Generics 60 | SwiftCSP makes extensive use of generics. It seems like a lot of unnecessary angle brackets, but it allows the type checker to ensure variables fit with their domains and constraints. Due to a limitation in Swift generics, `Constraint` is a class instead of a protocol. 61 | 62 | ## Help Wanted 63 | Contributions that implement heuristics, improve performance in other ways, or simplify the design are more than welcome. Just make sure all of the unit tests still run and the new version maintains the flexibility of having any `Hashable` type as a variable and any type as a `Domain`. Additional unit tests are also welcome. A simple MinConflicts solver is also implemented, but could be improved. 64 | 65 | ## Authorship and License 66 | SwiftCSP was written by David Kopec and released under the Apache License (see `LICENSE`). It was originally a port of a Dart library I wrote called [constraineD](https://github.com/davecom/constraineD) which itself was a port of a Python library I wrote many years before that. 67 | -------------------------------------------------------------------------------- /Sources/SwiftCSP/Backtrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Backtrack.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | /// the meat of the backtrack algorithm - a recursive depth first search 20 | /// 21 | /// - parameter csp: The CSP to operate on. 22 | /// - parameter assignment: Optionally, an already partially completed assignment. 23 | /// - parameter mrv: Should it use the mrv heuristic to try to improve performance (default false) 24 | /// - parameter lcv: SHould it use the lcv heuristic to try to improve performance (default false) 25 | /// - parameter mac3: SHould it use the mac3 heuristic to try to improve performance (default false) NOT IMPLEMENTED YET 26 | /// - returns: the assignment (solution), or nil if none can be found 27 | public func backtrackingSearch(csp: CSP, assignment: Dictionary = Dictionary(), mrv: Bool = false, lcv: Bool = false, mac3: Bool = false) -> Dictionary? 28 | { 29 | // assignment is complete if it has as many assignments as there are variables 30 | if assignment.count == csp.variables.count { return assignment } 31 | 32 | // get a var to assign 33 | let variable = selectUnassignedVariable(csp: csp, assignment: assignment, mrv: mrv) 34 | 35 | // get the domain of it and try each value in the domain 36 | for value in orderDomainValues(variable: variable, assignment: assignment, csp: csp, lcv: lcv) { 37 | 38 | // if the value is consistent with the current assignment we continue 39 | var localAssignment = assignment 40 | localAssignment[variable] = value 41 | //println(assignment) 42 | if isConsistent(variable: variable, value: value, assignment: localAssignment, csp: csp) { 43 | //println("Found \(variable) with value \(value) and other assignment \(assignment) consistent") 44 | 45 | // do inferencing if we have that turned on 46 | if mac3 { 47 | /* 48 | inferences = inference(var, assignment, csp) 49 | #by design inferences will have assignments already made 50 | 51 | if (inferences != False): 52 | assignment = inferences 53 | result = backtrack(assignment, csp) 54 | 55 | if (result != False) return result; */ 56 | } else { 57 | if let result = backtrackingSearch(csp: csp, assignment: localAssignment, mrv: mrv, lcv: lcv, mac3: mac3) { 58 | return result 59 | } 60 | } 61 | } 62 | 63 | //substitution for removing everything 64 | //assignment = oldAssignment; 65 | } 66 | return nil //no solution 67 | } 68 | 69 | /// check if the value assignment is consistent by checking all constraints of the variable 70 | func isConsistent(variable: V, value: D, assignment: Dictionary, csp: CSP) -> Bool { 71 | for constraint in csp.constraints[variable]! { //assume there are constraints for every variable 72 | if !constraint.isSatisfied(assignment: assignment) { 73 | return false 74 | } 75 | } 76 | return true; 77 | } 78 | 79 | /// Return an unassigned variable - we may want to use some logic here to return the 80 | /// minimum-remaining values 81 | func selectUnassignedVariable(csp: CSP, assignment: Dictionary, mrv: Bool) -> V { 82 | // do we want to use the mrv heuristic 83 | if (mrv) { 84 | //get the one with the fewest remaining values 85 | var minRemainingValues: Int = Int.max 86 | var minVariable: V = csp.variables.first! 87 | for variable in csp.variables where assignment[variable] == nil { 88 | if csp.domains[variable]!.count < minRemainingValues { 89 | minRemainingValues = csp.domains[variable]!.count 90 | minVariable = variable 91 | } 92 | } 93 | return minVariable 94 | } else { //if not just pick the first one that comes up 95 | for variable in csp.variables where assignment[variable] == nil { 96 | return variable 97 | } 98 | } 99 | print("No unassigned variables") 100 | return csp.variables.first! //will crash if csp has no variables 101 | } 102 | 103 | /// get the domain variables in a good order 104 | func orderDomainValues(variable: V, assignment: Dictionary, csp: CSP, lcv: Bool) -> [D] { 105 | guard let domain = csp.domains[variable] else { return [] } 106 | if lcv { 107 | var domainValueCounts: [UInt] = [UInt](repeating: 0, count: domain.count) 108 | for (index, domainValue) in domain.enumerated() { 109 | for constraint in csp.constraints[variable] ?? [] { 110 | let constraintVariables = constraint.vars.filter{ $0 != variable } 111 | for constraintVariable in constraintVariables where assignment[constraintVariable] == nil { 112 | for cvDomainValue in csp.domains[constraintVariable] ?? [] { 113 | var testAssignment = assignment 114 | testAssignment[variable] = domainValue 115 | testAssignment[constraintVariable] = cvDomainValue 116 | if constraint.isSatisfied(assignment: testAssignment) { 117 | domainValueCounts[index] += 1 118 | } 119 | } 120 | } 121 | } 122 | } 123 | // use zip to combine the two arrays and sort that based on the first 124 | let combined = zip(domainValueCounts, domain).sorted {$0.0 > $1.0} 125 | return combined.map{ $0.1 } // return sorted order based on least constraining value 126 | } else { 127 | // return the domain in its original order 128 | return domain 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/SwiftCSP/CSP.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CSP.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | /// Defines a constraint satisfaction problem. V is the type of the variables and D is the type of the domains. 20 | public struct CSP { 21 | /// The variables in the CSP to be constrained. 22 | let variables: [V] 23 | /// The domains - every variable should have an associated domain. 24 | let domains: [V: [D]] 25 | /// The constraints on the variables. 26 | var constraints = Dictionary]>() 27 | 28 | /// You should create the variables and domains before initializing the CSP. 29 | public init (variables: [V], domains:[V: [D]]) { 30 | self.variables = variables 31 | self.domains = domains 32 | for variable in variables { 33 | constraints[variable] = [Constraint]() 34 | if domains[variable] == nil { 35 | print("Error: Missing domain for variable \(variable).", terminator: "") 36 | } 37 | } 38 | } 39 | 40 | /// Add a constraint to the CSP. It will automatically be applied to all the variables it includes. It should only include variables actually in the CSP. 41 | /// 42 | /// - parameter constraint: The constraint to add. 43 | public mutating func addConstraint(constraint: Constraint) { 44 | for variable in constraint.vars { 45 | if !variables.contains(variable) { 46 | print("Error: Could not find variable \(variable) from constraint \(constraint) in CSP.", terminator: "") 47 | } 48 | constraints[variable]?.append(constraint) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SwiftCSP/Constraint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constraint.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | 20 | /// The base class of all constraints. It usually makes more sense to override one of the canonical subclasses 21 | /// *UnaryConstraint*, *BinaryConstraint*, and *ListConstraint*. V is the type of the variables, and D is the type of the domains. 22 | open class Constraint { 23 | /// All subclasses should override this method. It defines whether a constraint has successfully been satisfied. 24 | /// 25 | /// - parameter assignment: Potential domain selections for variables that are part of the constraint. 26 | /// - returns: Whether the constraint is satisfied. 27 | open func isSatisfied(assignment: Dictionary) -> Bool { 28 | return true 29 | } 30 | /// The variables that make up the constraint. 31 | public var vars: [V] {return []} 32 | } 33 | 34 | /// A constraint on a single variable. 35 | open class UnaryConstraint : Constraint { 36 | /// The constrained variable. 37 | public let variable: V 38 | 39 | /// Override this constructor in subclasses. 40 | public init(variable: V) { 41 | self.variable = variable 42 | } 43 | 44 | /// A list of that single variable 45 | public final override var vars: [V] {return [variable]} 46 | } 47 | 48 | /// A constraint between two variables. 49 | open class BinaryConstraint : Constraint { 50 | /// The first variable 51 | public let variable1: V 52 | /// The second variable 53 | public let variable2: V 54 | 55 | /// Override this constructor in subclasses 56 | public init(variable1: V, variable2: V) { 57 | self.variable1 = variable1 58 | self.variable2 = variable2 59 | } 60 | 61 | /// A list of the first and second variables 62 | public final override var vars: [V] {return [variable1, variable2]} 63 | } 64 | 65 | /// A constraint between any number of variables 66 | open class ListConstraint : Constraint { 67 | /// The constrained variables 68 | public let variables: [V] 69 | 70 | /// Override this constructor in subclasses 71 | public init(variables: [V]) { 72 | self.variables = variables 73 | } 74 | 75 | /// Same as *variables* 76 | public final override var vars: [V] {return variables} 77 | } 78 | -------------------------------------------------------------------------------- /Sources/SwiftCSP/MinConflicts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MinConflicts.swift 3 | // SwiftCSP 4 | // 5 | // Copyright (c) 2022 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | /// Use the MinConflicts algorithm to try to resolve the CSP 20 | /// 21 | /// - parameter csp: The CSP to operate on. 22 | /// - parameter maxSteps: Maxmimum number of steps to try 23 | /// - parameter assignment: Optionally, an already full assignment, . 24 | /// - returns: the assignment (solution), and any remaining conflicted variables 25 | public func minConflicts(csp: CSP, maxSteps: Int = 100000, assignment: Dictionary?) -> (Dictionary, Array) 26 | { 27 | var current = (assignment != nil) ? assignment! : randomAssignment(csp: csp) 28 | for _ in 0..(csp: CSP, maxSteps: Int = 100000, assignment: Dictionary?, mrv: Bool = false, lcv: Bool = false, mac3: Bool = false) -> Dictionary? { 50 | // let (current, conflicted) = minConflicts(csp: csp, maxSteps: maxSteps, assignment: assignment) 51 | // if conflicted.isEmpty { 52 | // return current 53 | // } 54 | // // Incomplete assignment, so remove conflicting variables from it 55 | // let partial = current.filter { !conflicted.contains($0.0) } 56 | // return backtrackingSearch(csp: csp, assignment: partial, mrv: mrv, lcv: lcv, mac3: mac3) 57 | //} 58 | 59 | public func minimallyConflictedValue(csp: CSP, variable: V, assignment: Dictionary) -> D { 60 | let domain = csp.domains[variable]! 61 | var minimallyConstrained = domain[0] 62 | var minimumConflictCount = countConflicts(csp: csp, variable: variable, value: minimallyConstrained, assignment: assignment) 63 | for domainValue in domain.dropFirst() { 64 | let conflictCount = countConflicts(csp: csp, variable: variable, value: domainValue, assignment: assignment) 65 | if conflictCount < minimumConflictCount { 66 | minimumConflictCount = conflictCount 67 | minimallyConstrained = domainValue 68 | } 69 | } 70 | return minimallyConstrained 71 | } 72 | 73 | public func countConflicts(csp: CSP, variable: V, value: D, assignment: Dictionary) -> Int { 74 | var current = assignment 75 | current[variable] = value 76 | var count = 0 77 | for constraint in csp.constraints[variable]! { 78 | if !constraint.isSatisfied(assignment: current) { 79 | count += 1 80 | } 81 | } 82 | return count 83 | } 84 | 85 | public func conflictedVariables(csp: CSP, assignment: Dictionary) -> [V] { 86 | var conflicted: [V] = [] 87 | outer: for variable in csp.variables { 88 | for constraint in csp.constraints[variable]! { 89 | if !constraint.isSatisfied(assignment: assignment) { 90 | conflicted.append(variable) 91 | continue outer 92 | } 93 | } 94 | } 95 | return conflicted 96 | } 97 | 98 | public func randomAssignment(csp: CSP) -> Dictionary 99 | { 100 | var assignment = Dictionary() 101 | for variable in csp.variables { 102 | assignment[variable] = csp.domains[variable]!.randomElement() 103 | } 104 | return assignment 105 | } 106 | -------------------------------------------------------------------------------- /SwiftCSP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davecom/SwiftCSP/d6fc2a00133d7ff2a301f716c962131aced3bd11/SwiftCSP.png -------------------------------------------------------------------------------- /SwiftCSP.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftCSP' 3 | s.version = '0.9.9' 4 | s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } 5 | s.summary = 'A Constraint Satisfaction Problem Solver in Pure Swift' 6 | s.homepage = 'https://github.com/davecom/SwiftCSP' 7 | s.social_media_url = 'https://twitter.com/davekopec' 8 | s.authors = { 'David Kopec' => 'david@oaksnow.com' } 9 | s.source = { :git => 'https://github.com/davecom/SwiftCSP.git', :tag => s.version } 10 | s.swift_versions = ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6'] 11 | s.ios.deployment_target = '8.0' 12 | s.osx.deployment_target = '10.9' 13 | s.tvos.deployment_target = '10.0' 14 | s.watchos.deployment_target = '2.0' 15 | s.source_files = 'Sources/SwiftCSP/*.swift' 16 | s.requires_arc = true 17 | end 18 | -------------------------------------------------------------------------------- /SwiftCSP.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 55047EAC1B6158BB00309A30 /* Backtrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55047EAB1B6158BB00309A30 /* Backtrack.swift */; }; 11 | 55047EAD1B6158BB00309A30 /* Backtrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55047EAB1B6158BB00309A30 /* Backtrack.swift */; }; 12 | 55047EB01B616DE000309A30 /* SendMoreMoneyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55047EAE1B616DE000309A30 /* SendMoreMoneyTest.swift */; }; 13 | 550E6B001B604AF500CDE757 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550E6AFF1B604AF500CDE757 /* AppDelegate.swift */; }; 14 | 550E6B021B604AF500CDE757 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 550E6B011B604AF500CDE757 /* Images.xcassets */; }; 15 | 550E6B051B604AF500CDE757 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 550E6B031B604AF500CDE757 /* MainMenu.xib */; }; 16 | 550E6B1B1B604CC400CDE757 /* CSP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550E6B1A1B604CC400CDE757 /* CSP.swift */; }; 17 | 550E6B1D1B604E9600CDE757 /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550E6B1C1B604E9600CDE757 /* Constraint.swift */; }; 18 | 550E6B1E1B604E9600CDE757 /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550E6B1C1B604E9600CDE757 /* Constraint.swift */; }; 19 | 550E6B1F1B604E9A00CDE757 /* CSP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550E6B1A1B604CC400CDE757 /* CSP.swift */; }; 20 | 551266B828B555CE00F13BFC /* MinConflicts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551266B728B555CE00F13BFC /* MinConflicts.swift */; }; 21 | 551266B928B555CE00F13BFC /* MinConflicts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551266B728B555CE00F13BFC /* MinConflicts.swift */; }; 22 | 551266BB28B9515800F13BFC /* SudokuTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551266BA28B9515800F13BFC /* SudokuTest.swift */; }; 23 | 55CD29161B63189800DF47C5 /* AustralianMapColoringTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD29151B63189800DF47C5 /* AustralianMapColoringTest.swift */; }; 24 | 55CD29181B6327CA00DF47C5 /* EightQueensTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD29171B6327CA00DF47C5 /* EightQueensTest.swift */; }; 25 | 55CD291A1B63339B00DF47C5 /* CircuitBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD29191B63339B00DF47C5 /* CircuitBoard.swift */; }; 26 | 55CD291C1B6339BE00DF47C5 /* LayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD291B1B6339BE00DF47C5 /* LayoutView.swift */; }; 27 | 55CD291E1B63555000DF47C5 /* CircuitBoardConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD291D1B63555000DF47C5 /* CircuitBoardConstraint.swift */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 550E6B0B1B604AF500CDE757 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 550E6AF21B604AF500CDE757 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 550E6AF91B604AF500CDE757; 36 | remoteInfo = SwiftCSP; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 55047EAB1B6158BB00309A30 /* Backtrack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Backtrack.swift; path = Sources/SwiftCSP/Backtrack.swift; sourceTree = ""; }; 42 | 55047EAE1B616DE000309A30 /* SendMoreMoneyTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SendMoreMoneyTest.swift; path = Tests/SwiftCSPTests/SendMoreMoneyTest.swift; sourceTree = SOURCE_ROOT; }; 43 | 550E6AFA1B604AF500CDE757 /* SwiftCSP.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftCSP.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 550E6AFE1B604AF500CDE757 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Demo/Info.plist; sourceTree = ""; }; 45 | 550E6AFF1B604AF500CDE757 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Demo/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 46 | 550E6B011B604AF500CDE757 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Demo/Images.xcassets; sourceTree = SOURCE_ROOT; }; 47 | 550E6B041B604AF500CDE757 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 48 | 550E6B0A1B604AF500CDE757 /* SwiftCSPTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftCSPTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 550E6B0F1B604AF500CDE757 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; }; 50 | 550E6B1A1B604CC400CDE757 /* CSP.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CSP.swift; path = Sources/SwiftCSP/CSP.swift; sourceTree = ""; }; 51 | 550E6B1C1B604E9600CDE757 /* Constraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Constraint.swift; path = Sources/SwiftCSP/Constraint.swift; sourceTree = ""; }; 52 | 551266B728B555CE00F13BFC /* MinConflicts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MinConflicts.swift; path = Sources/SwiftCSP/MinConflicts.swift; sourceTree = ""; }; 53 | 551266BA28B9515800F13BFC /* SudokuTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SudokuTest.swift; path = Tests/SwiftCSPTests/SudokuTest.swift; sourceTree = SOURCE_ROOT; }; 54 | 55CD29151B63189800DF47C5 /* AustralianMapColoringTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AustralianMapColoringTest.swift; path = Tests/SwiftCSPTests/AustralianMapColoringTest.swift; sourceTree = SOURCE_ROOT; }; 55 | 55CD29171B6327CA00DF47C5 /* EightQueensTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EightQueensTest.swift; path = Tests/SwiftCSPTests/EightQueensTest.swift; sourceTree = SOURCE_ROOT; }; 56 | 55CD29191B63339B00DF47C5 /* CircuitBoard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CircuitBoard.swift; path = Demo/CircuitBoard.swift; sourceTree = SOURCE_ROOT; }; 57 | 55CD291B1B6339BE00DF47C5 /* LayoutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LayoutView.swift; path = Demo/LayoutView.swift; sourceTree = SOURCE_ROOT; }; 58 | 55CD291D1B63555000DF47C5 /* CircuitBoardConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CircuitBoardConstraint.swift; path = Demo/CircuitBoardConstraint.swift; sourceTree = SOURCE_ROOT; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 550E6AF71B604AF500CDE757 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 550E6B071B604AF500CDE757 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 550E6AF11B604AF500CDE757 = { 80 | isa = PBXGroup; 81 | children = ( 82 | 550E6B1A1B604CC400CDE757 /* CSP.swift */, 83 | 550E6B1C1B604E9600CDE757 /* Constraint.swift */, 84 | 55047EAB1B6158BB00309A30 /* Backtrack.swift */, 85 | 551266B728B555CE00F13BFC /* MinConflicts.swift */, 86 | 550E6AFC1B604AF500CDE757 /* SwiftCSP */, 87 | 550E6B0D1B604AF500CDE757 /* SwiftCSPTests */, 88 | 550E6AFB1B604AF500CDE757 /* Products */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | 550E6AFB1B604AF500CDE757 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 550E6AFA1B604AF500CDE757 /* SwiftCSP.app */, 96 | 550E6B0A1B604AF500CDE757 /* SwiftCSPTests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 550E6AFC1B604AF500CDE757 /* SwiftCSP */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 550E6AFF1B604AF500CDE757 /* AppDelegate.swift */, 105 | 55CD291D1B63555000DF47C5 /* CircuitBoardConstraint.swift */, 106 | 55CD29191B63339B00DF47C5 /* CircuitBoard.swift */, 107 | 55CD291B1B6339BE00DF47C5 /* LayoutView.swift */, 108 | 550E6B011B604AF500CDE757 /* Images.xcassets */, 109 | 550E6B031B604AF500CDE757 /* MainMenu.xib */, 110 | 550E6AFD1B604AF500CDE757 /* Supporting Files */, 111 | ); 112 | path = SwiftCSP; 113 | sourceTree = ""; 114 | }; 115 | 550E6AFD1B604AF500CDE757 /* Supporting Files */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 550E6AFE1B604AF500CDE757 /* Info.plist */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | 550E6B0D1B604AF500CDE757 /* SwiftCSPTests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 55047EAE1B616DE000309A30 /* SendMoreMoneyTest.swift */, 127 | 55CD29151B63189800DF47C5 /* AustralianMapColoringTest.swift */, 128 | 551266BA28B9515800F13BFC /* SudokuTest.swift */, 129 | 55CD29171B6327CA00DF47C5 /* EightQueensTest.swift */, 130 | 550E6B0E1B604AF500CDE757 /* Supporting Files */, 131 | ); 132 | path = SwiftCSPTests; 133 | sourceTree = ""; 134 | }; 135 | 550E6B0E1B604AF500CDE757 /* Supporting Files */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 550E6B0F1B604AF500CDE757 /* Info.plist */, 139 | ); 140 | name = "Supporting Files"; 141 | sourceTree = ""; 142 | }; 143 | /* End PBXGroup section */ 144 | 145 | /* Begin PBXNativeTarget section */ 146 | 550E6AF91B604AF500CDE757 /* SwiftCSP */ = { 147 | isa = PBXNativeTarget; 148 | buildConfigurationList = 550E6B141B604AF500CDE757 /* Build configuration list for PBXNativeTarget "SwiftCSP" */; 149 | buildPhases = ( 150 | 550E6AF61B604AF500CDE757 /* Sources */, 151 | 550E6AF71B604AF500CDE757 /* Frameworks */, 152 | 550E6AF81B604AF500CDE757 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = SwiftCSP; 159 | productName = SwiftCSP; 160 | productReference = 550E6AFA1B604AF500CDE757 /* SwiftCSP.app */; 161 | productType = "com.apple.product-type.application"; 162 | }; 163 | 550E6B091B604AF500CDE757 /* SwiftCSPTests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 550E6B171B604AF500CDE757 /* Build configuration list for PBXNativeTarget "SwiftCSPTests" */; 166 | buildPhases = ( 167 | 550E6B061B604AF500CDE757 /* Sources */, 168 | 550E6B071B604AF500CDE757 /* Frameworks */, 169 | 550E6B081B604AF500CDE757 /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | 550E6B0C1B604AF500CDE757 /* PBXTargetDependency */, 175 | ); 176 | name = SwiftCSPTests; 177 | productName = SwiftCSPTests; 178 | productReference = 550E6B0A1B604AF500CDE757 /* SwiftCSPTests.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | /* End PBXNativeTarget section */ 182 | 183 | /* Begin PBXProject section */ 184 | 550E6AF21B604AF500CDE757 /* Project object */ = { 185 | isa = PBXProject; 186 | attributes = { 187 | LastSwiftMigration = 0710; 188 | LastSwiftUpdateCheck = 0700; 189 | LastUpgradeCheck = 1020; 190 | ORGANIZATIONNAME = "Oak Snow Consulting"; 191 | TargetAttributes = { 192 | 550E6AF91B604AF500CDE757 = { 193 | CreatedOnToolsVersion = 6.4; 194 | LastSwiftMigration = 1020; 195 | }; 196 | 550E6B091B604AF500CDE757 = { 197 | CreatedOnToolsVersion = 6.4; 198 | LastSwiftMigration = 1020; 199 | TestTargetID = 550E6AF91B604AF500CDE757; 200 | }; 201 | }; 202 | }; 203 | buildConfigurationList = 550E6AF51B604AF500CDE757 /* Build configuration list for PBXProject "SwiftCSP" */; 204 | compatibilityVersion = "Xcode 3.2"; 205 | developmentRegion = en; 206 | hasScannedForEncodings = 0; 207 | knownRegions = ( 208 | en, 209 | Base, 210 | ); 211 | mainGroup = 550E6AF11B604AF500CDE757; 212 | productRefGroup = 550E6AFB1B604AF500CDE757 /* Products */; 213 | projectDirPath = ""; 214 | projectRoot = ""; 215 | targets = ( 216 | 550E6AF91B604AF500CDE757 /* SwiftCSP */, 217 | 550E6B091B604AF500CDE757 /* SwiftCSPTests */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | 550E6AF81B604AF500CDE757 /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 550E6B021B604AF500CDE757 /* Images.xcassets in Resources */, 228 | 550E6B051B604AF500CDE757 /* MainMenu.xib in Resources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | 550E6B081B604AF500CDE757 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXResourcesBuildPhase section */ 240 | 241 | /* Begin PBXSourcesBuildPhase section */ 242 | 550E6AF61B604AF500CDE757 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 55CD291E1B63555000DF47C5 /* CircuitBoardConstraint.swift in Sources */, 247 | 55047EAC1B6158BB00309A30 /* Backtrack.swift in Sources */, 248 | 550E6B1D1B604E9600CDE757 /* Constraint.swift in Sources */, 249 | 55CD291C1B6339BE00DF47C5 /* LayoutView.swift in Sources */, 250 | 55CD291A1B63339B00DF47C5 /* CircuitBoard.swift in Sources */, 251 | 550E6B1B1B604CC400CDE757 /* CSP.swift in Sources */, 252 | 551266B828B555CE00F13BFC /* MinConflicts.swift in Sources */, 253 | 550E6B001B604AF500CDE757 /* AppDelegate.swift in Sources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | 550E6B061B604AF500CDE757 /* Sources */ = { 258 | isa = PBXSourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | 55CD29181B6327CA00DF47C5 /* EightQueensTest.swift in Sources */, 262 | 55047EAD1B6158BB00309A30 /* Backtrack.swift in Sources */, 263 | 55CD29161B63189800DF47C5 /* AustralianMapColoringTest.swift in Sources */, 264 | 551266B928B555CE00F13BFC /* MinConflicts.swift in Sources */, 265 | 55047EB01B616DE000309A30 /* SendMoreMoneyTest.swift in Sources */, 266 | 550E6B1E1B604E9600CDE757 /* Constraint.swift in Sources */, 267 | 551266BB28B9515800F13BFC /* SudokuTest.swift in Sources */, 268 | 550E6B1F1B604E9A00CDE757 /* CSP.swift in Sources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXSourcesBuildPhase section */ 273 | 274 | /* Begin PBXTargetDependency section */ 275 | 550E6B0C1B604AF500CDE757 /* PBXTargetDependency */ = { 276 | isa = PBXTargetDependency; 277 | target = 550E6AF91B604AF500CDE757 /* SwiftCSP */; 278 | targetProxy = 550E6B0B1B604AF500CDE757 /* PBXContainerItemProxy */; 279 | }; 280 | /* End PBXTargetDependency section */ 281 | 282 | /* Begin PBXVariantGroup section */ 283 | 550E6B031B604AF500CDE757 /* MainMenu.xib */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | 550E6B041B604AF500CDE757 /* Base */, 287 | ); 288 | name = MainMenu.xib; 289 | path = Demo; 290 | sourceTree = SOURCE_ROOT; 291 | }; 292 | /* End PBXVariantGroup section */ 293 | 294 | /* Begin XCBuildConfiguration section */ 295 | 550E6B121B604AF500CDE757 /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 321 | CLANG_WARN_UNREACHABLE_CODE = YES; 322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 323 | CODE_SIGN_IDENTITY = "-"; 324 | COPY_PHASE_STRIP = NO; 325 | DEBUG_INFORMATION_FORMAT = dwarf; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | ENABLE_TESTABILITY = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu99; 329 | GCC_DYNAMIC_NO_PIC = NO; 330 | GCC_NO_COMMON_BLOCKS = YES; 331 | GCC_OPTIMIZATION_LEVEL = 0; 332 | GCC_PREPROCESSOR_DEFINITIONS = ( 333 | "DEBUG=1", 334 | "$(inherited)", 335 | ); 336 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | MACOSX_DEPLOYMENT_TARGET = 10.10; 344 | MTL_ENABLE_DEBUG_INFO = YES; 345 | ONLY_ACTIVE_ARCH = YES; 346 | SDKROOT = macosx; 347 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 348 | }; 349 | name = Debug; 350 | }; 351 | 550E6B131B604AF500CDE757 /* Release */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ALWAYS_SEARCH_USER_PATHS = NO; 355 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 357 | CLANG_CXX_LIBRARY = "libc++"; 358 | CLANG_ENABLE_MODULES = YES; 359 | CLANG_ENABLE_OBJC_ARC = YES; 360 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_COMMA = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 375 | CLANG_WARN_STRICT_PROTOTYPES = YES; 376 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 379 | CODE_SIGN_IDENTITY = "-"; 380 | COPY_PHASE_STRIP = NO; 381 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 382 | ENABLE_NS_ASSERTIONS = NO; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | GCC_C_LANGUAGE_STANDARD = gnu99; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 387 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 388 | GCC_WARN_UNDECLARED_SELECTOR = YES; 389 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 390 | GCC_WARN_UNUSED_FUNCTION = YES; 391 | GCC_WARN_UNUSED_VARIABLE = YES; 392 | MACOSX_DEPLOYMENT_TARGET = 10.10; 393 | MTL_ENABLE_DEBUG_INFO = NO; 394 | SDKROOT = macosx; 395 | }; 396 | name = Release; 397 | }; 398 | 550E6B151B604AF500CDE757 /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 402 | COMBINE_HIDPI_IMAGES = YES; 403 | INFOPLIST_FILE = Demo/Info.plist; 404 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 405 | PRODUCT_BUNDLE_IDENTIFIER = "com.oaksnow.$(PRODUCT_NAME:rfc1034identifier)"; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 5.0; 408 | }; 409 | name = Debug; 410 | }; 411 | 550E6B161B604AF500CDE757 /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 415 | COMBINE_HIDPI_IMAGES = YES; 416 | INFOPLIST_FILE = Demo/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = "com.oaksnow.$(PRODUCT_NAME:rfc1034identifier)"; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 421 | SWIFT_VERSION = 5.0; 422 | }; 423 | name = Release; 424 | }; 425 | 550E6B181B604AF500CDE757 /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | BUNDLE_LOADER = "$(TEST_HOST)"; 429 | COMBINE_HIDPI_IMAGES = YES; 430 | FRAMEWORK_SEARCH_PATHS = ( 431 | "$(DEVELOPER_FRAMEWORKS_DIR)", 432 | "$(inherited)", 433 | ); 434 | GCC_PREPROCESSOR_DEFINITIONS = ( 435 | "DEBUG=1", 436 | "$(inherited)", 437 | ); 438 | INFOPLIST_FILE = Tests/Info.plist; 439 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 440 | PRODUCT_BUNDLE_IDENTIFIER = "com.oaksnow.$(PRODUCT_NAME:rfc1034identifier)"; 441 | PRODUCT_NAME = "$(TARGET_NAME)"; 442 | SWIFT_VERSION = 5.0; 443 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftCSP.app/Contents/MacOS/SwiftCSP"; 444 | }; 445 | name = Debug; 446 | }; 447 | 550E6B191B604AF500CDE757 /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | BUNDLE_LOADER = "$(TEST_HOST)"; 451 | COMBINE_HIDPI_IMAGES = YES; 452 | FRAMEWORK_SEARCH_PATHS = ( 453 | "$(DEVELOPER_FRAMEWORKS_DIR)", 454 | "$(inherited)", 455 | ); 456 | INFOPLIST_FILE = Tests/Info.plist; 457 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 458 | PRODUCT_BUNDLE_IDENTIFIER = "com.oaksnow.$(PRODUCT_NAME:rfc1034identifier)"; 459 | PRODUCT_NAME = "$(TARGET_NAME)"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 461 | SWIFT_VERSION = 5.0; 462 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftCSP.app/Contents/MacOS/SwiftCSP"; 463 | }; 464 | name = Release; 465 | }; 466 | /* End XCBuildConfiguration section */ 467 | 468 | /* Begin XCConfigurationList section */ 469 | 550E6AF51B604AF500CDE757 /* Build configuration list for PBXProject "SwiftCSP" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | 550E6B121B604AF500CDE757 /* Debug */, 473 | 550E6B131B604AF500CDE757 /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | 550E6B141B604AF500CDE757 /* Build configuration list for PBXNativeTarget "SwiftCSP" */ = { 479 | isa = XCConfigurationList; 480 | buildConfigurations = ( 481 | 550E6B151B604AF500CDE757 /* Debug */, 482 | 550E6B161B604AF500CDE757 /* Release */, 483 | ); 484 | defaultConfigurationIsVisible = 0; 485 | defaultConfigurationName = Release; 486 | }; 487 | 550E6B171B604AF500CDE757 /* Build configuration list for PBXNativeTarget "SwiftCSPTests" */ = { 488 | isa = XCConfigurationList; 489 | buildConfigurations = ( 490 | 550E6B181B604AF500CDE757 /* Debug */, 491 | 550E6B191B604AF500CDE757 /* Release */, 492 | ); 493 | defaultConfigurationIsVisible = 0; 494 | defaultConfigurationName = Release; 495 | }; 496 | /* End XCConfigurationList section */ 497 | }; 498 | rootObject = 550E6AF21B604AF500CDE757 /* Project object */; 499 | } 500 | -------------------------------------------------------------------------------- /SwiftCSP.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftCSP.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftCSPTests 3 | 4 | XCTMain([ 5 | testCase(AustralianMapColoringTest.allTests), 6 | testCase(EightQueensTest.allTests), 7 | testCase(SendMoreMoneyTest.allTests), 8 | testCase(SudokuTest.allTests), 9 | ]) 10 | -------------------------------------------------------------------------------- /Tests/SwiftCSPTests/AustralianMapColoringTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AustralianMapColoringTest.swift 3 | // SwiftCSPTests 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | 20 | import XCTest 21 | @testable import SwiftCSP 22 | 23 | final class MapColoringConstraint: BinaryConstraint { 24 | 25 | init(place1: String, place2: String) { 26 | super.init(variable1: place1, variable2: place2) 27 | } 28 | 29 | override func isSatisfied(assignment: Dictionary) -> Bool { 30 | // if either variable is not in the assignment then it must be consistent 31 | // since they still have their domain 32 | if assignment[variable1] == nil || assignment[variable2] == nil { 33 | return true 34 | } 35 | // check that the color of var1 does not equal var2 36 | return assignment[variable1] != assignment[variable2] 37 | } 38 | } 39 | 40 | class AustralianMapColoringTest: XCTestCase { 41 | var csp: CSP? 42 | 43 | override func setUp() { 44 | super.setUp() 45 | // Put setup code here. This method is called before the invocation of each test method in the class. 46 | let variables: [String] = ["Western Australia", "Northern Territory", 47 | "South Australia", "Queensland", "New South Wales", "Victoria", "Tasmania"] 48 | var domains = Dictionary() 49 | for variable in variables { 50 | domains[variable] = ["r", "g", "b"] 51 | } 52 | 53 | csp = CSP(variables: variables, domains: domains) 54 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "Western Australia", place2: "Northern Territory")); 55 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "Western Australia", place2: "South Australia")); 56 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "South Australia", place2: "Northern Territory")); 57 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "Queensland", place2: "Northern Territory")); 58 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "Queensland", 59 | place2: "South Australia")); 60 | csp?.addConstraint(constraint:MapColoringConstraint(place1: "Queensland", place2: "New South Wales")); 61 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "New South Wales", place2: "South Australia")); 62 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "Victoria", place2: "South Australia")); 63 | csp?.addConstraint(constraint: MapColoringConstraint(place1: "Victoria",place2: "New South Wales")); 64 | } 65 | 66 | override func tearDown() { 67 | // Put teardown code here. This method is called after the invocation of each test method in the class. 68 | super.tearDown() 69 | } 70 | 71 | func testSolution() { 72 | // This is an example of a functional test case. 73 | guard let cs: CSP = csp else { 74 | XCTFail("Fail") 75 | return 76 | } 77 | 78 | if let solution = backtrackingSearch(csp: cs, mrv: false, lcv: false) { 79 | print(solution, terminator: "") 80 | XCTAssertEqual(solution, ["South Australia": "b", "New South Wales": "g", "Western Australia": "r", "Northern Territory": "g", "Victoria": "r", "Tasmania": "r", "Queensland": "r"], "Pass") 81 | } else { 82 | XCTFail("Fail") 83 | } 84 | } 85 | 86 | static var allTests = [ 87 | ("testSolution", testSolution) 88 | ] 89 | 90 | } 91 | -------------------------------------------------------------------------------- /Tests/SwiftCSPTests/EightQueensTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EightQueensTest.swift 3 | // SwiftCSPTests 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | import XCTest 20 | @testable import SwiftCSP 21 | 22 | final class EightQueensConstraint: ListConstraint { 23 | 24 | override init(variables: [Int]) { 25 | super.init(variables: variables) 26 | } 27 | 28 | override func isSatisfied(assignment: Dictionary) -> Bool { 29 | // not the most efficient check for attacking each other... 30 | // better to subtract one from the other and go from there 31 | for q in assignment.values { 32 | for i in (q - (q % 8)).. i % 8 else { break } 44 | if assignment.values.firstIndex(of: i) != nil { 45 | return false 46 | } 47 | } 48 | for i in stride(from: (q - 7), through: 0, by: -7) { // diagonal up and forward 49 | guard q % 8 < i % 8 else { break } 50 | if assignment.values.firstIndex(of: i) != nil { 51 | return false 52 | } 53 | } 54 | for i in stride(from: (q + 7), to: 64, by: 7) { // diagonal down and back 55 | guard i % 8 < q % 8 else { break } 56 | if assignment.values.firstIndex(of: i) != nil { 57 | return false 58 | } 59 | } 60 | for i in stride(from: (q + 9), to: 64, by: 9) { // diagonal down and forward 61 | guard q % 8 < i % 8 else { break } 62 | if assignment.values.firstIndex(of: i) != nil { 63 | return false 64 | } 65 | } 66 | } 67 | 68 | // until we have all of the variables assigned, the assignment is valid 69 | return true 70 | } 71 | } 72 | 73 | func drawQueens(solution: Dictionary) { 74 | var output = "\n" 75 | for i in 0..<64 { 76 | if (solution.values.firstIndex(of: i) != nil) { 77 | output += "Q" 78 | } else { 79 | output += "X" 80 | } 81 | if (i % 8 == 7) { 82 | output += "\n" 83 | } 84 | } 85 | print(output, terminator: ""); 86 | } 87 | 88 | class EightQueensTest: XCTestCase { 89 | var csp: CSP? 90 | 91 | override func setUp() { 92 | super.setUp() 93 | // Put setup code here. This method is called before the invocation of each test method in the class. 94 | let variables: [Int] = [0, 1, 2, 3, 4, 5, 6, 7] 95 | var domains = Dictionary() 96 | for variable in variables { 97 | domains[variable] = [] 98 | for i in stride(from: variable, to: 64, by: 8) { 99 | domains[variable]?.append(i) 100 | } 101 | } 102 | 103 | csp = CSP(variables: variables, domains: domains) 104 | let smmc = EightQueensConstraint(variables: variables) 105 | csp?.addConstraint(constraint: smmc) 106 | 107 | } 108 | 109 | override func tearDown() { 110 | // Put teardown code here. This method is called after the invocation of each test method in the class. 111 | super.tearDown() 112 | } 113 | 114 | func testSolution() { 115 | // This is an example of a functional test case. 116 | if let cs: CSP = csp { 117 | if let solution = backtrackingSearch(csp: cs, mrv: true, lcv: false) { 118 | print(solution, terminator: "") 119 | drawQueens(solution: solution) 120 | XCTAssertTrue(cs.constraints[0]![0].isSatisfied(assignment: solution)) 121 | } else { 122 | XCTFail("Fail") 123 | } 124 | } else { 125 | XCTFail("Fail") 126 | } 127 | } 128 | 129 | static var allTests = [ 130 | ("testSolution", testSolution) 131 | ] 132 | } 133 | -------------------------------------------------------------------------------- /Tests/SwiftCSPTests/SendMoreMoneyTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SendMoreMoneyTest.swift 3 | // SwiftCSPTests 4 | // 5 | // Copyright (c) 2015-2019 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | 20 | import XCTest 21 | @testable import SwiftCSP 22 | 23 | final class SendMoreMoneyConstraint: ListConstraint { 24 | 25 | override init(variables: [String]) { 26 | super.init(variables: variables) 27 | } 28 | 29 | override func isSatisfied(assignment: Dictionary) -> Bool { 30 | // if there are duplicate values then it's not correct 31 | let d = Set(assignment.values) 32 | if d.count < assignment.count { 33 | return false 34 | } 35 | 36 | // if all variables have been assigned, check if it adds up correctly 37 | if assignment.count == variables.count { 38 | if let s = assignment["S"], let e = assignment["E"], let n = assignment["N"], let d = assignment["D"], let m = assignment["M"], let o = assignment["O"], let r = assignment["R"], let y = assignment["Y"] { 39 | let send: Int = s * 1000 + e * 100 + n * 10 + d 40 | let more: Int = m * 1000 + o * 100 + r * 10 + e 41 | let money: Int = m * 10000 + o * 1000 + n * 100 + e * 10 + y 42 | if (send + more) == money { 43 | return true; 44 | } else { 45 | return false; 46 | } 47 | } else { 48 | return false 49 | } 50 | } 51 | 52 | // until we have all of the variables assigned, the assignment is valid 53 | return true 54 | } 55 | } 56 | 57 | class SendMoreMoneyTest: XCTestCase { 58 | var csp: CSP? 59 | 60 | override func setUp() { 61 | super.setUp() 62 | // Put setup code here. This method is called before the invocation of each test method in the class. 63 | let variables: [String] = ["S", "E", "N", "D", "M", "O", "R", "Y"] 64 | var domains = Dictionary() 65 | for variable in variables { 66 | domains[variable] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 67 | } 68 | domains["S"] = [1, 2, 3, 4, 5, 6, 7, 8, 9] 69 | domains["M"] = [1] 70 | 71 | csp = CSP(variables: variables, domains: domains) 72 | let smmc = SendMoreMoneyConstraint(variables: variables) 73 | csp?.addConstraint(constraint: smmc) 74 | 75 | } 76 | 77 | override func tearDown() { 78 | // Put teardown code here. This method is called after the invocation of each test method in the class. 79 | super.tearDown() 80 | } 81 | 82 | func testSolution() { 83 | // This is an example of a functional test case. 84 | if let cs: CSP = csp { 85 | if let solution = backtrackingSearch(csp: cs, mrv: true, lcv: false) { 86 | print(solution, terminator: "") 87 | 88 | if let s = solution["S"], let e = solution["E"], let n = solution["N"], let d = solution["D"], let m = solution["M"], let o = solution["O"], let r = solution["R"], let y = solution["Y"] { 89 | let send: Int = s * 1000 + e * 100 + n * 10 + d 90 | let more: Int = m * 1000 + o * 100 + r * 10 + e 91 | let money: Int = m * 10000 + o * 1000 + n * 100 + e * 10 + y 92 | print("\(send) + \(more) = \(money)", terminator: "") 93 | XCTAssertEqual((send + more), money, "Pass") 94 | } else { 95 | XCTFail("Fail") 96 | } 97 | } else { 98 | XCTFail("Fail") 99 | } 100 | } else { 101 | XCTFail("Fail") 102 | } 103 | } 104 | 105 | static var allTests = [ 106 | ("testSolution", testSolution) 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /Tests/SwiftCSPTests/SudokuTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SudokuTest.swift 3 | // SwiftCSPTests 4 | // 5 | // Copyright (c) 2022 David Kopec 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | 20 | import XCTest 21 | @testable import SwiftCSP 22 | 23 | final class SudokuConstraint: BinaryConstraint { 24 | 25 | init(place1: SudokuBoard.GridLocation, place2: SudokuBoard.GridLocation) { 26 | super.init(variable1: place1, variable2: place2) 27 | } 28 | 29 | override func isSatisfied(assignment: Dictionary) -> Bool { 30 | // if either variable is not in the assignment then it must be consistent 31 | // since they still have their domain 32 | if assignment[variable1] == nil || assignment[variable2] == nil { 33 | return true 34 | } 35 | // check that the number of var1 does not equal var2 36 | return assignment[variable1] != assignment[variable2] 37 | } 38 | } 39 | 40 | struct SudokuBoard { 41 | var grid: [[UInt8]] = Array>(repeating: Array(repeating: 0, count: 9), count: 9) 42 | 43 | struct GridLocation: Hashable { 44 | let row: UInt8 45 | let col: UInt8 46 | 47 | var neighbors: [GridLocation] { 48 | var ns = Set() 49 | // All in the same col 50 | for r in 0..<9 { 51 | ns.insert(GridLocation(row: UInt8(r), col: col)) 52 | } 53 | // All in the same row 54 | for c in 0..<9 { 55 | ns.insert(GridLocation(row: row, col: UInt8(c))) 56 | } 57 | // All in the same subsection 58 | for r in 0..<9 { 59 | for c in 0..<9 { 60 | if r / 3 == row / 3 && c / 3 == col / 3 { 61 | ns.insert(GridLocation(row: UInt8(r), col: UInt8(c))) 62 | } 63 | } 64 | } 65 | // no constraints with yourself 66 | ns.remove(self) 67 | return Array(ns) 68 | } 69 | } 70 | 71 | init(boardDescription: String) { 72 | // Parse the board 73 | for (row, line) in boardDescription.split(whereSeparator: \.isNewline).enumerated() { 74 | for (col, item) in line.enumerated() { 75 | let numeral = UInt8(item.wholeNumberValue ?? 0) 76 | if numeral < 10 { grid[row][col] = numeral } 77 | } 78 | } 79 | } 80 | 81 | // Tries to solve the puzzle and returns the solution grid 82 | func solve() -> [[UInt8]]? { 83 | var variables = [GridLocation]() 84 | var domains: [GridLocation: [UInt8]] = [GridLocation: [UInt8]]() 85 | for r in 0..<9 { 86 | for c in 0..<9 { 87 | let gl = GridLocation(row: UInt8(r), col: UInt8(c)) 88 | variables.append(gl) 89 | if grid[r][c] != 0 { 90 | domains[gl] = [grid[r][c]] 91 | } else { // 1 through 9 removing what cannot be 92 | domains[gl] = Array(1...9) 93 | // remove any values in the same column 94 | for neighbor in gl.neighbors { 95 | let nv = grid[Int(neighbor.row)][Int(neighbor.col)] 96 | if nv != 0 { 97 | domains[gl]?.removeAll(where: { $0 == nv }) 98 | } 99 | } 100 | } 101 | } 102 | } 103 | var csp = CSP(variables: variables, domains: domains) 104 | var addedConstraints: [(p1: GridLocation, p2: GridLocation)] = [] 105 | for r in 0..<9 { 106 | for c in 0..<9 { 107 | let gl = GridLocation(row: UInt8(r), col: UInt8(c)) 108 | for neighbor in gl.neighbors { 109 | // have to make sure to not add the same constraint in both directions 110 | if !addedConstraints.contains(where: { $0 == (p1: neighbor, p2: gl)}) { 111 | csp.addConstraint(constraint: SudokuConstraint(place1: gl, place2: neighbor)) 112 | addedConstraints.append((gl, neighbor)) 113 | } 114 | } 115 | } 116 | } 117 | 118 | //if let result = hybridSearch(csp: csp, maxSteps: 1000, assignment: nil) { 119 | if let result = backtrackingSearch(csp: csp) { 120 | //let (result, conflicted) = minConflicts(csp: csp, maxSteps: 1000000, assignment: nil) 121 | var solutionGrid: [[UInt8]] = Array>(repeating: Array(repeating: 0, count: 9), count: 9) 122 | for (key, value) in result { 123 | solutionGrid[Int(key.row)][Int(key.col)] = value 124 | } 125 | //print("Still Conflicted:") 126 | //print(conflicted) 127 | return solutionGrid 128 | } else { 129 | return nil 130 | } 131 | } 132 | } 133 | 134 | func printGrid(_ grid: [[UInt8]]) { 135 | for (row, line) in grid.enumerated() { 136 | if row % 3 == 0 && row != 0 { 137 | print("-----------") 138 | } 139 | var rowBuild: String = "" 140 | for (col, item) in line.enumerated() { 141 | if col % 3 == 0 && col != 0 { rowBuild += "|" } 142 | if item == 0 { 143 | rowBuild += "." 144 | } else { 145 | rowBuild += item.description 146 | } 147 | } 148 | print(rowBuild) 149 | } 150 | } 151 | 152 | class SudokuTest: XCTestCase { 153 | 154 | override func setUp() { 155 | super.setUp() 156 | } 157 | 158 | override func tearDown() { 159 | // Put teardown code here. This method is called after the invocation of each test method in the class. 160 | super.tearDown() 161 | } 162 | 163 | func testEasySolution1() { 164 | let puzzle = """ 165 | 2..1.5..3 166 | .54...71. 167 | .1.2.3.8. 168 | 6.28.73.4 169 | ......... 170 | 1.53.98.6 171 | .2.7.1.6. 172 | .81...24. 173 | 7..4.2..1 174 | """ 175 | let sb = SudokuBoard(boardDescription: puzzle) 176 | if let solutionGrid = sb.solve() { 177 | printGrid(solutionGrid) 178 | // just check all the rows have unique values... 179 | // should put some more checks in here, to make sure other constraints came out right 180 | for row in solutionGrid { 181 | let uniqueValues = Set(row) 182 | XCTAssertEqual(uniqueValues.count, row.count, "Not every item in row is unique.") 183 | } 184 | } else { 185 | XCTFail("Could not find solution to puzzle.") 186 | } 187 | 188 | } 189 | 190 | func testMediumSolution1() { 191 | let puzzle = """ 192 | .8..6.... 193 | ..2...18. 194 | .7...3... 195 | .....12.. 196 | .43....1. 197 | .51.76438 198 | .2....... 199 | .38...72. 200 | ..67.9.5. 201 | """ 202 | let sb = SudokuBoard(boardDescription: puzzle) 203 | if let solutionGrid = sb.solve() { 204 | printGrid(solutionGrid) 205 | // just check all the rows have unique values... 206 | // should put some more checks in here, to make sure other constraints came out right 207 | for row in solutionGrid { 208 | let uniqueValues = Set(row) 209 | XCTAssertEqual(uniqueValues.count, row.count, "Not every item in row is unique.") 210 | } 211 | } else { 212 | XCTFail("Could not find solution to puzzle.") 213 | } 214 | 215 | } 216 | 217 | func testHardSolution1() { 218 | let puzzle = """ 219 | ..1.....7 220 | .9...613. 221 | ...3....4 222 | .6..2.... 223 | ........1 224 | ..97..58. 225 | ..58..39. 226 | 8....7... 227 | .......4. 228 | """ 229 | let sb = SudokuBoard(boardDescription: puzzle) 230 | if let solutionGrid = sb.solve() { 231 | printGrid(solutionGrid) 232 | // just check all the rows have unique values... 233 | // should put some more checks in here, to make sure other constraints came out right 234 | for row in solutionGrid { 235 | let uniqueValues = Set(row) 236 | XCTAssertEqual(uniqueValues.count, row.count, "Not every item in row is unique.") 237 | } 238 | } else { 239 | XCTFail("Could not find solution to puzzle.") 240 | } 241 | 242 | } 243 | 244 | static var allTests = [ 245 | ("testEasySolution1", testEasySolution1), 246 | ("testMediumSolution1", testMediumSolution1), 247 | ("testHardSolution1", testHardSolution1), 248 | ] 249 | 250 | } 251 | --------------------------------------------------------------------------------