Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Created October 27, 2016 10:31
Show Gist options
  • 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")]
]
@sunshinejr
Copy link

sunshinejr commented Oct 27, 2016

First idea that came to my mind:

func playground(blocks: [Block]) -> [PlaygroundElement] {
    return blocks.reduce([PlaygroundElement]()) { result, block -> [PlaygroundElement] in
        var newResult = result
        if case let Block.code(code) = block {
            newResult.append(PlaygroundElement.code(code))
        } else if case let PlaygroundElement.documentation(documentation)? = newResult.last {
            var newDocumentation = documentation
            newDocumentation.append(block)
            newResult[newResult.count - 1] = PlaygroundElement.documentation(newDocumentation)
        } else {
            let newDocumentation = PlaygroundElement.documentation([block])
            newResult.append(newDocumentation)
        }

        return newResult
    }
}

Using append instead of returning arrays wih +, because compile time ¯_(ツ)_/¯

@AndreiVidrasco
Copy link

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

    var playgroundElement: PlaygroundElement {
        switch self {
        case .code(let value):
            return .code(value)
        case .text:
            return .documentation([self])
        case .header:
            return .documentation([self])
        }
    }
}

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


    func combineDocumentation(element: PlaygroundElement) -> [PlaygroundElement] {
        guard let elementValue = element.documentValue() else { return [self, element] }
        guard let documentValue = documentValue() else { return [self, element] }

        return [.documentation(documentValue + elementValue)]
    }


    func documentValue() -> [Block]? {
        switch self {
        case .documentation(let value):
            return value
        default:
            return nil
        }
    }
}

func playground(blocks: [Block]) -> [PlaygroundElement] {
    return blocks.reduce([]) { (result, block) -> [PlaygroundElement] in
        let playgroundElement = block.playgroundElement
        guard result.count > 0 else { return [playgroundElement] }
        var mutableA = result
        let last = mutableA.removeLast()
        mutableA.appendContentsOf(last.combineDocumentation(playgroundElement))

        return mutableA
    }
}

let sample: [Block] = [.code(code: "swift sample code"), .header("My Header"), .text("Hello"), .code(code: "more"), .text("text")]

let result = playground(sample)

let a = result[0]
let b = result[1]
let c = result[2]
let d = result[3]

@AliSoftware
Copy link

AliSoftware commented Oct 27, 2016

My take:

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

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

func playground(blocks: [Block]) -> [PlaygroundElement] {
  func flush(_ buffer: [Block]) -> [PlaygroundElement] {
    return buffer.isEmpty ? [] : [.documentation(buffer)]
  }
  typealias Accum = (elements: [PlaygroundElement], buffer: [Block])
  let r = blocks.reduce(Accum([], [])) { (acc: Accum, block: Block) -> Accum in
    switch block {
    case .code(let code):
      return Accum(acc.elements + flush(acc.buffer) + [.code(code)], [])
    case .text, .header:
      return Accum(acc.elements, acc.buffer + [block])
    }
  }
  return r.elements + flush(r.buffer)
}

// The following input:
let sample: [Block] = [
  .code("swift sample code"), .header("My Header"), .text("Hello"), .code("more code"), .code("code"), .text("text")
]

// Should return the following result:
let expected: [PlaygroundElement] = [
  .code("swift sample code"),
  .documentation([.header("My Header"), .text("Hello")]),
  .code("more"),
  .code("code"),
  .documentation([.text("text")])
]

let result = playground(blocks: sample)
print(result)
print(expected)

[EDIT] Removed the useless lazy before reduce (I added it while trying other solutions where it made sense… before settling on this one)

@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