Reputation: 906
The problem: to return a number of zeros in an array that contains 0s and 1s, if there are 3 0s in a row, count them as one, for example [0, 1, 0, 0, 0, 1, 0, 1, 0] should return 4, but when i try to solve it like this
func findZeros(_ c: [Int]) -> Int {
var zeros = 0
for var i in 0..<c.count {
switch c[i] {
case _ where c[i] == 0 && c[i+1] == 0 && c[i+2] == 0: // row 5
zeros += 1
i += 2
case _ where c[i] == 0:
zeros += 1
default:
break
}
}
return zeros
}
i always get index out of range error in row 5 , although when i hardcode c[1], c[2], c[3] == 0, it just counts as false and goes through... i've just started to learn swift so maybe that's not optimal, but anyway i can't even get this one working :/
Upvotes: 0
Views: 120
Reputation: 63271
I'm going to show a very different solution approach. It's not one that would come naturally to a new beginner, so don't worry if it's foreign to you. It's something that you might come up with from having a bit more experience and being able to relate scattered concepts with each other.
The over-all process is actually very straight-forward, and is an almost exact codification of the your english explanation to the solution:
["A", "B, B", "C", "D", "D", "D"]
, and turns it into a sequence like [("A", 1), ("B", 2), ("C", 1), ("D", 3)]
0
s that repeat precisely 3 times, and treat them as runs of a single 0.Here's what the code to do that would look like:
let input = [0, 1, 0, 0, 0, 1, 0, 1, 0]
let result = input.runLengthEncoded()
.lazy
.filter(keepOnlyRunsOfZeros)
.map(convertThreeCountRunsIntoOneCountRuns)
.map { $0.count }
.reduce(0, +)
print(result)
And here are the supporting functions that make it possible:
typealias Run = (element: Int, count: Int)
func keepOnlyRunsOfZeros(_ run: Run) -> Bool {
return run.element == 0
}
func convertThreeCountRunsIntoOneCountRuns(_ run: Run) -> Run {
if run.count == 3 {
return (element: run.element, count: 1)
}
else {
return run
}
}
Knowing that the process in #1 is a known algorithm called run-length encoding, I can find and reuse an existing implementation. Over time as a developer, you build up a collection of useful functions/techniques that you reuse for future use. Often times, you find other people's libraries that you've come to find useful, thus have booked-marked and re-use.
In this case, I have an implementation of run-length encoding that I've written and used in previous projects. It's quite long, but it's generalized and lazy-evaluated (it doesn't need to make an array of all runs, it serves them one by one as you request them, which improves performance for huge inputs), which isn't strictly necessary in this case, but it's what I already have on hand.
There alternative implementations that you can find that are eagerly evaluated and less generic (which should be fine for a simple problem like this), feel free to substitute one of those, instead.
public extension Sequence where Self.Iterator.Element: Equatable {
func runLengthEncoded() -> LazySequenceRunLengthEncoder<Self> {
return LazySequenceRunLengthEncoder(encoding: self)
}
}
public struct LazySequenceRunLengthEncoder<WrappedSequence: Sequence>: Sequence
where WrappedSequence.Element: Equatable {
public let wrappedSequence: WrappedSequence
public init(encoding wrappedSequence: WrappedSequence) {
self.wrappedSequence = wrappedSequence
}
public func makeIterator() -> RunLengthEncodingIterator<WrappedSequence.Iterator> {
return RunLengthEncodingIterator(encoding: wrappedSequence.makeIterator())
}
}
public struct RunLengthEncodingIterator<WrappedIterator: IteratorProtocol>: IteratorProtocol
where WrappedIterator.Element: Equatable {
public private(set) var wrappedIterator: WrappedIterator
public private(set) var currentGrouping: (element: WrappedIterator.Element, count: Int)? = nil
public init(encoding wrappedIterator: WrappedIterator) {
self.wrappedIterator = wrappedIterator
}
public mutating func next() -> (element: WrappedIterator.Element, count: Int)? {
while let newElement = wrappedIterator.next() { // Take all elements of this run
if let currentGrouping = self.currentGrouping {
if newElement == currentGrouping.element { // increment the current run
let newCount = currentGrouping.count + 1
self.currentGrouping = (element: newElement, count: newCount)
} else { // Broke the streak
defer {
self.currentGrouping = (element: newElement, count: 1) // start a new group
}
return self.currentGrouping
}
} else { // There is no current group, this is the first element
self.currentGrouping = (element: newElement, count: 1)
}
}
// Reached end of the wrapped iterator
// 2. Only return the current grouping once, return the `nil` next time to end this iterator.
defer { self.currentGrouping = nil }
// 1. Return current grouping, if there is one
return self.currentGrouping
}
}
Upvotes: 1
Reputation: 236370
You can group the consecutive elements and sum how many groups you have of those elements:
extension Collection where Element: Equatable {
var grouped: [[Element]] {
reduce(into: []) {
// check if the last element of the last collection is equal to the current element
$0.last?.last == $1 ?
// append the element to the last collection
$0[$0.index(before: $0.endIndex)].append($1) :
// otherwise add a new collection with the new element
$0.append([$1])
}
}
func repeatedOccurences(of element: Element) -> Int {
// if the collection first element is equal to the element add one otherwise return the current result
grouped.reduce(0) { $1.first == element ? $0 + 1 : $0 }
}
}
[0, 1, 0, 0, 0, 1, 0, 1, 0].repeatedOccurences(of: 0) // 4
[0, 1, 0, 0, 0, 1, 0, 1, 0].repeatedOccurences(of: 1) // 3
Upvotes: 2
Reputation: 174515
If you were only accessing c[i]
, then counting i
up to c.count - 1
would be fine - but you're also trying to access c[i+1]
and c[i+2]
, so you need to take that into account when setting the upper boundary of the range - and then verify that the array indeed has at least 3 elements:
if c.count < 3 {
return 0;
}
for var i in 0..<c.count-2 {
// now you can safely access c[i+2]
}
Upvotes: 1