Swift Filter other arrays based on another Bool array
Asked Answered
C

4

5

I have an array of Bools and want to edit an array of Scores and an array of Dates for the values that are False. I can't get to it. I thought of getting elements that are false and using that array to remove those elements from the score array but I can imagine there is a direct way of doing it.

let hbiCompleteArray = [true, true, true, true, false, true, true, false, false]

let hbiScoreArray = [12, 12, 12, 12, 3, 13, 13, 2, 2]

I want an array of completeHbiScores = [12, 12, 12, 12, 13, 13]

Centaurus answered 29/9, 2016 at 13:4 Comment(3)
Consider to use one array of a custom struct for the model rather than multiple arrays.Eulogium
OOO that sounds super cool how can I do that?Centaurus
Swift Language Guide: Classes and StructuresEulogium
M
6

If you have to use two arrays, you can solve this with zip, filter and map like this:

let hbiCompleteArray = [true, true, true, true, false, true, true, false, false]
let hbiScoreArray = [12, 12, 12, 12, 3, 13, 13, 2, 2]

let result = zip(hbiCompleteArray, hbiScoreArray).filter { $0.0 }.map { $1 }
print(result)

Gives:

[12, 12, 12, 12, 13, 13]

Explanation: the zip interleaves the two arrays (makes an array of (Bool, Int) tuples), then filter { $0.0 } only keeps the true booleans, then the map only keeps the Int values.

Mullins answered 29/9, 2016 at 13:9 Comment(10)
This is a good and fairly simple way to do it if the arrays aren't overly large. The only issue is that each of these operations iterate through the arrays and so it can bog down on large arrays. Is it enough to optimize? That's up to you to decide, certainly don't go for a more complicated solution unless testing shows this to be too slow for your needs.Vermin
never seen zip before!David
@Honey it creates a Zip2Sequence instance.Romaine
@ColGraff It's true that it iterates multiple times, but I think it's not an issue, not even a concern, here. We can guess that OP won't have billions of scores in their array - also, the nature of Zip2Sequence (not an actual array) and the fact that filter/map are optimized (buffers, copy-on-write, compiler optimizations, etc) makes me say that it's really ok here. :)Mullins
I agree that it's probably not a concern and switching to another solution without testing for bottlenecks is definitely premature optimization. Use this solution for its simplicity and clarity, if testing shows it's a bottleneck then optimize it. That should be a guideline for most software development!Vermin
@ColGraff Couldn't agree more. 👍Mullins
Note that you could also use flatMap instead of filter and map, e.g let result = zip(hbiCompleteArray, hbiScoreArray).flatMap {$0 ? $1 : nil} :)Mutineer
@Mutineer Nice one!Mullins
Actually the arrays can get quite large as I will present a graph of the data for 7 days, 30 days and 90 days. For 90 days then it can be 90 x every days readings taken. That could be an array of say up to 2500 elements. I do not know if that would be a concern for this solution. It is amazing though.Centaurus
Works well and is really elegant. Trying got use the same method to return an array of dates now. Thank youCentaurus
A
7

The comment from vadian is very important here. You should not have multiple arrays this way. Create a struct that holds the data:

struct Score {
    let isComplete: Bool
    let finalScore: Int
}

You can then add a Date or whatever other fields you currently have parallel arrays for. Then your data looks like:

let scores = [
    Score(isComplete: true, finalScore: 12),
    Score(isComplete: true, finalScore: 12),
    Score(isComplete: true, finalScore: 12),
    Score(isComplete: true, finalScore: 12),
    Score(isComplete: false, finalScore: 3),
    Score(isComplete: true, finalScore: 13),
    Score(isComplete: true, finalScore: 13),
    Score(isComplete: false, finalScore: 2),
    Score(isComplete: false, finalScore: 2),
]

And getting complete ones is simple by filtering

let completeScores = scores.filter { $0.isComplete }

