Skip to content

Instantly share code, notes, and snippets.

@curtclifton
Created October 15, 2015 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curtclifton/2ac04e88e186f52f2239 to your computer and use it in GitHub Desktop.
Save curtclifton/2ac04e88e186f52f2239 to your computer and use it in GitHub Desktop.
//: Zippers, based on http://learnyouahaskell.com/zippers
import UIKit
enum FileSystemItem {
case File(name: String, data: String)
case Folder(name: String, contents: [FileSystemItem])
var name: String {
switch self {
case let .File(name: name, data: _):
return name
case let .Folder(name: name, contents: _):
return name
}
}
}
let myDisk = FileSystemItem.Folder(name: "root", contents: [
.File(name: "goat_yelling_like_man.wmv", data: "baaaaaa"),
.File(name: "pope_time.avi", data: "god bless"),
.Folder(name: "pics", contents: [
.File(name: "ape_throwing_up.jpg", data: "bleargh"),
.File(name: "watermelon_smash.gif", data: "smash!!"),
.File(name: "skull_man(scary).bmp", data: "Yikes!"),
]),
.File(name: "dijon_poupon.doc", data: "best mustard"),
.Folder(name: "programs", contents:[
.File(name: "fartwizard.exe", data: "10gotofart"),
.File(name: "owl_bandit.dmg", data: "mov eax, h00t"),
.File(name: "not_a_virus.exe", data: "really not a virus"),
.Folder(name: "source code", contents: [
.File(name: "best_hs_prog.hs", data: "main = print (fix error)"),
.File(name: "random.hs", data: "main = print 4"),
]),
])
])
struct FileSystemCrumb {
let folderName: String
let precedingItems: [FileSystemItem]
let followingItems: [FileSystemItem]
}
enum FileSystemZipperError: ErrorType {
case IllegalMove(message: String)
case ItemNotFound(name: String)
}
// CCC, 10/14/2015. How should we handle failures? Since we're mutating the struct, we could just make currentItem an optional. Or we could make all the mutating functions throw?
struct FileSystemZipper {
private(set) var currentItem:FileSystemItem
// Unlike the Haskell implementation, the most recent breadcrumb goes at the end of the array
private var breadcrumbs: [FileSystemCrumb] = []
init(item: FileSystemItem) {
currentItem = item
}
mutating func moveUp() throws {
guard let latestCrumb = breadcrumbs.last else {
throw FileSystemZipperError.IllegalMove(message: "already at root node")
}
var newContents = latestCrumb.precedingItems
newContents.append(currentItem)
newContents.appendContentsOf(latestCrumb.followingItems)
let newFolder = FileSystemItem.Folder(name: latestCrumb.folderName, contents: newContents)
currentItem = newFolder
breadcrumbs.removeLast()
}
mutating func moveToTop() throws {
while !breadcrumbs.isEmpty {
try moveUp()
}
}
mutating func moveToName(name: String) throws {
switch currentItem {
case .File(_):
throw FileSystemZipperError.IllegalMove(message: "can only search by name in a folder")
case let .Folder(name: folderName, contents: contents):
let (maybeNewItem, precedingItems, followingItems) = contents.breakAt { $0.name == name }
guard let newItem = maybeNewItem else {
throw FileSystemZipperError.ItemNotFound(name: name)
}
let newCrumb = FileSystemCrumb(folderName: folderName, precedingItems: precedingItems, followingItems: followingItems)
currentItem = newItem
breadcrumbs.append(newCrumb)
}
}
mutating func renameToName(newName: String) {
switch currentItem {
case let .File(name: _, data: data):
currentItem = .File(name: newName, data: data)
case let .Folder(name: _, contents: contents):
currentItem = .Folder(name: newName, contents: contents)
}
}
mutating func addFollowingItem(newItem: FileSystemItem) throws {
guard let latestCrumb = breadcrumbs.last else {
throw FileSystemZipperError.IllegalMove(message: "no parent folder in which to add item")
}
breadcrumbs.removeLast()
var newPrecedingItems = latestCrumb.precedingItems
newPrecedingItems.append(currentItem)
let newLatestCrumb = FileSystemCrumb(folderName: latestCrumb.folderName, precedingItems: newPrecedingItems, followingItems: latestCrumb.precedingItems)
breadcrumbs.append(newLatestCrumb)
currentItem = newItem
}
mutating func addChildItem(newItem: FileSystemItem) throws {
switch currentItem {
case .File(_):
throw FileSystemZipperError.IllegalMove(message: "can only add child to a folder")
case .Folder(name: let name, contents: var contents):
contents.append(newItem)
currentItem = .Folder(name: name, contents: contents)
}
}
}
extension Array {
func breakAt(predicate: (Element) -> Bool) -> (Element?, [Element], [Element]) {
guard let matchIndex = self.indexOf(predicate) else {
// no match
return (nil, self, [])
}
return (self[matchIndex], Array(self.prefixUpTo(matchIndex)), Array(self.suffixFrom(matchIndex + 1)))
}
}
var newFocus = FileSystemZipper(item: myDisk)
try newFocus.moveToName("pics")
try newFocus.moveToName("skull_man(scary).bmp")
var newFocus2 = newFocus
try newFocus2.moveUp()
try newFocus2.moveToName("watermelon_smash.gif")
newFocus.currentItem
newFocus2.currentItem
try newFocus2.addFollowingItem(.File(name: "beagle.jpg", data: "daww!"))
try newFocus2.moveToTop()
print(newFocus2.currentItem)
var newFocus3 = FileSystemZipper(item: myDisk)
try newFocus3.moveToName("pics")
newFocus3.renameToName("images")
try newFocus3.addChildItem(.File(name: "horse.png", data: "of course, of course"))
try newFocus3.moveUp()
print(newFocus3.currentItem)
try newFocus3.moveToTop()
do {
try newFocus3.moveUp()
} catch {
print("bzzt")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment