1

I have an array in SWIFT

let a = [0.0, 0.00250, 0.0050, 0.00749, 0.01, 0.01251, 0.01499, 0.0175, 0.02, 0.022, 0.025] // is always a sequence (increasing)

I also have a second array

let b = [0.0, 0.004, 0.008, 0.012, 0.016, 0.02, 0.024, 0.028, 0.032, 0.036, 0.04, 0.044, 0.048, 0.052, 0.056] // is always a sequence (increasing)

I have to find all elements of "b" that are >= a[0] & <= a[a.count-1] and populate a vector "c" with these values.

So that

let c = [0.0, 0.004, 0.008, 0.012, 0.016, 0.02, 0.024] // ANSWER

What would be the most efficient way to do this?

Pat
  • 325
  • 1
  • 12

4 Answers4

1

Since you know the both sequences to be increasing, you need only make use of the first and last element of the array a. Using these, you can make use of the index(where:) method to find the corresponding indices in the b array, fulfilling the predicate >= (first of a) and <= (last of a). E.g.

if let aFirst = a.first, let aLast = a.last,
   let startIndex = b.index(where: { $0 >= aFirst }),
   case let endIndex = b.index(where: { $0 > aLast }) ?? a.endIndex {
    let c = b[startIndex..<endIndex] // note: this is an ArraySlice<Double>
                                     // wrap in Array(..) to construct a new array
    print(c) 
       // [0.0, 0.0040000000000000001, 0.0080000000000000002, 0.012, 0.016, 0.02, 0.024]
}

Or, as proposed by @MartinR, we can make a guess that we will, on avarage, more quickly fulfil the predicate of the second index(where:) call if we start from the end of b when checking for the first element that fulfills the $0 <= aLast condition:

if let aFirst = a.first, let aLast = a.last,
   let startIndex = b.index(where: { $0 >= aFirst }),
   case let endIndex = b.reversed().index(where: { $0 <= aLast })!.base {
                    /* ^^^^^- as proposed by @MartinR */
    let c = b[startIndex..<endIndex]
    print(c) 
       // [0.0, 0.0040000000000000001, 0.0080000000000000002, 0.012, 0.016, 0.02, 0.024]
}

In the end this approach (as well as the filter approach in the currently available other answers) runs in O(n). A more performant approach would be using binary search, which runs in O(log n) asymptotically. As also mentioned by @MartinR, a good starting point could be the binary search Swift implementations from the Rosetta code:

as mentioned in this Q&A:


Answering the comment:

... I also have another issue that I forgot to mention. I have another array y that has exactly the same number of elements as b. We can regard these as X and Y values of a signal. I have to choose the matching elements from y for the elements I choose in b.

The simplest solution would simply apply the calculated startIndex and endIndex to slice out a part of the y array (just like you sliced out a part of b to construct c).

let d = y[startIndex..<endIndex] 
// assumes y.count == b.count, and that these are not slices
// with non-matching indices

However, I believe a better approach would be using zip. Simply zip y and b and use the result to construct an array of tuples, whereafter you can apply the predicate above to the b members of the zipped sequence:

let y = Array(1...b.count)

if let aFirst = a.first, let aLast = a.last,
   let startIndex = bAndY.index(where: { $0.0 >= aFirst }),
   case let endIndex = bAndY.index(where: { $0.0 > aLast }) ?? a.endIndex {
    let cAndD = bAndY[startIndex..<endIndex]
    print(cAndD)
        /* [(0.0040000000000000001, 2),
            (0.0080000000000000002, 3), 
            (0.012,                 4), 
            (0.016,                 5),
            (0.02,                  6), 
            (0.024,                 7)] */
}

