0

How do you determine if this array has only a single lowest value?

let scoresExampleOne = [2, 2, 3, 4] // return false

let scoresExampleTwo = [2, 3, 3, 5] // return true 

"scoreValues" are embedded in a custom "Player" object.

I just tried to simplify it for the sake of this question.

1
  • 2
    Find the min. Then filter to the min. Then count. Commented Apr 5, 2021 at 21:05

3 Answers 3

3

All you need is to iterate your collection and keep track of the minimum value and if it repeats or not:

extension Collection {
    func minElement<T: Comparable>(_ predicate: (Element) -> T) -> (element: Element, single: Bool)? {
        guard var minElement = first else { return nil }
        var min = predicate(minElement)
        var single = true
        for element in dropFirst() {
            let value = predicate(element)
            if value > min { continue }
            if value < min {
                minElement = element
                min = value
                single = true
            } else {
                single = false
            }
        }
        return (minElement, single)
    }
    func min<T: Comparable>(_ predicate: (Element) -> T) -> (min: T, single: Bool)? {
        guard let (element, single) = minElement(predicate) else { return nil }
        return (predicate(element), single)
    }
}

Playground testing:

struct Player {
    let score: Int
}

let players1: [Player] = [.init(score: 2),
                          .init(score: 2),
                          .init(score: 3),
                          .init(score: 4)]
let players2: [Player] = [.init(score: 2),
                          .init(score: 3),
                          .init(score: 3),
                          .init(score: 5)]

let scoresExampleOne = players1.min(\.score)          // (min 2, single false)
let scoresExampleTwo = players2.min(\.score)          // (min 2, single true)
let scoresExampleThree = players1.minElement(\.score) // ({score 2}, single false)
let scoresExampleFour = players2.minElement(\.score)  // ({score 2}, single true)
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks Leo, this does exactly what I was looking for and more. Very nicely written code.
Just found out. Im still learning Swift, really like the way you used generics here in an extension, opens up a whole new world for me.
1
func isSingleLowestValue(scores: [Int]) -> Bool {
  guard let min = scores.min() else { return false }

  let minCount = scores.lazy.filter { $0 == min }.count
  return minCount == 1
}

2 Comments

This will unnecessarily iterate the whole collection twice. It would be much more efficient if you provide an early exit if the count exceeds one but it wouldn't avoid iterating it twice depending on the position of the minimum values
@LeoDabus The efficiency depends on the length of the array.
0

Leo's answer is good, but it's a special case of this extremum method.

public extension Sequence {
  /// The first element of the sequence.
  /// - Note: `nil` if the sequence is empty.
  var first: Element? {
    var iterator = makeIterator()
    return iterator.next()
  }

  /// - Parameters:
  ///   - comparable: The property to compare.
  ///   - areSorted: Whether the elements are in order, approaching the extremum.
  func extremum<Comparable: Swift.Comparable>(
    comparing comparable: (Element) throws -> Comparable,
    areSorted: (Comparable, Comparable) throws -> Bool
  ) rethrows -> Extremum<Element>? {
    try first.map { first in
      try dropFirst().reduce(into: .init(value: first, count: 1)) {
        let comparables = (try comparable($0.value), try comparable($1))

        if try areSorted(comparables.0, comparables.1) {
          $0 = .init(value: $1, count: 1)
        } else if (comparables.0 == comparables.1) {
          $0.count += 1
        }
      }
    }
  }

  /// - throws: `Extremum<Element>.UniqueError`
  func uniqueMin<Comparable: Swift.Comparable>(
    comparing comparable: (Element) throws -> Comparable
  ) throws -> Extremum<Element> {
    typealias Error = Extremum<Element>.UniqueError

    guard let extremum = try extremum(comparing: comparable, areSorted: >)
    else { throw Error.emptySequence }

    guard extremum.count == 1
    else { throw Error.notUnique(extremum) }

    return extremum
  }
}
public struct Extremum<Value> {
  enum UniqueError: Swift.Error {
    case emptySequence
    case notUnique(Extremum)
  }

  var value: Value
  var count: Int
}

2 Comments

Not everybody has two hands. That term of art is offensive.
No, experience has shown that it's not worth talking to them about it as an individual. It will take a big albeist controversy to change it, similar to how they switched from master to main. The names I like are .0 and .1.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.