2

I have an array of objects

var arrayOfObjects: [Object]?

And they all have a property called depth. I want to find the very next object in that array that has the same depth with a specific object I know the index of:

[
   ...objects_before...,
   object_I_know: {depth:3},
   ...objects_after...
]

Is there a more efficient way other than using a for loop starting from the object_I_know index, and traversing down until it finds one?

6
  • Are depths unique? Commented Mar 14, 2018 at 16:09
  • 2
    More efficient? No. If the objects are not ordered by depth then your only solution is to start iterating from the index you know and looking at each object until you find a match. Then the question becomes one of syntax. Do you write a standard loop or is there some "Swifty" way. But nothing will be more efficient than a basic loop. Commented Mar 14, 2018 at 16:26
  • Now, if the objects in the array are sorted by depth then the solution is trivial. No searching/iterating required. Simply check the object at the next index. It either matches the depth or it doesn't. Commented Mar 14, 2018 at 17:04
  • @rmaddy not necessarily. You're assuming the index is the first object in the sorted array where 2+ objects have the same depth. What if the index is the 2nd sorted object of the same depth? Or 3rd? Or 4th? Commented Mar 14, 2018 at 17:23
  • @Ben The OP wants the next object in the array with the same depth. So I don't understand your point. If the objects are sorted by depth and the current index happens to be the 2nd of 4 with the same depth then simply checking the next object in the array will get the desired result. Commented Mar 14, 2018 at 17:25

5 Answers 5

4
let nextIndex: Int? = (givenIndex ..< array.endIndex).first { index  in
    return array[index].depth == array[givenIndex].depth
}

The item with the object with the same depth would be at that nextIndex if there is one

let nextObject: Object? = (nextIndex == nil) ? nil : array[nextIndex!]
Sign up to request clarification or add additional context in comments.

Comments

0

Here's a sample model I came up with for testing:

struct S {
    let id: Int
    let depth: Int
}

var id = 0
let getID: () -> Int = { defer { id += 1 }; return id }

let objects = [
    S(id: getID(), depth: 1),
    S(id: getID(), depth: 3),
    S(id: getID(), depth: 2),
    S(id: getID(), depth: 3),
    S(id: getID(), depth: 4),
]

Here's a solution that account for the situations in which there are no elements which match the predicate, or only 1 such element:

let isDepth3: (S) -> Bool = { $0.depth == 3 }

// Get the index of the first item (can be nil)
let indexOfFirstDepth3 = objects.index(where: isDepth3)

// Get the index after that (can be nil), so that we can exclude everything before it
let firstIndexOfRemainingItems = indexOfFirstDepth3.flatMap { objects.index($0, offsetBy: +1, limitedBy: objects.endIndex) }
let indexOfSecondDepth3 = firstIndexOfRemainingItems.flatMap {
    // Slice the `objects` array, to omit all the items before up to and including the first depth 3 item.
    // Then find the index of the next next 3 item thereafter.
    return objects[$0...].index(where: isDepth3)
}

// Print results
func stringifyOptional<T>(_ item: T?) -> String {
    return item.map{ String(describing: $0) } ?? "nil"
}

print("First item with depth 3 is \(stringifyOptional(indexOfFirstDepth3.map{ objects[$0] })) at index \(stringifyOptional(indexOfFirstDepth3))")
print("Second item with depth 3 is \(stringifyOptional(indexOfSecondDepth3.map{ objects[$0] })) at index \(stringifyOptional(indexOfFirstDepth3))")

If you're sure that you'll have 2 such elements, and you're sure that force unwrapping will be safe, then this can be simplified dramatically:

let isDepth3: (S) -> Bool = { $0.depth == 3 }

let indexOfFirstDepth3 = objects.index(where: isDepth3)!
let indexOfSecondDepth3 = objects[indexOfFirstDepth3...].index(where: isDepth3)!

// Just printing the result
print("First item with depth 3 is \(objects[indexOfFirstDepth3]) at index \(indexOfFirstDepth3)")
print("Second item with depth 3 is \(objects[indexOfFirstDepth3])) at index \(indexOfFirstDepth3)")

Comments

0

Context

struct DepthObject { let depth: Int }
let objs           = [a, b, c, d ,e]
let index          = 1                   //predetermined index
let depthToFind    = objs[index].depth
let startIndex     = index + 1
let remainingArray = objs[startIndex...] //The slice we want to work with

One way

let aMessage: String? = remainingArray
    .first { $0.depth == depthToFind }
    .flatMap { "The world is yours \($0)" }

Decide based on it

if let nextDepthObject = remainingArray.first(where: { $0.depth == depthToFind }) {
    //Found the next one!
} else {
    //Didn't find it!
}

Loop it

var nextDepthObject: DepthObject? = nil
for sliceDepthObject in remainingArray {
    if sliceDepthObject.depth == depthToFind {
        nextDepthObject = sliceDepthObject
        break
    }
}

Implementing a particular approach

func nextDepthObject(within array: Array<DepthObject>, startingAt index: Int) -> DepthObject? {
    guard index + 1 < array.count && index < array.count else {
        return nil
    }
    let depthToFind = array[index].depth
    let suffixArray = array[(index + 1)...]
    return suffixArray.first { $0.depth == depthToFind }
}

let theNextOne: DepthObject? = nextDepthObject(within: objs, startingAt: index)

Comments

0

You can add an extension over Collection (which Array conforms to):

extension Collection {
    func next(startingWith next: Self.Index, where match: (Element) -> Bool) -> Element? {
        guard next < endIndex else { return nil }
        return self[next..<endIndex].first(where: match)
    }
}

You'd use it like this:

let nextMatch = arrayOfObjects.next(startingWith: foundIndex+1) { $0.depth == searchedDepth }

Comments

0
class Object {
    var name: String
    var depth: Float
    init(name: String, depth: Float) {
        self.name = name
        self.depth = depth
    }
}

let o1 = Object(name: "object1", depth: 10)
let o2 = Object(name: "object2", depth: 12)
let o3 = Object(name: "object3", depth: 4)
let o4 = Object(name: "object4", depth: 12)
let o5 = Object(name: "object5", depth: 14)

let array = [o1, o2, o3, o4, o5]
let knownIndex = 1
let knownDepth = array[knownIndex].depth
var searchResults = [Object]()

// iterate through the second half of the array after the known
// index and break the loop when a match is found

for i in knownIndex + 1..<array.count {
    if array[i].depth == knownDepth {
        searchResults = [array[i]]
        break
    }
}

// after the loop is finished (either by going all the way to the
// end or breaking after a match is found), check your search results

if searchResults.count > 0 {
    print("match found: \(searchResults[0].name)")
} else {
    print("no match found")
}

index(where:) uses a loop also, unbeknownst to the commenter, except that the compiler does it for you behind the scenes. index(where:) also loops through the entire array which is not very efficient if you already know the starting index (which OP does).

4 Comments

What happens if your initial index is 1? Doesn't this return the same object?
Using a for loop instead of index(where:) is not a good starting point.
This answer is missing one important requirement stated in the question. The search needs to be begin with the object after the "object_I_know" index. This answer iterates all objects from the start and will even return the already known object index which is not what the OP wants.
@rmaddy missed that part, updated, thanks for the catch

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.