You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
95 lines
4.5 KiB
95 lines
4.5 KiB
//
|
|
// SectionedValues.swift
|
|
// Dwifft
|
|
//
|
|
// Created by Jack Flintermann on 4/14/17.
|
|
// Copyright © 2017 jflinter. All rights reserved.
|
|
//
|
|
|
|
/// SectionedValues represents, well, a bunch of sections and their associated values.
|
|
/// You can think of it sort of like an "ordered dictionary", or an order of key-pairs.
|
|
/// If you are diffing a multidimensional structure of values (what might normally be,
|
|
/// for example, a 2D array), you will want to use this.
|
|
public struct SectionedValues<Section: Equatable, Value: Equatable>: Equatable {
|
|
|
|
/// Initializes the struct with an array of key-pairs.
|
|
///
|
|
/// - Parameter sectionsAndValues: An array of tuples. The first element in the tuple is
|
|
/// the value of the section. The second element is an array of values to be associated with
|
|
/// that section. Ordering matters, obviously. Note, it's totally ok if `sectionsAndValues`
|
|
/// contains duplicate sections (or duplicate values within those sections).
|
|
public init(_ sectionsAndValues: [(Section, [Value])] = []) {
|
|
self.sectionsAndValues = sectionsAndValues
|
|
}
|
|
|
|
/// The underlying tuples contained in the receiver
|
|
public let sectionsAndValues: [(Section, [Value])]
|
|
|
|
internal var sections: [Section] { get { return self.sectionsAndValues.map { $0.0 } } }
|
|
internal subscript(i: Int) -> (Section, [Value]) {
|
|
return self.sectionsAndValues[i]
|
|
}
|
|
|
|
|
|
/// Returns a new SectionedValues appending a new key-value pair. I think this might be useful
|
|
/// if you're building up a SectionedValues conditionally? (Well, I hope it is, anyway.)
|
|
///
|
|
/// - Parameter sectionAndValue: the new key-value pair
|
|
/// - Returns: a new SectionedValues containing the receiever's contents plus the new pair.
|
|
public func appending(sectionAndValue: (Section, [Value])) -> SectionedValues<Section, Value> {
|
|
return SectionedValues(self.sectionsAndValues + [sectionAndValue])
|
|
}
|
|
|
|
/// Compares two `SectionedValues` instances
|
|
public static func ==(lhs: SectionedValues<Section, Value>, rhs: SectionedValues<Section, Value>) -> Bool {
|
|
guard lhs.sectionsAndValues.count == rhs.sectionsAndValues.count else { return false }
|
|
for i in 0..<(lhs.sectionsAndValues.count) {
|
|
let ltuple = lhs.sectionsAndValues[i]
|
|
let rtuple = rhs.sectionsAndValues[i]
|
|
if (ltuple.0 != rtuple.0 || ltuple.1 != rtuple.1) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
// MARK: - Custom grouping
|
|
public extension SectionedValues where Section: Hashable {
|
|
|
|
/// This is a convenience initializer of sorts for `SectionedValues`. It acknowledges
|
|
/// that sometimes you have an array of things that are naturally "groupable" - maybe
|
|
/// a list of names in an address book, that can be grouped into their first initial,
|
|
/// or a bunch of events that can be grouped into buckets of timestamps. This will handle
|
|
/// clumping all of your values into the correct sections, and ordering everything correctly.
|
|
///
|
|
/// - Parameters:
|
|
/// - values: All of the values that will end up in the `SectionedValues` you're making.
|
|
/// - valueToSection: A function that maps each value to the section it will inhabit.
|
|
/// In the above examples, this would take a name and return its first initial,
|
|
/// or take an event and return its bucketed timestamp.
|
|
/// - sortSections: A function that compares two sections, and returns true if the first
|
|
/// should be sorted before the second. Used to sort the sections in the returned `SectionedValues`.
|
|
/// - sortValues: A function that compares two values, and returns true if the first
|
|
/// should be sorted before the second. Used to sort the values in each section of the returned `SectionedValues`.
|
|
public init(
|
|
values: [Value],
|
|
valueToSection: ((Value) -> Section),
|
|
sortSections: ((Section, Section) -> Bool),
|
|
sortValues: ((Value, Value) -> Bool)) {
|
|
var dictionary = [Section: [Value]]()
|
|
for value in values {
|
|
let section = valueToSection(value)
|
|
var current = dictionary[section] ?? []
|
|
current.append(value)
|
|
dictionary[section] = current
|
|
}
|
|
let sortedSections = dictionary.keys.sorted(by: sortSections)
|
|
self.init(sortedSections.map { section in
|
|
let values = dictionary[section] ?? []
|
|
let sortedValues = values.sorted(by: sortValues)
|
|
return (section, sortedValues)
|
|
})
|
|
}
|
|
|
|
}
|