Swift's Enum is powerful. Swift's enum and switch statements are my favorite of Swift's language specifications. Without this enum, I'm tired of Swift long ago. So powerful. Today I used it to implement a multidimensional mixed array.
MultiDimensionalArray.swift
// MARK: Definition
public enum MultiDimensionalArray<T> {
case val(T)
case ary([Self])
}
// MARK: Convinient Initializers
public extension MultiDimensionalArray {
static func ary(_ values: T...) -> Self {
.ary(values.map(Self.val))
}
static func ary(_ values: Self...) -> Self {
.ary(values)
}
}
// MARK: Functional
public extension MultiDimensionalArray {
func map<U>(_ transform: (T) throws -> U) rethrows -> MultiDimensionalArray<U> {
switch self {
case .val(let v):
return try .val(transform(v))
case .ary(let a):
return try .ary(a.map { try $0.map(transform) })
}
}
func flatMap<U>(_ transform: (T) throws -> MultiDimensionalArray<U>) rethrows -> MultiDimensionalArray<U> {
switch self {
case .val(let v):
return try transform(v)
case .ary(let a):
return try a
.map { try $0.flatMap(transform) }
.reduce(.empty, +)
}
}
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, T) throws -> Result) rethrows -> Result {
switch self {
case .val(let v):
return try nextPartialResult(initialResult, v)
case .ary(let a):
return try a.reduce(initialResult) { accum, next in
try next.reduce(accum, nextPartialResult)
}
}
}
}
// MARK: Monoid
public extension MultiDimensionalArray {
static func + (lhs: Self, rhs: Self) -> Self {
switch (lhs, rhs) {
case (.ary(let a), .ary(let b)):
return .ary(a + b)
case (_, .val(_)):
return lhs + .ary([rhs])
case (.val(_), _):
return .ary([lhs]) + rhs
}
}
static var empty: Self {
.ary([])
}
}
// MARK: Properties
public extension MultiDimensionalArray {
var count: Int {
switch self {
case .val(_):
return 1
case .ary(let a):
return a.count
}
}
var flatCount: Int {
switch self {
case .val(_):
return 1
case .ary(let a):
return a.map(\.flatCount).reduce(0, +)
}
}
var depth: Int {
switch self {
case .val(_):
return 0
case .ary(let a):
return (a.map(\.depth).max() ?? 0) + 1
}
}
var flatten: Self {
flatMap(Self.val)
}
var flattenArray: [T] {
flatten.map { [$0] }.reduce([], +)
}
}
// MARK: Index Accessibility
extension MultiDimensionalArray {
subscript(_ index: Int) -> Self {
self[safe: index]!
}
subscript(_ indices: [Int]) -> Self {
self[safe: indices]!
}
subscript(safe index: Int) -> Self? {
self[safe: [index]]
}
subscript(safe indices: [Int]) -> Self? {
switch (self, indices.splitted) {
case (.ary(let a), .some(let t)):
return a[t.head][safe: t.tail]
case (_, .none):
return self
default:
return .none
}
}
subscript(_ indices: Int...) -> Self {
self[indices]
}
subscript(safe indices: Int...) -> Self? {
self[safe: indices]
}
}
// MARK: Description
extension MultiDimensionalArray: CustomStringConvertible {
public var description: String {
switch self {
case .val(let v):
return "\(v)"
case .ary(let a):
return "[\(a.map(String.init).joined(separator: ", "))]"
}
}
}
// MARK: Equatable
extension MultiDimensionalArray: Equatable where T: Equatable {}
private extension Array {
var splitted: (head: Element, tail: [Element])? {
guard let f = first else { return .none }
return (f, dropFirst().map { $0 })
}
}
It is troublesome to explain how to use it, so I wrote a sample code.
TEST
let a: MultiDimensionalArray<Int> = .ary([.val(1), .val(2), .ary([.val(3), .val(4), .ary([.val(5), .val(6), .ary([]), .val(7)])]), .val(8)])
let b: MultiDimensionalArray<String> = .ary(.ary("apple", "banana"), .ary(.ary("gorilla", "ziraph")), .val("gorilla-gorilla-gorilla"))
let c: MultiDimensionalArray<String> = .val("zebra")
print(a)
print(a.map { $0 * 2 })
print(a.flatMap { .val($0 * 2) })
print(a.flatMap { .ary([.val($0), .val($0 * 2)]) })
print(a.flatMap { .ary([.ary([.val($0), .val($0 * 2)])]) })
print(MultiDimensionalArray.val(10).reduce(5, +))
print(a.count)
print(a.flatCount)
print(a.depth)
print(MultiDimensionalArray<Int>.ary([.ary([])]).depth)
print(MultiDimensionalArray.val(true).flatMap(MultiDimensionalArray.val))
print(MultiDimensionalArray.ary([.val(false)]).flatMap(MultiDimensionalArray.val))
print(b.map { $0.uppercased() }.flattenArray)
print(c.map { $0.uppercased() }.flattenArray)
print(b[1, 0, 1])
print(a == a)
print(b != (b + b))
By the way, the output looks like this.
Console
[1, 2, [3, 4, [5, 6, [], 7]], 8]
[2, 4, [6, 8, [10, 12, [], 14]], 16]
[2, 4, 6, 8, 10, 12, 14, 16]
[1, 2, 2, 4, 3, 6, 4, 8, 5, 10, 6, 12, 7, 14, 8, 16]
[[1, 2], [2, 4], [3, 6], [4, 8], [5, 10], [6, 12], [7, 14], [8, 16]]
15
4
8
4
2
true
[false]
["APPLE", "BANANA", "GORILLA", "ZIRAPH", "GORILLA-GORILLA-GORILLA"]
["ZEBRA"]
ziraph
true
true
By the way, I tried to implement an array normally with enum before. It was like this.
List.swift
// MARK: List
public enum List<Element> {
case empty
indirect case cons(Element, List<Element>)
}
// MARK: - Initializers
public extension List {
init(_ elements: Element...) {
self = .init(from: elements)
}
init<T: Collection>(from collection: T) where Element == T.Element {
self = collection.reversed().reduce(.empty) { .cons($1, $0) }
}
}
// MARK: - Implement Collection (Essential)
extension List: Collection {
public func index(after i: Int) -> Int { i + 1 }
public var startIndex: Int { 0 }
public var endIndex: Int { count }
public var count: Int {
switch self {
case .cons(_, let xs):
return xs.count + 1
case .empty:
return 0
}
}
}
// MARK: - Implement Collection (Additional)
extension List {
public __consuming func dropFirst(_ k: Int = 1) -> List<Element> {
return self[from: k]
}
public __consuming func reversed() -> List<Element> {
reduce(.empty) { .cons($1, $0) }
}
public func map<T>(_ transform: (Element) throws -> T) rethrows -> List<T> {
guard case let .cons(x, xs) = self else { return .empty }
do { return .cons(try transform(x), try xs.map(transform)) }
catch { return .empty }
}
public func flatMap<T>(_ transform: (Element) throws -> List<T>) rethrows -> List<T> {
guard case let .cons(x, xs) = self else { return .empty }
do { return try transform(x) + xs.flatMap(transform) }
catch { return .empty }
}
}
// MARK: - Implement subscript
public extension List {
subscript(index: Int) -> Element {
let elem = self[safe: index]
precondition(elem != nil, "Out of bounds")
return elem!
}
subscript(safe index: Int) -> Element? {
guard case let .cons(x, xs) = self else { return .none }
return index > 0 ? xs[safe: index - 1] : x
}
subscript(from index: Int) -> List<Element> {
guard index > 0 else { return self }
guard case let .cons(_, xs) = self else { return .empty }
return xs[from: index - 1]
}
}
// MARK: - Implement Equatable
extension List: Equatable where Element: Equatable {
public static func == (lhs: List<Element>, rhs: List<Element>) -> Bool {
lhs.count == rhs.count && zip(lhs, rhs).reduce(true) { $0 && $1.0 == $1.1 }
}
}
// MARK: - Implement CustomStringConvertible
extension List: CustomStringConvertible {
public var description: String {
return map { "\($0)" }.joined(separator: ", ")
}
}
// MARK: - Operator Support
public func +<T>(lhs: List<T>, rhs: List<T>) -> List<T> {
guard case let .cons(x, xs) = lhs else { return rhs }
if case .empty = xs { return .cons(x, rhs) }
return .cons(x, xs + rhs)
}
For a box to hold multiple elements, the first four lines are enough.
public enum List<Element> {
case empty
indirect case cons(Element, List<Element>)
}
Recommended Posts