Skip to content

Add count(where:) and tests #16099

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ set(SWIFT_BENCH_MODULES
single-source/Chars
single-source/ClassArrayGetter
single-source/Combos
single-source/CountAlgo
single-source/DataBenchmarks
single-source/DeadArray
single-source/DictOfArraysToArrayOfDicts
Expand Down
126 changes: 126 additions & 0 deletions benchmark/single-source/CountAlgo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//===--- CountAlgo.swift --------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import TestsUtils

public let CountAlgo = [
BenchmarkInfo(
name: "CountAlgoArray",
runFunction: run_CountAlgoArray,
tags: [.validation, .api]),
BenchmarkInfo(
name: "CountAlgoString",
runFunction: run_CountAlgoString,
tags: [.validation, .api]),
]

@inline(never)
public func run_CountAlgoArray(_ N: Int) {
for _ in 1...10*N {
CheckResults(numbers.count(where: { $0 & 4095 == 0 }) == 25)
}
}

@inline(never)
public func run_CountAlgoString(_ N: Int) {
let vowels = Set("aeiou")
for _ in 1...5*N {
CheckResults(text.count(where: vowels.contains) == 2014)
}
}

let numbers = Array(0..<100_000)

let text = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempus
dictum tellus placerat ultrices. Proin mauris risus, eleifend a elit ut,
semper consectetur nibh. Nulla ultricies est a vehicula rhoncus. Morbi
sollicitudin efficitur est a hendrerit. Interdum et malesuada fames ac ante
ipsum primis in faucibus. Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Nulla facilisi. Sed euismod sagittis laoreet. Ut elementum
tempus ultrices. Donec convallis mauris at faucibus maximus.
Nullam in nunc sit amet ante tristique elementum quis ut eros. Fusce
dignissim, ante at efficitur dapibus, ex massa convallis nibh, et venenatis
leo leo sit amet nisl. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Quisque sed mi eu mi rutrum accumsan vel non massa. Nunc condimentum,
arcu eget interdum hendrerit, ipsum mi pretium felis, ut mollis erat metus
non est. Donec eu sapien id urna lobortis eleifend et eu ipsum. Mauris
purus dolor, consequat ac nulla a, vehicula sollicitudin nulla.
Phasellus a congue diam. Curabitur sed orci at sem laoreet facilisis eget
quis est. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Maecenas justo tellus, efficitur id
velit at, mollis pellentesque mi. Vivamus maximus nibh et ipsum porttitor
facilisis. Curabitur cursus lobortis erat. Sed vitae eros et dolor feugiat
consequat. In ac massa in odio gravida dignissim. Praesent aliquam gravida
ullamcorper.
Etiam feugiat sit amet odio sed tincidunt. Duis dolor odio, posuere at
pretium sed, dignissim eu diam. Aenean eu convallis orci, vitae finibus
erat. Aliquam nec mollis tellus. Morbi luctus sed quam et vestibulum.
Praesent id diam tempus, consectetur tortor vel, auctor orci. Aliquam
congue ex eu sagittis sodales. Suspendisse non convallis nulla. Praesent
elementum semper augue, et fringilla risus ullamcorper id. Fusce eu lorem
sit amet augue fermentum tincidunt. In aliquam libero sit amet dui rhoncus,
ac scelerisque sem porttitor. Cras venenatis, nisi quis ullamcorper
dapibus, odio dolor rutrum magna, vel pellentesque sem lectus in tellus.
Proin faucibus leo iaculis nulla egestas molestie.
Phasellus vitae tortor vitae erat elementum feugiat vel vel enim. Phasellus
fringilla lacus sed venenatis dapibus. Phasellus sagittis vel neque ut
varius. Proin aliquam, lectus sit amet auctor finibus, lorem libero
pellentesque turpis, ac condimentum augue felis sit amet sem. Pellentesque
pharetra nisl nec est congue, in posuere felis maximus. In ut nulla
sodales, pharetra neque et, venenatis dui. Mauris imperdiet, arcu vel
hendrerit vehicula, elit massa consectetur purus, eu blandit nunc orci sit
amet turpis. Vestibulum ultricies id lorem id maximus. Pellentesque
feugiat, lacus et aliquet consequat, mi leo vehicula justo, dapibus dictum
mi quam convallis magna. Quisque id pulvinar dui, consequat gravida nisl.
Nam nec justo venenatis, tincidunt enim a, iaculis odio. Maecenas eget
lorem posuere, euismod nisl vel, pulvinar ex. Maecenas vitae risus ipsum.
Proin congue sem ante, sit amet sagittis odio mattis sit amet. Nullam et
nisi nulla.
Donec vel hendrerit metus. Praesent quis finibus erat. Aliquam erat
volutpat. Fusce sit amet ultricies tellus, vitae dictum dolor. Morbi auctor
dolor vel ligula pretium aliquam. Aenean lobortis vel magna vel ultricies.
Aenean porta urna vitae ornare porta. Quisque pretium dui diam, quis
iaculis odio venenatis non. Maecenas at lacus et ligula tincidunt feugiat
eu vel ipsum. Proin fermentum elit et quam tempus, eget pulvinar nisl
pharetra.
Mauris sodales tempus erat in lobortis. Duis vitae lacinia sapien.
Pellentesque vitae massa eget orci sodales aliquet. Orci varius natoque
penatibus et magnis dis parturient montes, nascetur ridiculus mus. Fusce
nisi arcu, egestas vel consectetur eu, auctor et metus. In ultricies ligula
felis, vitae pellentesque dolor tempor ac. Praesent mi magna, ultrices ut
ultrices vel, sollicitudin a leo.
Nam porta, nisi in scelerisque consequat, leo lacus accumsan massa,
venenatis faucibus tellus quam eget tellus. Curabitur pulvinar, tellus ac
facilisis consectetur, lacus lacus venenatis est, eu pretium orci augue
gravida nunc. Aenean odio tellus, facilisis et finibus id, varius vitae
diam. Aenean at suscipit sem. Suspendisse porta neque at nibh semper, sit
amet suscipit libero egestas. Donec commodo vitae justo vitae laoreet.
Suspendisse dignissim erat id ante maximus porta. Curabitur hendrerit
maximus odio, et maximus felis malesuada eu. Integer dapibus finibus diam,
quis convallis metus bibendum non.
In vel vulputate nisi, non lacinia nunc. Nullam vitae ligula finibus,
varius arcu in, pellentesque ipsum. Morbi vel velit tincidunt quam cursus
lacinia non in neque. Suspendisse id feugiat nibh. Vestibulum egestas eu
leo viverra fringilla. Curabitur ultrices sollicitudin libero, non sagittis
felis consectetur id. Aenean non metus eget leo ornare porta sed in metus.
Nullam quis fermentum sapien, sit amet sodales mi. Maecenas nec purus urna.
Phasellus condimentum enim nec magna convallis, eu lacinia libero
scelerisque. Suspendisse justo libero, maximus in auctor id, euismod quis
risus. Nam eget augue diam. Ut id risus pulvinar elit consectetur varius.
Aliquam tincidunt tortor pretium feugiat tempor. Nunc nec feugiat ex.
Ut pulvinar augue eget pharetra vehicula. Phasellus malesuada tempor sem,
ut tincidunt velit convallis in. Vivamus luctus libero vitae massa tempus,
id elementum urna iaculis. Sed eleifend quis purus quis convallis. In
rhoncus interdum mollis. Pellentesque dictum euismod felis, eget lacinia
elit blandit vel. Praesent elit velit, pharetra a sodales in, cursus vitae
tortor. In vitae scelerisque tellus.
"""
2 changes: 2 additions & 0 deletions benchmark/utils/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import CharacterProperties
import Chars
import ClassArrayGetter
import Combos
import CountAlgo
import DataBenchmarks
import DeadArray
import DictOfArraysToArrayOfDicts
Expand Down Expand Up @@ -195,6 +196,7 @@ registerBenchmark(CharacterPropertiesStashedMemo)
registerBenchmark(CharacterPropertiesPrecomputed)
registerBenchmark(Chars)
registerBenchmark(Combos)
registerBenchmark(CountAlgo)
registerBenchmark(ClassArrayGetter)
registerBenchmark(DataBenchmarks)
registerBenchmark(DeadArray)
Expand Down
34 changes: 34 additions & 0 deletions stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ public struct FilterTest {
}
}

public struct PredicateCountTest {
public let expected: Int
public let sequence: [Int]
public let includeElement: (Int) -> Bool
public let loc: SourceLoc

public init(
_ expected: Int,
_ sequence: [Int],
_ includeElement: @escaping (Int) -> Bool,
file: String = #file, line: UInt = #line
) {
self.expected = expected
self.sequence = sequence
self.includeElement = includeElement
self.loc = SourceLoc(file, line, comment: "test data")
}
}

public struct FindTest {
public let expected: Int?
public let element: MinimalEquatableValue
Expand Down Expand Up @@ -522,6 +541,21 @@ public let filterTests = [
),
]

public let predicateCountTests = [
PredicateCountTest(
0, [],
{ _ -> Bool in expectUnreachable(); return true }),

PredicateCountTest(0, [ 0, 30, 10, 90 ], { _ -> Bool in false }),
PredicateCountTest(
4, [ 0, 30, 10, 90 ], { _ -> Bool in true }
),
PredicateCountTest(
3, [ 0, 30, 10, 90 ], { (x: Int) -> Bool in x % 3 == 0 }
),
]


public let findTests = [
FindTest(
expected: nil,
Expand Down
44 changes: 44 additions & 0 deletions stdlib/public/core/SequenceAlgorithms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,50 @@ extension Sequence where Element : Equatable {
}
}

//===----------------------------------------------------------------------===//
// count(where:)
//===----------------------------------------------------------------------===//

extension Sequence {
/// Returns the number of elements in the sequence that satisfy the given
/// predicate.
///
/// You can use this method to count the number of elements that pass a test.
/// For example, this code finds the number of names that are fewer than
/// five characters long:
///
/// let names = ["Jacqueline", "Ian", "Amy", "Juan", "Soroush", "Tiffany"]
/// let shortNameCount = names.count(where: { $0.count < 5 })
/// // shortNameCount == 3
///
/// To find the number of times a specific element appears in the sequence,
/// use the equal-to operator (`==`) in the closure to test for a match.
///
/// let birds = ["duck", "duck", "duck", "duck", "goose"]
/// let duckCount = birds.count(where: { $0 == "duck" })
/// // duckCount == 4
///
/// The sequence must be finite.
///
/// - Parameter predicate: A closure that takes each element of the sequence
/// as its argument and returns a Boolean value indicating whether
/// the element should be included in the count.
/// - Returns: The number of elements in the sequence that satisfy the given
/// predicate.
@inlinable
public func count(
where predicate: (Element) throws -> Bool
) rethrows -> Int {
var count = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not use reduce here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using a reduce is needlessly complicated both in terms of abstraction and in terms of code readability, which is why I went with the for loop. If you feel strongly, I can switch it to a reduce.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. This kind of calculation is exactly what reduce is for. It states declaratively what the code does instead of writing an open-coded for loop, so improves readability. It's also important that the code in the std lib encourage the practice of using map/reduce/filter where appropriate.

(it does bug me you have to write try twice with reduce, tho)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I know I'm very late to this but just now came across this proposal, I just have a question and my apologies if it's a dumb one, but wouldn't the below make more sense:

@inlinable
  public func count<T: Sequence>(where predicate: (Element) throws -> Bool) rethrows -> Int
  where T: Sequence, T.Element == Element, T: AnyObject {
    var count = 0
    for element in self {
      if try predicate(element) {
        count += 1
      }
    }
    return count
  }
  • The method is explicitly constrained to Sequence types to avoid ambiguity with Collection.count, which should help reduce the type checker's workload and resolve the performance issue.
  • The generic constraint T: AnyObject is added to restrict the method usage and aid the compiler in resolving type ambiguities more efficiently, although this might need to be adjusted based on the specific requirements and context.

for e in self {
if try predicate(e) {
count += 1
}
}
return count
}
}

//===----------------------------------------------------------------------===//
// reduce()
//===----------------------------------------------------------------------===//
Expand Down
15 changes: 15 additions & 0 deletions validation-test/stdlib/SequenceType.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,21 @@ SequenceTypeTests.test("contains/Predicate") {
}
}

//===----------------------------------------------------------------------===//
// count(where:)
//===----------------------------------------------------------------------===//

SequenceTypeTests.test("count/Predicate") {
for test in predicateCountTests {
let s = MinimalSequence<OpaqueValue<Int>>(
elements: test.sequence.map { OpaqueValue($0) })
expectEqual(
test.expected,
s.count(where: { test.includeElement($0.value) }),
stackTrace: SourceLocStack().with(test.loc))
}
}

//===----------------------------------------------------------------------===//
// reduce()
//===----------------------------------------------------------------------===//
Expand Down