Of course if you wanted just the final scores as an array, you can map down to that:

let finalCompleteScores = completeScores.map { $0.finalScore }

This is how you should be thinking about your data, rather than as a bunch of arrays you have to keep in sync.

Averment answered 29/9, 2016 at 13:26 Comment(2)
This is certainly the optimal solution if you can put your data into a single struct, it usually results in cleaner and easier to maintain code. If you have to have two arrays for other design reasons then the other solutions might work better.Vermin
I like this solution as I can also add the date of the score. I can then use the date/ score for the graph. I will try it later.Centaurus
M
6

If you have to use two arrays, you can solve this with zip, filter and map like this:

let hbiCompleteArray = [true, true, true, true, false, true, true, false, false]
let hbiScoreArray = [12, 12, 12, 12, 3, 13, 13, 2, 2]

let result = zip(hbiCompleteArray, hbiScoreArray).filter { $0.0 }.map { $1 }
print(result)

Gives:

[12, 12, 12, 12, 13, 13]

Explanation: the zip interleaves the two arrays (makes an array of (Bool, Int) tuples), then filter { $0.0 } only keeps the true booleans, then the map only keeps the Int values.

Mullins answered 29/9, 2016 at 13:9 Comment(10)
This is a good and fairly simple way to do it if the arrays aren't overly large. The only issue is that each of these operations iterate through the arrays and so it can bog down on large arrays. Is it enough to optimize? That's up to you to decide, certainly don't go for a more complicated solution unless testing shows this to be too slow for your needs.Vermin
never seen zip before!David
@Honey it creates a Zip2Sequence instance.Romaine
@ColGraff It's true that it iterates multiple times, but I think it's not an issue, not even a concern, here. We can guess that OP won't have billions of scores in their array - also, the nature of Zip2Sequence (not an actual array) and the fact that filter/map are optimized (buffers, copy-on-write, compiler optimizations, etc) makes me say that it's really ok here. :)Mullins
I agree that it's probably not a concern and switching to another solution without testing for bottlenecks is definitely premature optimization. Use this solution for its simplicity and clarity, if testing shows it's a bottleneck then optimize it. That should be a guideline for most software development!Vermin
@ColGraff Couldn't agree more. 👍Mullins
Note that you could also use flatMap instead of filter and map, e.g let result = zip(hbiCompleteArray, hbiScoreArray).flatMap {$0 ? $1 : nil} :)Mutineer
@Mutineer Nice one!Mullins
Actually the arrays can get quite large as I will present a graph of the data for 7 days, 30 days and 90 days. For 90 days then it can be 90 x every days readings taken. That could be an array of say up to 2500 elements. I do not know if that would be a concern for this solution. It is amazing though.Centaurus
Works well and is really elegant. Trying got use the same method to return an array of dates now. Thank youCentaurus
R
1

Agree that parallel arrays approach isn't the best structure to use for your code but an alternative to filter and map used by Eric is reduce:

let completeHbiScores = zip(hbiCompleteArray, hbiScoreArray).reduce([Int]()){
    (newArray,zippedArray) in
    if zippedArray.0 {
        return newArray + [zippedArray.1]
    }
    else {
        return newArray
    }}
Romaine answered 29/9, 2016 at 13:15 Comment(0)
V
0

Another fairly easy way to do this that iterates only once through your arrays is this:

let hbiCompleteArray = [true, true, true, true, false, true, true, false, false]
let hbiScoreArray = [12, 12, 12, 12, 3, 13, 13, 2, 2]
var completeHbiScores = [Int]()

for score in hbiScoreArray.enumerated() {
  // break out of the loop if we're at the end of hbiCompleteArray
  // assuming that no value means default to false
  guard score.offset < hbiCompleteArray.count else { break }

  // go to the next score if the current element is false
  guard hbiCompleteArray[score.offset] else { continue }

  completeHbiScores.append(score.element)
}
Vermin answered 29/9, 2016 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.