0

I need to filter an array by some condition on its elements, but I need to obtain the indices of the elements that passed the test, not the elements themselves.

For example: given an array of Bool, I want to transform that into an array of Int that contains only the indices of the elements of the orginal array that are true.

I could do this:

// INPUT array:
let flags = [true, false, true, false]

// OUTPUT array:
var trueIndices = [Int]()

for (index, value) in flags.enumerated() where value == true {
    trueIndices.append(index)
}

...but it isn't "swifty" at all.

Is there a more elegant way? Something akin to filter(), but that returns the indices instead of the elements.

1
  • Thank you very much everyone. The "swiftiness" in all your answers is TOO DAMN HIGH Commented Sep 20, 2017 at 15:46

3 Answers 3

5

You can directly filter the indices

let flags = [true, false, true, false]    
let trueIndices = flags.indices.filter{ flags[$0] }

and

let falseIndices = flags.indices.filter{ !flags[$0] }
Sign up to request clarification or add additional context in comments.

5 Comments

Oh, it makes sense now. You are filtering the array (?) of all indices with a closure that captures the outer flags array, and tests its contents at the current index ($0).
This is "swiftier"
It's a long way to "think in portals"!
Of course one could define a func indices(where isIncluded: (Element) -> Bool) -> [Index] extension method for collections ...
Just a reminder: this only works well when subscripting is possible, and possible in O(1). Won't work for sequences
4

Not sure how much "swiftier" it is, but you can also use a consecutive filter and map operation or just a single flatMap instead. For sure, you can declare the array immutable and you don't have to write any explicit loops, both of which are usually considered a more functional approach than your current one.

let trueIndices = flags.enumerated().filter{$0.element}.map{$0.offset}
print(trueIndices)

Output:

[0,2]

Using a single flatMap:

let trueIndices = flags.enumerated().flatMap { (offset, flag) in flag ? offset : nil }

5 Comments

Of course! enumerated() gives an array of tuples, I can filter/map that. Guess I’ve been coding for too many hours now...
The flatMap version does not compile in Xcode 9. My suggestion: flags.enumerated().flatMap { (offset, flag) in flag ? offset : nil }
@MartinR thanks for the tip, I've only tested in Xcode 8, but updated my answer, since your solution is more clear anyways
You probably meant "declare the array immutable"
Yeah, I was editing my wording and forgot to change mutable to immutable, thanks.
1

Here's what I would do. It's generalizable to sequences, or even collections whose subscripting isn't O(1).

let indicesOfTrue = Array(flags.enumerated().lazy.filter{ $0.element }.map{ $0.offset })

Comments

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.