1

Say I have two arrays:

let arrayOne = ["Hi", "Hi", "Hello", "Not Hey", "Howdy", "Hi"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy", "Hi", "Hi"]

and I have this for loop that gets the percent similarity of the doubles:

var matches = 0

for (index, item) in enumerate(arrayOne) {
    if item == arrayTwo[index] {
        matches++
    }
}

However, what if those arrays are longer and I want data points of those similarities instead of one single calculation. What kind of a function could I write where it takes the first 5 elements off the array, returns their similarity with the for loop, and moves on to the next 5? ie. it would be a function that takes two string arrays and returns an array of doubles. I am sure this is a simple question but I do not know how to approach taking 5 array elements at a time and then returning an array of doubles (like data points the the string array similarities).

modesitt
  • 7,052
  • 2
  • 34
  • 64
  • 1
    Have a look at using `Sets` instead of arrays. http://stackoverflow.com/questions/24589181/set-operations-union-intersection-on-swift-array – sbarow Jul 08 '15 at 16:32

3 Answers3

4

I’m not clear quite what you’re asking, however, you might find playing around with zip, map, and reduce helpful.

For example, you could rewrite your original loop like this (assuming Swift 2.0, you’d have to rearrange slightly for 1.2):

zip(arrayOne, arrayTwo).reduce(0) { $0 + ($1.0 == $1.1 ? 1 : 0) }
// returns 4

zip creates a new sequence of the pairs of elements at each corresponding position.

reduce takes a starting value, then keeps a running value by applying a function to the current value and the next value in the sequence – bearing in mind this is a sequence of pair elements, you want to add 1 when they are the same, 0 when they aren’t. This then gives you a count of the positions where both match.

If instead you wanted an array, with true representing a match at that point, and false if different, you could use map:

zip(arrayOne, arrayTwo).map(==)
// returns [true, false, false, false, false, true]

If on the other hand you wanted a list of the differences in string length between the two strings at each position, you could change it to:

zip(arrayOne, arrayTwo).map { (a,b) in
    a.characters.count - b.characters.count 
}
// returns [0, -3, 2, -2, 3, 0]

As some have suggested, Set might help, e.g.:

let commonElements = Set(arrayOne).intersect(arrayTwo)
// returns strings present in both e.g. {"Hi", "Hello”}

This is a good approach if you are OK treating your data as a set i.e. order doesn’t matter and duplicates can be ignored. If order and dupes do matter, you probably have to stick with arrays.

Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
1

Take a look at this. I think it does what you are asking. By introducing a count variable to keep track of the number of items you have processed, you will know when to update your counts array:

var matches = 0

let arrayOne = ["Hi", "Hi", "Hello", "Not Hey", "Howdy", "Hi", "a", "b", "c"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy", "Hi", "Hi", "a", "B", "C"]

var count = 0
var counts:[Int] = []

for (index, item) in enumerate(arrayOne) {
    if item == arrayTwo[index] {
        matches++
    }
    // Have we done 5?  If so, time to update counts array
    if ++count == 5 {
        counts.append(matches)
        count = 0
        matches = 0
    }
}
// If we didn't have 5, just append the matches for the remaining items
if count > 0 {
    counts.append(matches)
}

println(counts)  // prints "[1, 2]"
vacawama
  • 150,663
  • 30
  • 266
  • 294
1

Right - so I think I understand what you're looking for: you want to have a matches function like the one you've written that works for chunks of a certain number of elements. First off, you're going to need a chunk function. There's a good discussion of them here, but they're for arrays, and you're going to want to zip your two arrays together here, so you'll need one for SequenceType. This works:

public extension SequenceType {

  /// Returns an array of arrays of n non-overlapping elements of self
  /// - Parameter n: The size of the chunk
  ///  ```swift
  ///  [1, 2, 3, 4, 5].chunk(2)
  ///
  ///  [[1, 2], [3, 4], [5]]
  /// ```

  func chunk(_ n: Int) -> [[Generator.Element]] {
    var g = self.generate()
    var ret: [[Generator.Element]] = [[]]
    while let next = g.next() {
      if ret.last!.count < n {
        ret[ret.endIndex.predecessor()].append(next)
      } else {
        ret.append([next])
      }
    }
    return ret
  }
}

Then, you need a function that counts the matches in two arrays. You could inline it with a closure, or you could define it separately, it doesn't make much of a difference.

func matchesEvery<
  S0 : SequenceType,
  S1 : SequenceType,
  T  : Equatable where
  S0.Generator.Element == T,
  S1.Generator.Element == T
  >(_ n: Int, s0: S0, s1: S1) -> [Int] {
    return zip(s0, s1)
      .chunk(5)
      .map { $0.reduce(0) { $1.0 == $1.1 ? $0 + 1 : $0 } }
}

That will return:

let arrayOne = ["Hi", "Hi", "Hello", "Not Hey", "Howdy", "Hi"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy", "Hi", "Hi"]

matchesEvery(5, s0: arrayOne, s1: arrayTwo) // [1, 1]

Since in the first five there's one match, and in the last there is one as well.

Community
  • 1
  • 1
oisdk
  • 9,763
  • 4
  • 18
  • 36