Naturally constructing the cAndD array will yield a minor overhead, but unless you're coding some really HPC application, you shouldn't need to worry about that.

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Actually, I want the range, not the exact matching values. – Pat Jan 17 '17 at 13:16
  • @Pat Ah, my bad. Revised. – dfrib Jan 17 '17 at 13:23
  • I love both the answers - thanks Yannick and dfri ! Is one method faster than the other ? I am looking for the fasting method since my array are big in practise. – Pat Jan 17 '17 at 13:25
  • Depending on the array sizes and values it *may* be more effective to search the end index from the *end* of the array backwards: Roughly `b.reversed().index(where: { $0 <= aLast })!.base` – Martin R Jan 17 '17 at 13:27
  • In addition to MartinR:s comment above: for `n` being the length of `b`, both are `O(n)`. The `filter` approach will use exactly `n` "iterations", in combination with at least `n` comparisons and at most `2n` comparisons. The `index(where:`) approach have a worst-case scenario of `2n` passes (passes fully over `b` in both `index(where:` calls) and likewhise many comparisons. Best-case would be 2 "iterations" and 2 comparisons (when `a` is uniform :) ). On avarage: depends on array sizes and the "overlap" of the sequences. – dfrib Jan 17 '17 at 13:31
  • @MartinR cool with the [`.base`](https://developer.apple.com/reference/swift/slice/2142239-base), didn't know about that :) and yes, binary search would be a better alternative here, `O(log n)` as compared to the `O(n)` of our two answers. – dfrib Jan 17 '17 at 13:34
  • Thanks guys ! Where do I insert ".base" code from Martin ? And any help on binary search would be much appreciated :) – Pat Jan 17 '17 at 13:37
  • @Pat the `.base` call is only needed if you use the `reversed()` approach by MartinR in his comment above (transfirming a `ReversedRandomAccessIndex>` to the index type of `b`, namely `Int`). The `reversed()` approach is also `O(n)`, it simply makes a guess that we will more quickly fulfil the predicate of the `index(where:)` call if we start from the back when checking for the `<= aLast` condition. A faster approach (even w.r.t. asymptotic complexity) than the two `O(n)` answers you've been given here is, however, using binary search. – dfrib Jan 17 '17 at 13:39
  • Here is something that might serve as a starting point: http://stackoverflow.com/a/26679191/1187415. – Martin R Jan 17 '17 at 13:44
  • Thanks so much ! I will look into binary search shortly. I also have another issue that I forgot to mention. I have another array "y" that has exactly the same number of elements as "b". We can regard these as X and Y values of a signal. I have to choose the matching elements from "y" for the elements I choose in "b". – Pat Jan 17 '17 at 14:01
  • @Pat happy to help. Added an additional paragraph to the answer to answer your comment. – dfrib Jan 17 '17 at 14:17
  • 1
    Thanks dfri ! Yes, I realized I could use the same indices on y. Will also consider your other suggestion about zip. Thanks :) – Pat Jan 17 '17 at 14:21
0

You can use the filter function on your array which will return you a new array. You can compare if the value is >= a[0] and <= a[a.count-1]. Obviously, you should always check if the array has more than 0 elements.

let smallestValue = a[0]
let highestValue = a[a.count-1]

let c = b.filter() { return smallestValue <= $0 && $0 <= highestValue }

Calculate the indexes

Since b is always increased by a fixed amount, e.g. 0.004 in your case, you could calculate your index. This is really efficient because you only have two comparisons and at max two calculations which are really fast.

let smallestValueA = a[0]
let highestValueA = a[a.count-1]
let smallestValueB = b[0]
let highestValueB = b[b.count-1]
let increase = 0.004 //based on your example
var smallestIndex: Int = 0
var highestIndex: Int = b.count

if smallestValueA > smallestValueB {
    smallestIndex = Int((smallestValueA - smallestValueB) / increase).rounded(.up)
}

if highestValueA < highestValueB {
    highestIndex = Int(((highestValueB - highestValueA) - smallestValueB) / increase)
}

let c = b[smallestIndex..<highestIndex] //[0.0, 0.0040000000000000001, 0.0080000000000000002, 0.012, 0.016, 0.02, 0.024]
Yannick
  • 3,210
  • 1
  • 21
  • 30
  • Hi Yannick, I just realized that "b" may not always increase by fixed amount. As its time value, sometimes, there may be uneven "Delta Ts". Sorry about the confusion. – Pat Jan 17 '17 at 14:13
  • No problem! If they are really similar, maybe you can still try to calculate the value. Don't rely on the calculated number, though. Make some checks and see if it works out. – Yannick Jan 17 '17 at 14:20
  • Note that the latter approach above also assumes that both arrays start at the same value. It could be modified to find the first value in `a` that is `>=` to the first value in `b`, and thereafter use maths for the remaining part, but this finding operation is also `O(n)`. – dfrib Jan 17 '17 at 14:21
  • No it does not! Try it out yourself. If smallestValueA is smaller or equal than smallestValueB, then we can start at index 0. Else, we calculate difference – Yannick Jan 17 '17 at 14:22
  • I just did: try with `let a = [0.00250, 0.0050, 0.00749, 0.01, 0.01251, 0.01499, 0.0175, 0.02, 0.022, 0.025]` (note: first element of `a` from example removed) and you'll see that the `c` array includes `0`, even though it doesn't fulfil the criteria `b_elem >= a[0]`. – dfrib Jan 17 '17 at 14:23
0

Another way of doing this would be:

let c = b.filter() { return (a.first)! <= $0 && $0 <= (a.last)! }
Mr. Xcoder
  • 4,719
  • 5
  • 26
  • 44
-1

You should not save there values to be a array.

if it is not equal for each other,

save there to be a set and use

let c = a.intersection(b)

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html

Archie
  • 150
  • 6
  • A `Set` is a non-ordered collection, the OP is working with ordered collections, so the `intersection` approach is not viable here (non-ordered time stamps might be quite confusing in the context I believe OP is using them :) ). – dfrib Jan 17 '17 at 16:28