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

5 years ago
  1. //
  2. // SectionedValues.swift
  3. // Dwifft
  4. //
  5. // Created by Jack Flintermann on 4/14/17.
  6. // Copyright © 2017 jflinter. All rights reserved.
  7. //
  8. /// SectionedValues represents, well, a bunch of sections and their associated values.
  9. /// You can think of it sort of like an "ordered dictionary", or an order of key-pairs.
  10. /// If you are diffing a multidimensional structure of values (what might normally be,
  11. /// for example, a 2D array), you will want to use this.
  12. public struct SectionedValues<Section: Equatable, Value: Equatable>: Equatable {
  13. /// Initializes the struct with an array of key-pairs.
  14. ///
  15. /// - Parameter sectionsAndValues: An array of tuples. The first element in the tuple is
  16. /// the value of the section. The second element is an array of values to be associated with
  17. /// that section. Ordering matters, obviously. Note, it's totally ok if `sectionsAndValues`
  18. /// contains duplicate sections (or duplicate values within those sections).
  19. public init(_ sectionsAndValues: [(Section, [Value])] = []) {
  20. self.sectionsAndValues = sectionsAndValues
  21. }
  22. /// The underlying tuples contained in the receiver
  23. public let sectionsAndValues: [(Section, [Value])]
  24. internal var sections: [Section] { get { return self.sectionsAndValues.map { $0.0 } } }
  25. internal subscript(i: Int) -> (Section, [Value]) {
  26. return self.sectionsAndValues[i]
  27. }
  28. /// Returns a new SectionedValues appending a new key-value pair. I think this might be useful
  29. /// if you're building up a SectionedValues conditionally? (Well, I hope it is, anyway.)
  30. ///
  31. /// - Parameter sectionAndValue: the new key-value pair
  32. /// - Returns: a new SectionedValues containing the receiever's contents plus the new pair.
  33. public func appending(sectionAndValue: (Section, [Value])) -> SectionedValues<Section, Value> {
  34. return SectionedValues(self.sectionsAndValues + [sectionAndValue])
  35. }
  36. /// Compares two `SectionedValues` instances
  37. public static func ==(lhs: SectionedValues<Section, Value>, rhs: SectionedValues<Section, Value>) -> Bool {
  38. guard lhs.sectionsAndValues.count == rhs.sectionsAndValues.count else { return false }
  39. for i in 0..<(lhs.sectionsAndValues.count) {
  40. let ltuple = lhs.sectionsAndValues[i]
  41. let rtuple = rhs.sectionsAndValues[i]
  42. if (ltuple.0 != rtuple.0 || ltuple.1 != rtuple.1) {
  43. return false
  44. }
  45. }
  46. return true
  47. }
  48. }
  49. // MARK: - Custom grouping
  50. public extension SectionedValues where Section: Hashable {
  51. /// This is a convenience initializer of sorts for `SectionedValues`. It acknowledges
  52. /// that sometimes you have an array of things that are naturally "groupable" - maybe
  53. /// a list of names in an address book, that can be grouped into their first initial,
  54. /// or a bunch of events that can be grouped into buckets of timestamps. This will handle
  55. /// clumping all of your values into the correct sections, and ordering everything correctly.
  56. ///
  57. /// - Parameters:
  58. /// - values: All of the values that will end up in the `SectionedValues` you're making.
  59. /// - valueToSection: A function that maps each value to the section it will inhabit.
  60. /// In the above examples, this would take a name and return its first initial,
  61. /// or take an event and return its bucketed timestamp.
  62. /// - sortSections: A function that compares two sections, and returns true if the first
  63. /// should be sorted before the second. Used to sort the sections in the returned `SectionedValues`.
  64. /// - sortValues: A function that compares two values, and returns true if the first
  65. /// should be sorted before the second. Used to sort the values in each section of the returned `SectionedValues`.
  66. public init(
  67. values: [Value],
  68. valueToSection: ((Value) -> Section),
  69. sortSections: ((Section, Section) -> Bool),
  70. sortValues: ((Value, Value) -> Bool)) {
  71. var dictionary = [Section: [Value]]()
  72. for value in values {
  73. let section = valueToSection(value)
  74. var current = dictionary[section] ?? []
  75. current.append(value)
  76. dictionary[section] = current
  77. }
  78. let sortedSections = dictionary.keys.sorted(by: sortSections)
  79. self.init(sortedSections.map { section in
  80. let values = dictionary[section] ?? []
  81. let sortedValues = values.sorted(by: sortValues)
  82. return (section, sortedValues)
  83. })
  84. }
  85. }