How I can shuffle my struct by variable priority and display in TableView?
So now I have 20 documents in my struct, but later I will have 100+ documents in my struct.
5 or 7 or 10 documents will have priority from 10 to 1, other documents have priority 0. Me need display 5 or 7 or 10 documents on top position in tableView. Other documents which have priority 0 must be located after firsts 5 or 7 or 10 documents in random order.
I. e. the firsts 5 or 7 or 10 documents should be placed depending on the priority, if a document has priority 10, it should be the first one, the next one which has priority 9 should be behind the document with priority 10 and so on to the document with priority 1. Other documents due be randomly in order.
This code which help me get documents from firestore:
fileprivate func observeQuery() {
MBProgressHUD.showAdded(to: self.view, animated: true)
guard let query = query else { return }
let time = DispatchTime.now() + 0.5
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
if let snapshot = snapshot {
DispatchQueue.main.asyncAfter(deadline: time) {
var photoModels = snapshot.documents.map { (document) -> Photographer in
if let photoModel = Photographer(dictionary: document.data(), id: document.documentID) {
return photoModel
} else {
fatalError("Fatal error")
}
}
self.photographers = photoModels
// this need use shuffle
self.document = snapshot.documents
self.tableView.reloadData()
MBProgressHUD.hide(for: self.view, animated: true)
}
}
}
}
What you could do is
Sort the documents by decreasing priority first.
Then shuffle the part of the array with documents with zero priority.
Example:
var sorted = documents.sorted(by: { $0.priority > $1.priority } )
if let idx = sorted.firstIndex(where: { $0.priority == 0 }) {
sorted[idx...].shuffle()
}
An alternative is to shuffle the complete array and then do a “stable sort” by decreasing priority, using the ideas from How to stable sort an array in swift?:
let sorted = documents.shuffled().enumerated()
.sorted(by: { ($0.element.priority, $0.offset) > ($1.element.priority, $1.offset) })
.map { $0.element }
This would sort the entries by decreasing order, and randomly shuffle all entries with identical priority, not only the entries with zero priority.
Remark: The sort methods from the Swift standard library happens to be stable in Swift 5, so that the last approach could be simplified to
let sorted = documents.shuffled()
.sorted(by: { $0.priority > $1.priority } )
However, that is not guaranteed, compare Is sort() stable in Swift 5? in the Swift forum.
Here is a solution where all elements in the array gets sorted at once
array.sort(by: {
let first = $0.priority == 0 ? Double.random(in: 0..<1) : Double($0.priority)
let second = $1.priority == 0 ? Double.random(in: 0..<1) : Double($1.priority)
return first > second
})
If you want to avoid the cast to Double you can define a negative range for the random numbers, I just hardcoded a value here but one option could be to base the min value (-1000) on the size of array
array.sort(by: {
let first = $0.priority == 0 ? Int.random(in: -1000..<1) : $0.priority
let second = $1.priority == 0 ? Int.random(in: -1000..<1) : $1.priority
return first > second
})
You could put the documents into buckets:
var buckets = Array(repeating: [Document](), count: 11)
for i in documents.indices {
buckets[10 - documents[i].priority].append(documents[i])
}
This is an O(n) algorithm worst case unlike TimSort's O(nlog(n)).
and then shuffle the last bucket :
buckets[10].shuffle()
last but not least, flatMap the buckets array:
let sorted = buckets.flatMap { $0 }
Speed Shuffle
If you'd like to an even faster shuffle, you could use this modified Fisher-Yates Algorithm (Faster than Array.shuffled()):
extension Array where Element: Comparable {
mutating func fisherYatesShuffle() {
if count < 2 {
return
}
for i in 1..<count {
let ix = abs(x.next()) % (i+1)
swapAt(i,ix)
}
}
}
Or more generally for MutableCollection (which would include ArraySlice):
extension MutableCollection where Index == Int, Element: Comparable {
mutating func fisherYatesShuffle() {
if count < 2 {
return
}
let start = self.index(after: startIndex)
for i in start..<endIndex {
let ix = abs(x.next()) % (i + 1 - startIndex) + startIndex
swapAt(i, ix)
}
}
}
This extension uses a Xoshiro random number generator (Faster than Int.random(in:), but less random/uniform):
struct Xoshiro: RandomNumberGenerator {
public typealias StateType = (UInt32, UInt32, UInt32, UInt32)
private var state: StateType
public init(seed: StateType) {
self.state = seed
}
public mutating func next() -> Int {
let x = state.1 &* 5
let result = ((x &<< 7) | (x &>> 25)) &* 9
let t = state.1 &<< 9
state.2 ^= state.0
state.3 ^= state.1
state.1 ^= state.2
state.0 ^= state.3
state.2 ^= t
state.3 = (state.3 &<< 21) | (state.3 &>> 11)
return Int(result)
}
}
var x = Xoshiro(seed: (UInt32.random(in: 0..<10), //Other upper limits could be used to increase randomness
UInt32.random(in: 0..<10),
UInt32.random(in: 0..<10),
UInt32.random(in: 0..<10)))
To use it, Document should be Comparable :
struct Document: Comparable {
let priority: Int
static func < (lhs: Document, rhs: Document) -> Bool {
return lhs.priority < rhs.priority
}
}
and call our shuffle like so:
buckets[10].fisherYatesShuffle()
Partition, Sort, Shuffle
As per Josh Homann's suggestion, you could also partition the documents array putting the documents with zero priority after a pivot index. Then sort the first partition, and shuffle the second one :
var result = documents
let pivot = result.partition { $0.priority == 0 }
result[..<pivot].sort(by: > )
result[pivot...].shuffle()
Benchmarks
Here are some benchmarks :
Joakim Danielson's : 1643µs
MartinR's : 169µs
Josh Homann's : 152µs
Orig. Fisher-Yates : 38µs
This : 15µs
(Even though TIO uses Swift 4, relatively comparable results were got using the same code on my local machine with Swift 5, run in the terminal with optimizations)
Related
I want to use arc4random to generate array of 30 different numbers, so that there is no repeating in it. How can I do it?
Update:
Thanks to #Sulthan for his elegant thought (commented on Gereon's answer):
anyway, depending on the difference between limit and the number of
generated elements this can have a terrible performance. The problem
is the case when limit is close to the number of generated elements.
Then it would be much better to take 1..<limit and shuffle it.
Which means that there is no even need to generate random Int. The simplest way I could think of is to do it as (Swift 4.2):
let randoms = Array(0..<30).shuffled()
therefore randoms is an array of Ints, contains 30 unique values from 0 to 29.
Less Than Swift 4.2:
However, if you are not using Swift 4.2, I would recommend to check this great answer for getting shuffled collection.
Ignored Solution:
You could achieve it like this:
var uniques = Set<UInt32>()
func generateUniqueUInt32() -> UInt32 {
let random = arc4random_uniform(31)
if !uniques.contains(random) {
uniques.insert(random)
return random
} else {
return generateUniqueUInt32()
}
}
let randomArray = Array(0..<30).map { _ in Int(generateUniqueUInt32()) }
Therefore randomArray is an array of Ints, contains 30 unique values from 0 to 29.
Swift 4.2:
You should replace:
let random = arc4random_uniform(31)
with:
let random = Int.random(in: 1..<30)
which means that generateUniqueUInt32 should return -directly- Int (and renamed to generateUniqueInt):
func generateUniqueInt() -> Int {
let random = Int.random(in: 1..<30)
if !uniques.contains(random) {
uniques.insert(random)
return random
} else {
return generateUniqueInt()
}
}
let randomArray = Array(0..<30).map { _ in generateUniqueInt() }
It may be a pretty heavy action but you can do like that:
var randomNumbers: [Int] = []
while randomNumbers.count != 30{
let number = Int(arc4random_uniform(1000) + 1)
if randomNumbers.contains(number) {
print("Already Exits")
}else{
randomNumbers.append(number)
}
}
replace "1000" for a range of number that you need. That function generated 30 different number between 0...1000
Use a Set to store the generated numbers so far
func thirtyUniqueRandomNumbers(_ limit: Int) -> [Int] {
var randoms = Set<Int>()
repeat {
let rnd = Int(arc4random_uniform(UInt32(limit)))
if !randoms.contains(rnd) {
randoms.insert(rnd)
}
} while randoms.count < 30
return randoms.map { $0 }
}
Here is a simple solution. Start a while loop. Within this loop generate a random number (between 0 and 1000). And then append the number into your array if the array doesn't contains the number.
func generateArrayOfRandomNumbers() -> [Int] {
var array: [Int] = []
while array.count < 30 {
let randomInt = Int.random(in: 0...1000)
guard !array.contains(randomInt) else { continue }
array.append(randomInt)
}
return array
}
I have index out of range error when i delete object from my array. There is my code. It is an Elevator function which takes object of Floor class and works with array of objects of Passenger class on this floor. I create a temporary copy of object of current floor and then I go along array of this copy and if object is suitable for the conditions we push this object to Elevator array of passengers and delete it by index from original array of current floor object. If this matters, I use the Equatable protocol and created a function to compare.
Thanks for any answers.
class Passenger: Equatable{...}
func ==(l: Passenger, r: Passenger) -> Bool {
return l === r
}
func checkFloor(f: Floor){
var tempFloor = f
var pass = passengers
for i in 0..<passengers.count {
if(passengers.isEmpty){
break
}
if(pass[i].getFloor()==f.getIdFloor()){
f.peoples.append(pass[i])
f.peoples[f.peoples.count - 1].setDirection(who: "nothing")
//if var index = passengers.index(of: pass[i]) {
if let index = searchInArray(who: passengers, who: pass[i]) {
passengers.remove(at: index)
}
}
}
// in this part I have a problem
for i in 0..<tempFloor.countOf() {
if(f.peoples.isEmpty || passengers.count >= capacity){
break
}
if(tempFloor.peoples[i].getDirection()==tempFloor.peoplesDirection()
){
passengers.append(tempFloor.peoples[i])
if let index = f.peoples.index(of: tempFloor.peoples[i]) {
if (index >= 0 && index < f.peoples.count) {
//print(index)
f.peoples.remove(at: index) // index out of range error
}
}
}
}
}
You are removing items whilst enumerating a range, so the range is changing (potentially often) but this wont update for i in 0..<tempFloor.countOf()
When you remove an item from an array, each item after that index changes its index and the count is reduced. so if you plan to do this, it's usually best to enumerate the array backwards, so the removal of the current item will not affect what your doing next.
To demonstrate, try this code in a playground
var arr = [1,2,3,4,5,6,7,8,9,10]
for (index, item) in arr.enumerated().reversed() {
if item % 2 == 0 {
arr.remove(at: index)
}
}
print(arr)
It will iterate over the items in the array backwards and remove any that are even, and will output:
"[1, 3, 5, 7, 9]\n"
I'd like a function runningSum on an array of numbers a (or any ordered collection of addable things) that returns an array of the same length where each element i is the sum of all elements in A up to an including i.
Examples:
runningSum([1,1,1,1,1,1]) -> [1,2,3,4,5,6]
runningSum([2,2,2,2,2,2]) -> [2,4,6,8,10,12]
runningSum([1,0,1,0,1,0]) -> [1,1,2,2,3,3]
runningSum([0,1,0,1,0,1]) -> [0,1,1,2,2,3]
I can do this with a for loop, or whatever. Is there a more functional option? It's a little like a reduce, except that it builds a result array that has all the intermediate values.
Even more general would be to have a function that takes any sequence and provides a sequence that's the running total of the input sequence.
The general combinator you're looking for is often called scan, and can be defined (like all higher-order functions on lists) in terms of reduce:
extension Array {
func scan<T>(initial: T, _ f: (T, Element) -> T) -> [T] {
return self.reduce([initial], combine: { (listSoFar: [T], next: Element) -> [T] in
// because we seeded it with a non-empty
// list, it's easy to prove inductively
// that this unwrapping can't fail
let lastElement = listSoFar.last!
return listSoFar + [f(lastElement, next)]
})
}
}
(But I would suggest that that's not a very good implementation.)
This is a very useful general function, and it's a shame that it's not included in the standard library.
You can then generate your cumulative sum by specializing the starting value and operation:
let cumSum = els.scan(0, +)
And you can omit the zero-length case rather simply:
let cumSumTail = els.scan(0, +).dropFirst()
Swift 4
The general sequence case
Citing the OP:
Even more general would be to have a function that takes any sequence
and provides a sequence that's the running total of the input
sequence.
Consider some arbitrary sequence (conforming to Sequence), say
var seq = 1... // 1, 2, 3, ... (CountablePartialRangeFrom)
To create another sequence which is the (lazy) running sum over seq, you can make use of the global sequence(state:next:) function:
var runningSumSequence =
sequence(state: (sum: 0, it: seq.makeIterator())) { state -> Int? in
if let val = state.it.next() {
defer { state.sum += val }
return val + state.sum
}
else { return nil }
}
// Consume and print accumulated values less than 100
while let accumulatedSum = runningSumSequence.next(),
accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91
// Consume and print next
print(runningSumSequence.next() ?? -1) // 120
// ...
If we'd like (for the joy of it), we could condense the closure to sequence(state:next:) above somewhat:
var runningSumSequence =
sequence(state: (sum: 0, it: seq.makeIterator())) {
(state: inout (sum: Int, it: AnyIterator<Int>)) -> Int? in
state.it.next().map { (state.sum + $0, state.sum += $0).0 }
}
However, type inference tends to break (still some open bugs, perhaps?) for these single-line returns of sequence(state:next:), forcing us to explicitly specify the type of state, hence the gritty ... in in the closure.
Alternatively: custom sequence accumulator
protocol Accumulatable {
static func +(lhs: Self, rhs: Self) -> Self
}
extension Int : Accumulatable {}
struct AccumulateSequence<T: Sequence>: Sequence, IteratorProtocol
where T.Element: Accumulatable {
var iterator: T.Iterator
var accumulatedValue: T.Element?
init(_ sequence: T) {
self.iterator = sequence.makeIterator()
}
mutating func next() -> T.Element? {
if let val = iterator.next() {
if accumulatedValue == nil {
accumulatedValue = val
}
else { defer { accumulatedValue = accumulatedValue! + val } }
return accumulatedValue
}
return nil
}
}
var accumulator = AccumulateSequence(1...)
// Consume and print accumulated values less than 100
while let accumulatedSum = accumulator.next(),
accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91
The specific array case: using reduce(into:_:)
As of Swift 4, we can use reduce(into:_:) to accumulate the running sum into an array.
let runningSum = arr
.reduce(into: []) { $0.append(($0.last ?? 0) + $1) }
// [2, 4, 6, 8, 10, 12]
By using reduce(into:_:), the [Int] accumulator will not be copied in subsequent reduce iterations; citing the Language reference:
This method is preferred over reduce(_:_:) for efficiency when the
result is a copy-on-write type, for example an Array or a
Dictionary.
See also the implementation of reduce(into:_:), noting that the accumulator is provided as an inout parameter to the supplied closure.
However, each iteration will still result in an append(_:) call on the accumulator array; amortized O(1) averaged over many invocations, but still an arguably unnecessary overhead here as we know the final size of the accumulator.
Because arrays increase their allocated capacity using an exponential
strategy, appending a single element to an array is an O(1) operation
when averaged over many calls to the append(_:) method. When an array
has additional capacity and is not sharing its storage with another
instance, appending an element is O(1). When an array needs to
reallocate storage before appending or its storage is shared with
another copy, appending is O(n), where n is the length of the array.
Thus, knowing the final size of the accumulator, we could explicitly reserve such a capacity for it using reserveCapacity(_:) (as is done e.g. for the native implementation of map(_:))
let runningSum = arr
.reduce(into: [Int]()) { (sums, element) in
if let sum = sums.last {
sums.append(sum + element)
}
else {
sums.reserveCapacity(arr.count)
sums.append(element)
}
} // [2, 4, 6, 8, 10, 12]
For the joy of it, condensed:
let runningSum = arr
.reduce(into: []) {
$0.append(($0.last ?? ($0.reserveCapacity(arr.count), 0).1) + $1)
} // [2, 4, 6, 8, 10, 12]
Swift 3: Using enumerated() for subsequent calls to reduce
Another Swift 3 alternative (with an overhead ...) is using enumerated().map in combination with reduce within each element mapping:
func runningSum(_ arr: [Int]) -> [Int] {
return arr.enumerated().map { arr.prefix($0).reduce($1, +) }
} /* thanks #Hamish for improvement! */
let arr = [2, 2, 2, 2, 2, 2]
print(runningSum(arr)) // [2, 4, 6, 8, 10, 12]
The upside is you wont have to use an array as the collector in a single reduce (instead repeatedly calling reduce).
Just for fun: The running sum as a one-liner:
let arr = [1, 2, 3, 4]
let rs = arr.map({ () -> (Int) -> Int in var s = 0; return { (s += $0, s).1 } }())
print(rs) // [1, 3, 6, 10]
It does the same as the (updated) code in JAL's answer, in particular,
no intermediate arrays are generated.
The sum variable is captured in an immediately-evaluated closure returning the transformation.
If you just want it to work for Int, you can use this:
func runningSum(array: [Int]) -> [Int] {
return array.reduce([], combine: { (sums, element) in
return sums + [element + (sums.last ?? 0)]
})
}
If you want it to be generic over the element type, you have to do a lot of extra work declaring the various number types to conform to a custom protocol that provides a zero element, and (if you want it generic over both floating point and integer types) an addition operation, because Swift doesn't do that already. (A future version of Swift may fix this problem.)
Assuming an array of Ints, sounds like you can use map to manipulate the input:
let arr = [0,1,0,1,0,1]
var sum = 0
let val = arr.map { (sum += $0, sum).1 }
print(val) // "[0, 1, 1, 2, 2, 3]\n"
I'll keep working on a solution that doesn't use an external variable.
I thought I'd be cool to extend Sequence with a generic scan function as is suggested in the great first answer.
Given this extension, you can get the running sum of an array like this: [1,2,3].scan(0, +)
But you can also get other interesting things…
Running product: array.scan(1, *)
Running max: array.scan(Int.min, max)
Running min: array.scan(Int.max, min)
Because the implementation is a function on Sequence and returns a Sequence, you can chain it together with other sequence functions. It is efficient, having linear running time.
Here's the extension…
extension Sequence {
func scan<Result>(_ initialResult: Result, _ nextPartialResult: #escaping (Result, Self.Element) -> Result) -> ScanSequence<Self, Result> {
return ScanSequence(initialResult: initialResult, underlying: self, combine: nextPartialResult)
}
}
struct ScanSequence<Underlying: Sequence, Result>: Sequence {
let initialResult: Result
let underlying: Underlying
let combine: (Result, Underlying.Element) -> Result
typealias Iterator = ScanIterator<Underlying.Iterator, Result>
func makeIterator() -> Iterator {
return ScanIterator(previousResult: initialResult, underlying: underlying.makeIterator(), combine: combine)
}
var underestimatedCount: Int {
return underlying.underestimatedCount
}
}
struct ScanIterator<Underlying: IteratorProtocol, Result>: IteratorProtocol {
var previousResult: Result
var underlying: Underlying
let combine: (Result, Underlying.Element) -> Result
mutating func next() -> Result? {
guard let nextUnderlying = underlying.next() else {
return nil
}
previousResult = combine(previousResult, nextUnderlying)
return previousResult
}
}
One solution using reduce:
func runningSum(array: [Int]) -> [Int] {
return array.reduce([], combine: { (result: [Int], item: Int) -> [Int] in
if result.isEmpty {
return [item] //first item, just take the value
}
// otherwise take the previous value and append the new item
return result + [result.last! + item]
})
}
I'm very late to this party. The other answers have good explanations. But none of them have provided the initial result, in a generic way. This implementation is useful to me.
public extension Sequence {
/// A sequence of the partial results that `reduce` would employ.
func scan<Result>(
_ initialResult: Result,
_ nextPartialResult: #escaping (Result, Element) -> Result
) -> AnySequence<Result> {
var iterator = makeIterator()
return .init(
sequence(first: initialResult) { partialResult in
iterator.next().map {
nextPartialResult(partialResult, $0)
}
}
)
}
}
extension Sequence where Element: AdditiveArithmetic & ExpressibleByIntegerLiteral {
var runningSum: AnySequence<Element> { scan(0, +).dropFirst() }
}
I have the following swift array:
var winSuitArray = [cardSuit1, cardSuit2, cardSuit3, cardSuit4, cardSuit5, cardSuit6, cardSuit7]
cardSuit1, cardSuit2 and so on, are variables that will equal strings like "clubs" or "hearts". What I want to do is loop through this array, and if the loop finds 4 identical objects whose indexes are consecutive, set the winSuitStatus bool to true.
For example, if the array looks like this:
["hearts", "clubs", "clubs", "clubs", "clubs", "diamonds", "spades"]
I want to loop through it like so:
for card in winSuitArray {
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
Is this possible to do?
To tell the truth, I'd probably do something similar to #KnightOfDragon's answer. There's nothing wrong with that approach. But this problem opens up some opportunities to build some much more reusable code at little cost, so it seems worth a little trouble to do that.
The basic problem is that you want to create a sliding window of a given size over the list, and then you want to know if any of the windows contain only a single value. So the first issue to to create these windows. We can do that very generally for all collections, and we can do it lazily so we don't have to compute all the windows (we might find our answer at the start of the list).
extension Collection {
func slidingWindow(length: Int) -> AnyRandomAccessCollection<SubSequence> {
guard length <= count else { return AnyRandomAccessCollection([]) }
let windows = sequence(first: (startIndex, index(startIndex, offsetBy: length)),
next: { (start, end) in
let nextStart = self.index(after: start)
let nextEnd = self.index(after: end)
guard nextEnd <= self.endIndex else { return nil }
return (nextStart, nextEnd)
})
return AnyRandomAccessCollection(
windows.lazy.map{ (start, end) in self[start..<end] }
)
}
}
The use of AnyRandomAccessCollection here is to just hide the lazy implementation detail. Otherwise we'd have to return a LazyMapSequence<UnfoldSequence<(Index, Index), ((Index, Index)?, Bool)>, SubSequence>, which would be kind of crazy.
Now are next question is whether all the elements in a window are equal. We can do that for any kind of Equatable sequence:
extension Sequence where Iterator.Element: Equatable {
func allEqual() -> Bool {
var g = makeIterator()
guard let f = g.next() else { return true }
return !contains { $0 != f }
}
}
And with those two pieces, we can just ask our question. In the windows of length 4, are there any runs that area all equal?
let didWin = suits.slidingWindow(length: 4).contains{ $0.allEqual() }
Or we could go a little different way, and create a SlidingWindowSequence that we could iterate over. The logic here is basically the same. This just wraps up the windowing into a specific type rather than a AnyRandomAccessCollection. This may be overkill for this problem, but it demonstrates another powerful pattern.
public struct SlidingWindowSequence<Base: Collection>: Sequence, IteratorProtocol {
let base: Base
let windowSize: Base.IndexDistance
private var windowStart: Base.Index
public init(_ base: Base, windowSize: Base.IndexDistance) {
self.base = base
self.windowSize = windowSize
self.windowStart = base.startIndex
}
public mutating func next() -> Base.SubSequence? {
if base.distance(from: windowStart, to: base.endIndex) < windowSize {
return nil
}
let window = base[windowStart..<base.index(windowStart, offsetBy: windowSize)]
windowStart = base.index(after: windowStart)
return window
}
}
let didWin = SlidingWindowSequence(suits, windowSize: 4).contains{ $0.allEqual() }
var suit = ""
var count = 1
for card in winSuitArray {
if(suit == card)
{
count++
}
else
{
count = 1
suit = card
}
if(count == 4)
{
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
}
You can use a counter variable to do this, initialized to 1.
for each value in array:
if value equals previous value
increment counter
else
counter = 1
if counter >= 4
set winCounter to true
Okay, so I have a stream that is receiving an array at a fast speed constantly. Here's what I want to do...
If the array is the same, don't do anything. If it is different, make a new array with nil as every value except the changed ones.
Example:
Incoming array 1: [1,1,1,1]
Incoming array 2: [1,1,2,1]
I want to create: [nil, nil, 2, nil]. Only marking the changes.
I made something that worked, I just don't think it's efficient. Is it the best way to do it?
var storedArray = [Int](count: 10, repeatedValue: 0) //array for comparing
func incomingArray(data: [Int]) {
if data == storedArray {return} //do nothing if its the same
var tempArray = [Int?](count: 10, repeatedValue: nil) //nil array
for index in 0...9 {
if storedArray[index] != data[index] {
tempArray[index] = data[index] //replace the temp array index
}
}
//send the completed tempArray to do work ....
storedArray = incomingArray //save the stored as the current data
}
So the above code works. It's just not efficient. Any better ideas for this?
thanks
UPDATE 1:
I have mistaken in the original post. Instead of Int. They are UInt8.
If you’re concerned about performance, the first thing to look for is hidden loops. Here’s one:
if data == storedArray {return}
This is presumably here for intended efficiency – if the two arrays are equal, don’t bother doing anything. But really, this might be self-defeating. That comparison isn’t constant time – it loops over the elements and compares them. Since you’re going to loop over them later anyway, that probably doesn’t give you much.
You could argue it saves you allocating a new array, but this then leads to the next question which is do you really need to create an array with all those nil values? Why not instead generate an array of the indices into the array that are different? That way, the recipient of your differences will only have to loop over the differences (maybe only a couple) rather than the whole array.
It probably makes sense to factor out the array diffing from the processing and storage. Here’s a function that takes two arrays and returns an array of indices where they differ:
func differences<T: Equatable>(lhs: [T], rhs: [T]) -> [Int] {
// indexedPairs is a sequence of (index, (left-hand val, right-hand val))
let indexedPairs = enumerate(zip(lhs,rhs))
// the lazy may or may not help here, benchmark to find out...
return lazy(indexedPairs).filter { (index, pair) in
// only return different pairs
pair.0 != pair.1
}.map {
// only return the index not the values
$0.0
}.array
}
Note this is a pure function – that is, it takes inputs and produces a result without referencing any external state. This makes it easier to test and debug as a standalone function.
You could then rewrite your original function in terms of it:
func incomingArray(data: [Int]) {
let diffs = differences(storedArray, data)
if !diffs.isEmpty {
// send new data and diff indices for further processing
// then overwrite the old array
storedArray = data
}
}
Update
Benchmarking suggests the filter/map version performs horribly, compared to a simple loop, so here’s a version of differences that just uses for…in:
func differences<T: Equatable>(lhs: [T], rhs: [T]) -> [Int] {
var diffs: [Int] = []
// still using zip, since this guards against the two
// arrays being of different sizes - doesn’t seem to
// impact performance
for (i,(l,r)) in zip(indices(lhs),zip(lhs,rhs)) {
if l != r { diffs.append(i) }
}
return diffs
}
Some quick tests suggests this version gets a significant speedup if the input is large and the # of differences small, but performs identically if the arrays are mostly different.
Here are some ideas to make this code faster:
1) Instead of using an array of Int?, use a plain Int and instead of marking elements as nil, mark them as some special integer value. I don't know what that value is, maybe 0 is fine, or -1, or Int.max.
Update: The above change gives me a ~ 10% performance increase
2) Recycle your result array. So that you can skip the following code:
var tempArray = [Int?](count: 10, repeatedValue: nil)
Or maybe better, let the caller pass it in via an inout parameter so that you don't have to worry about ownership of it.
Update: The above change gives me a ~ 50% performance increase
Here is the code for all the versions suggested in this question:
import UIKit
import XCTest
var storedArray1 = [Int?](count: 10, repeatedValue: 0) //array for comparing
func processIncomingArray1(data: [Int]) {
var tempArray = [Int?](count: 10, repeatedValue: nil) //nil array
for index in 0...9 {
if storedArray1[index] != data[index] {
tempArray[index] = data[index] //replace the temp array index
}
}
storedArray1 = tempArray
}
var storedArray2 = [Int](count: 10, repeatedValue: 0)
func processIncomingArray2(data: [Int]) {
var tempArray = [Int](count: 10, repeatedValue: Int.max)
for index in 0...9 {
if storedArray2[index] != data[index] {
tempArray[index] = data[index]
}
}
storedArray2 = tempArray
}
var storedArray3 = [Int](count: 10, repeatedValue: Int.max)
func processIncomingArray3(data: [Int], inout result: [Int]) {
for index in 0...9 {
if result[index] != data[index] {
result[index] = data[index]
}
}
}
// Given two sequences, return a sequence of 2-tuples (pairs)
public func zip<A: SequenceType, B: SequenceType>(a: A, b: B)
-> ZipSequence<A, B>
{
return ZipSequence(a, b)
}
// Lazy sequence of tuples created from values from two other sequences
public struct ZipSequence<A: SequenceType, B: SequenceType>: SequenceType {
private var a: A
private var b: B
public init (_ a: A, _ b: B) {
self.a = a
self.b = b
}
public func generate() -> ZipGenerator<A.Generator, B.Generator> {
return ZipGenerator(a.generate(), b.generate())
}
}
// Generator that creates tuples of values from two other generators
public struct ZipGenerator<A: GeneratorType, B: GeneratorType>: GeneratorType {
private var a: A
private var b: B
public init(_ a: A, _ b: B) {
self.a = a
self.b = b
}
mutating public func next() -> (A.Element, B.Element)? {
switch (a.next(), b.next()) {
case let (.Some(aValue), .Some(bValue)):
return (aValue, bValue)
default:
return nil
}
}
}
func differences<T: Equatable>(lhs: [T], rhs: [T]) -> [Int] {
// indexedPairs is a sequence of (index, (left-hand val, right-hand val))
let indexedPairs = enumerate(zip(lhs,rhs))
// the lazy may or may not help here, benchmark to find out...
return lazy(indexedPairs).filter { (index, pair) in
// only return different pairs
pair.0 != pair.1
}.map {
// only return the index not the values
$0.0
}.array
}
var storedArray4 = [Int](count: 10, repeatedValue: Int.max)
func processIncomingArray4(data: [Int]) {
let diffs = differences(storedArray4, data)
if !diffs.isEmpty {
// send new data and diff indices for further processing
// then overwrite the old array
storedArray4 = data
}
}
func differences5<T: Equatable>(lhs: [T], rhs: [T]) -> [Int] {
var diffs: [Int] = []
// still using zip, since this guards against the two
// arrays being of different sizes - doesn’t seem to
// impact performance
for (i,(l,r)) in zip(indices(lhs),zip(lhs,rhs)) {
if l != r { diffs.append(i) }
}
return diffs
}
var storedArray5 = [Int](count: 10, repeatedValue: Int.max)
func processIncomingArray5(data: [Int]) {
let diffs = differences5(storedArray4, data)
if !diffs.isEmpty {
// send new data and diff indices for further processing
// then overwrite the old array
storedArray5 = data
}
}
class StackOverflowTests: XCTestCase {
func testPerformanceExample1() {
var data = [1,2,3,4,5,6,7,8,9,10]
self.measureBlock() {
for i in 1...100000 {
processIncomingArray1(data)
}
}
}
func testPerformanceExample2() {
var data = [1,2,3,4,5,6,7,8,9,10]
self.measureBlock() {
for i in 1...100000 {
processIncomingArray2(data)
}
}
}
func testPerformanceExample3() {
var data = [1,2,3,4,5,6,7,8,9,10]
self.measureBlock() {
for i in 1...100000 {
processIncomingArray3(data, &storedArray3)
}
}
}
func testPerformanceExample4() {
var data = [1,2,3,4,5,6,7,8,9,10]
self.measureBlock() {
for i in 1...100000 {
processIncomingArray4(data)
}
}
}
func testPerformanceExample5() {
var data = [1,2,3,4,5,6,7,8,9,10]
self.measureBlock() {
for i in 1...100000 {
processIncomingArray5(data)
}
}
}
}
I think I have the best answer. Instead of the whole empty nil array. I made my temp array a bool value. Then if the value changes, mark its index as true.
So here's a example.
Incoming array 1: [1,1,1,1]
Incoming array 2: [1,1,2,1]
I then export the full array plus a bool array of: [false, false, true, false].
Then I just check if theres a change and pull the value. It turned out to work much faster then the other answers. I also recycled the temp array to speed it up. My guess is that since it's value can only be true or false, it's a lot faster then NIL/UInt8.
Thanks for the help guys. Let me know if any other ideas come up.