Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Created October 27, 2016 10:31
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 chriseidhof/b0fb6b0a4cf370e873dc56ee0a6ad42d to your computer and use it in GitHub Desktop.
Save chriseidhof/b0fb6b0a4cf370e873dc56ee0a6ad42d to your computer and use it in GitHub Desktop.
enum Block {
case code(code: String)
case text(String)
case header(String)
}
enum PlaygroundElement {
case code(String)
case documentation([Block])
}
func playground(blocks: [Block]) -> [PlaygroundElement] {
// We want to generate a playground from an array of `Block` elements.
//
// Separate the code from the documentation.
// All the consecutive non-code should be grouped together.
}
// The following input:
let sample: [Block] = [.code("swift sample code"), .header("My Header"), .text("Hello"), .code("more"), .text("text")]
// Should return the following result:
let result: [PlaygroundElement] = [
.code("swift sample code"),
.documentation[.header("My Header"), .text("Hello")]
.code("more"),
.documentation[.text("text")]
]
@AliSoftware
Copy link

AliSoftware commented Oct 27, 2016

Another variation:

func playground(blocks: [Block]) -> [PlaygroundElement] {
  return blocks.reduce([]) { (acc, block) in
    switch block {
    case .code(let code):
      return acc + [.code(code)]
    case .text, .header:
      if case .documentation(let blocks)? = acc.last {
        return acc.dropLast() + [.documentation(blocks + [block])]
      } else {
        return acc + [.documentation([block])]
      }
    }
  }
}

Agreed that using reduce with value types might not be very efficient, but in this case I find FP nicer than using mutability — and the solution with mutability has already been proposed by @nicklockwood on Twitter 😉

@JadenGeller
Copy link

It'd be easy if we had this useful function in the standard library.

extension Block {
    var isCode: Bool { 
        if case .code = self { return true } else { return false }    
    }
}

func playground(blocks: [Block]) -> [PlaygroundElement] {
    return blocks.cut { $0.isCode || $1.isCode }.map { 
        if case .code(let value) = $0.first! { 
            assert($0.count == 1)
            return .code(value) 
        } else {
            return .documentation(Array($0))
        }
    }
}

@odnoletkov
Copy link

func playground(blocks: [Block]) -> [PlaygroundElement] {
    return blocks.reduce([]) { res, block in
        switch (res.last, block) {

        case (_, .code(let code)):
            return res + [.code(code)]

        case (.some(.documentation(let blocks)), _):
            return res.dropLast() + [.documentation(blocks + [block])]

        default:
            return res + [.documentation([block])]
        }
    }
}

@jmcd
Copy link

jmcd commented Oct 27, 2016

First I did:

func playground(blocks: [Block]) -> [PlaygroundElement] {
    var playground = [PlaygroundElement]()
    for block in blocks {
        let ele: PlaygroundElement
        if case let .code(s) = block {
            ele = .code(s)
        } else if case let .documentation(existingDocBlocks)? = playground.last {
            playground.removeLast()
            ele = .documentation(existingDocBlocks + [block])
        } else {
            ele = .documentation([block])
        }
        playground.append(ele)
    }
    return playground
}

but I saw it looked quite like another suggestion, and I didn't like removing the last element from the array, so I tried another way:

extension Block {
    var shouldBeClassedAsDocumentation: Bool {
        switch self {
        case .code: return false
        default: return true
        }
    }
}

func playground(blocks: [Block]) -> [PlaygroundElement] {

    var playground = [PlaygroundElement]()

    for var idx in 0..<blocks.endIndex {

        let ele: PlaygroundElement

        if case let .code(s) = blocks[idx] {
            ele = .code(s)
        } else {
            let docEndIndex = blocks[idx..<blocks.endIndex].index { !$0.shouldBeClassedAsDocumentation }
            let docBlocks = Array(blocks[idx..<(docEndIndex ?? blocks.endIndex)])
            idx += docBlocks.count + 1
            ele = .documentation(docBlocks)
        }
        playground.append(ele)

    }

    return playground
}

@brunogb
Copy link

brunogb commented Nov 4, 2016

Using indirectEnums and also without mutating the original structs

import Foundation

enum Block {
    case code(String)
    case text(String)
    case header(String)
}

enum PlaygroundElement {
    case code(String)
    case documentation([Block])
}

indirect enum PlaygroundBlock {
    case code(String, PlaygroundBlock?)
    case documentation([Block], PlaygroundBlock?)
    
    init(block: Block, parent: PlaygroundBlock? = nil) {
        switch block {
        case let .code(value):
            self = .code(value, parent)
        default:
            self = .documentation([block], parent)
        }
    }
    
    mutating func process(block: Block)-> PlaygroundBlock {
        switch (self, block) {
        case (.code, _):
            return PlaygroundBlock(block: block, parent: self)
        case (.documentation, .code):
            return PlaygroundBlock(block: block, parent: self)
        case (.documentation(let (values, parent)), _):
            var newValues = [block]
            newValues.append(contentsOf: values)
            self = .documentation(newValues, parent)
            return self
        }
    }
    
    func flatMap()-> [PlaygroundElement] {
        switch self {
        case let .code(value, parent):
            var items: [PlaygroundElement] = [.code(value)]
            if let parent = parent {
                items.append(contentsOf: parent.flatMap())
            }
            return items
        case let .documentation(values, parent):
            var items: [PlaygroundElement] = [.documentation(values)]
            if let parent = parent {
                items.append(contentsOf: parent.flatMap())
            }
            return items
        }
    }
    
}

func playground(blocks: [Block]) -> [PlaygroundElement] {
    var consumableBlocks: [Block] = blocks
    guard let lastBlock = consumableBlocks.popLast() else { return [] }
    var lastLine = PlaygroundBlock(block: lastBlock)
    while let nextBlock = consumableBlocks.popLast() {
        lastLine = lastLine.process(block: nextBlock)
    }
    return lastLine.flatMap()
}

let sample: [Block] = [.code("swift sample code"), .header("My Header"), .text("Hello"), .code("more"), .text("text")]
let results = playground(blocks: sample)
results.count == 4
print(results)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment