Percent Similarity of an Array Swift - ios

Say I have two arrays:
var arrayOne = ["Hi", "Hello", "Hey", "Howdy"]
var arrayOne = ["Hi", "Hello", "Hey", "Not Howdy"]
What could I do to compare how similar the array elements are? As in a function that would return 75% Because the first three elements are the same but the last element are not. The arrays I'm using in my project are strings but they will almost entirely match except for a few elements. I need to see What percent the differences are. Any ideas?

let arrayOne = ["Hi", "Hello", "Hey", "Howdy"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy"]
var matches = 0
for (index, item) in enumerate(arrayOne) {
if item == arrayTwo[index] {
matches++
}
}
Double(matches) / Double(arrayOne.count) // 0.75

Both of these algorithms use the idea that if you have two different length arrays, the highest similarity you can have is short length / long length, meaning that the difference in the array lengths are counted as not matching.
You could add all of the terms to a set and then make your percentage the size of the set / length of longest array.
You could sort both arrays and then do a loop with an index variable for each array and compare the values at the two indices, advancing the index for the array that has the "lower" value in the comparison, or increment a counter if they are equivalent. Your percentage would be the counter / length of longest array.
One thing to think about though is how you want to measure similarity in weird cases. Suppose you have two arrays: [1, 2, 3, 4, 5] and [1, 1, 1, 1, 1]. I don't know whether you would want to say they are completely similar, since all of the elements in the second array are in the first array, or if they only have a similarity of 20% because once the 1 in the first array is "used", it can't be used again.
Just some thoughts.

maybe something like this? (written off top of my head so havent checked if it actually compiles)
var arrayOne = ["Hi", "Hello", "Hey", "Howdy"]
var arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy"]
var matches = 0
for i in 0...arrayOne.count { //assuming the arrays are always the same length
if arrayOne[i] == arrayTwo[i]{
matches++
}
}
var percent = matches / arrayOne.count

A good way to measure the similarity of 2 arrays is to iterate all elements of an array, and keep a cursor on the 2nd array, such that at any time the current element of the iterated array is not greater than the element at the cursor position.
As you may argue, this algorithm require elements to be comparable, and as such it works if the arrays type implements the Comparable interface.
I've worked on a generic function that perform that calculation, here it is:
func compare<T: Comparable>(var lhs: [T], var rhs: [T]) -> (matches: Int, total: Int) {
lhs.sort { $0 < $1 } // Inline sort
rhs.sort { $0 < $1 } // Inline sort
var matches = 0
var rightSequence = SequenceOf(rhs).generate()
var right = rightSequence.next()
for left in lhs {
while right != nil && left > right {
right = rightSequence.next()
}
if left == right {
++matches
right = rightSequence.next()
}
}
return (matches: matches, total: max(lhs.count, rhs.count))
}
Let me say that the implementation can probably be optimized, but my goal here is to show the algorithm, not to provide its best implementation.
The first thing to do is to obtain a sorted version of each of the 2 arrays - for simplicity, I have declared both parameters as var, which allows me to edit them, leaving all changes in the local scope. That's way I am using in-place sort.
A sequence on the 2nd array is created, called rightSequence, and the first element is extracted, copied into the right variable.
Then the first array is iterated over - for each element, the sequence is advanced to the next element until the left element is not greater than the right one.
Once this is done, left and right are compared for equality, in which case the counter of matches is incremented.
The algorithm works for arrays having repetitions, different sizes, etc.

Related

How to detect if a Label contains the same elements as a String(In any orders) - Swift 4

I'd like to detect with a if condition when a label contains all the same elements (in any order) of a 'String'.
Here's an example of my program:
#IBOutlet weak var label: UILabel!
var word: String = "house"
label.text = "ouhse"
It is at this point that I am stuck. How can I detect if the text of label contains the same letters as word?
What I want is a way to know if ‘label’ contains the same letters (and the same amount of letters) as ‘word’ without it being necessarily in the same order.
Unclear exactly what the requirements are. Here's an easy way to see if two strings are "all the same letters" in a fairly broad sense:
let s1 = "house"
let s2 = "ouhse"
s1.sorted() == s2.sorted() // true
But that might not cover what you really want to do (because you didn't enunciate clearly what that might be). For example, this tests "all the same letters" in a somewhat different sense:
let s1 = "yoho"
let s2 = "hoy"
Set(s1) == Set(s2) // true

Generation random (positive and negative) numbers for a quiz

I am writing a Math Quiz app for my daughter in xcode/swift.Specifically, I want to produce a question that will contain at least one negative number to be added or subtracted against a second randomly generated number.Cannot be two positive numbers.
i.e.
What is (-45) subtract 12?
What is 23 Minus (-34)?
I am struggling to get the syntax right to generate the numbers, then decide if the said number will be a negative or positive.
Then the second issue is randomizing if the problem is to be addition or subtraction.
It's possible to solve this without repeated number drawing. The idea is to:
Draw a random number, positive or negative
If the number is negative: Draw another number from the same range and return the pair.
If the number is positive: Draw the second number from a range constrained to negative numbers.
Here's the implementation:
extension CountableClosedRange where Bound : SignedInteger {
/// A property that returns a random element from the range.
var random: Bound {
return Bound(arc4random_uniform(UInt32(count.toIntMax())).toIntMax()) + lowerBound
}
/// A pair of random elements where always one element is negative.
var randomPair: (Bound, Bound) {
let first = random
if first >= 0 {
return (first, (self.lowerBound ... -1).random)
}
return (first, random)
}
}
Now you can just write...
let pair = (-10 ... 100).randomPair
... and get a random tuple where one element is guaranteed to be negative.
Here's my attempt. Try running this in a playground, it should hopefully get you the result you want. I hope I've made something clean enough...
//: Playground - noun: a place where people can play
import Cocoa
let range = Range(uncheckedBounds: (-50, 50))
func generateRandomCouple() -> (a: Int, b: Int) {
// This function will generate a pair of random integers
// (a, b) such that at least a or b is negative.
var first, second: Int
repeat {
first = Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound))) - range.upperBound
second = Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound))) - range.upperBound
}
while (first > 0 && second > 0);
// Essentially this loops until at least one of the two is less than zero.
return (first, second)
}
let couple = generateRandomCouple();
print("What is \(couple.a) + (\(couple.b))")
// at this point, either of the variables is negative
// I don't think you can do it in the playground, but here you would read
// her input and the expected answer would, naturally, be:
print(couple.a + couple.b)
In any case, feel free to ask for clarifications. Good luck !

Init array with range of Int-s, can someone explain this one-liner?

I'd like to initialise an array of Int-s with a series of numbers. Example [0, 1, 2, 3, 4, 5]. I know there are some better way than using the for loop and I've fond this one-liner that works var values: [Int] = (0...4).map() { $0 }. But I just can't understand what's going on here. I understand I have a range 0...4. Is this range in brackets behaving like closure? And what does the map() function do? I can't find it in the reference. And also $0 is something I can't understand. Can someone explain what's going on here?
var values: [Int] = (0...4).map() { $0 }
// result: [0, 1, 2, 3, 4]
.I'm still using Swift 1.2
(0...4)
creates a Range
.map() { $0 }
applies a transformation (which here is no transformation)to each element of that range and returns an array containing the results of that transformation, which is a [Int]. The reference for map, at the bottom.
And the $0 means the first parameter of the method, so $1 would be the second parameter and so on. And here $0 means first 0, then 1...
And for example if you want to create 2, 4, 6, you could use map like this:
var values = (1...3).map { $0 * 2 }
so you multiply first 1 * 2, then 2 * 2 finally 3 * 2.
The brackets surrounding the range are only used to use methods of the Range type, in this case map.
This method takes each elements of your collection (a range in your code), pass it to a closure or block ({ $0 }) and the return value of the closure is used in the collection returned by map.
Swift syntax allow to write closure after the brackets of map(), if the closure is very simple you can even omit return and parameters name which will be called $x where x is a zero-based progressive number, in your case $0 represents the number of your range.
If we expand your code to make it more clear we get
let range = 0...4
bar values: [Int] = range.map({ (i: Int) -> Int in
return i
})

iOS slow image pixel iterating

I am trying to implement RGB histogram computation for images in Swift (I am new to iOS).
However the computation time for 1500x1000 image is about 66 sec, which I consider to be too slow.
Are there any ways to speed up image traversal?
P.S. current code is the following:
func calcHistogram(image: UIImage) {
let bins: Int = 20;
let width = Int(image.size.width);
let height = Int(image.size.height);
let binStep: Double = Double(bins-1)/255.0
var hist = Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Int())))
for i in 0..<bins {
for j in 0..<bins {
for k in 0..<bins {
hist[i][j][k] = 0;
}
}
}
var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage))
var data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
for x in 0..<width {
for y in 0..<height {
var pixelInfo: Int = ((width * y) + x) * 4
var r = Double(data[pixelInfo])
var g = Double(data[pixelInfo+1])
var b = Double(data[pixelInfo+2])
let r_bin: Int = Int(floor(r*binStep));
let g_bin: Int = Int(floor(g*binStep));
let b_bin: Int = Int(floor(b*binStep));
hist[r_bin][g_bin][b_bin] += 1;
}
}
}
As noted in my comment on the question, there are some things you might rethink before you even try to optimize this code.
But even if you do move to a better overall solution like GPU-based histogramming, a library, or both... There are some Swift pitfalls you're falling into here that are good to talk about so you don't run into them elsewhere.
First, this code:
var hist = Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Array(count:bins, repeatedValue:Int())))
for i in 0..<bins {
for j in 0..<bins {
for k in 0..<bins {
hist[i][j][k] = 0;
}
}
}
... is initializing every member of your 3D array twice, with the same result. Int() produces a value of zero, so you could leave out the triple for loop. (And possibly change Int() to 0 in your innermost repeatedValue: parameter to make it more readable.)
Second, arrays in Swift are copy-on-write, but this optimization can break down in multidimensional arrays: changing an element of a nested array can cause the entire nested array to be rewritten instead of just the one element. Multiply that by the depth of nested arrays and number of element writes you have going on in a double for loop and... it's not pretty.
Unless there's a reason your bins need to be organized this way, I'd recommend finding a different data structure for them. Three separate arrays? One Int array where index i is red, i + 1 is green, and i + 2 is blue? One array of a custom struct you define that has separate r, g, and b members? See what conceptually fits with your tastes or the rest of your app, and profile to make sure it works well.
Finally, some Swift style points:
pixelInfo, r, g, and b in your second loop don't change. Use let, not var, and the optimizer will thank you.
Declaring and initializing something like let foo: Int = Int(whatever) is redundant. Some people like having all their variables/constants explicitly typed, but it does make your code a tad less readable and harder to refactor.
Int(floor(x)) is redundant — conversion to integer always takes the floor.
If you have some issues about performance in your code, first of all, use Time Profiler from Instruments. You can start it via Xcode menu Build->Profile, then, Instruments app opened, where you can choose Time Profiler.
Start recording and do all interactions in the your app.
Stop recording and analyse where is the "tightest" place of your code.
Also check options "Invert call tree", "Hide missing symbols" and "Hide system libraries" for better viewing profile results.
You can also double click at any listed function to view it in code and seeing percents of usage

Swift - Create variables with different names in a for loop

I am learning Apple Swift with the hope of releasing apps for the iPhone.
There are three different 'modes' to my game: 5 swipes, 10 swipes, or 25 swipes. Let's use 5 swipes as an example. I want a variable to be assigned to each swipe, which will be a random integer within the range 1...100 (inclusive). Obviously it doesn't seem neat when I am creating variables in a long list like this:
var s1 = arc4random_uniform...
var s2 = arc4random_uniform...
Also that could just be a pain when I get to 25 swipes.
So I thought, maybe I could use a 'for' loop. So:
for index(in 1...5) {
//create variable with different name with a random integer
}
So here's where my problem lies... I am unsure how I would create variables with different names. So: s1, s2, s3, s4, and s5.
It would probably be in the form of an algorithm like:
var s(prevnumber+1) = arc4random_uniform....
I will do it this way:
var numElement = 5 // change to 10 or 25 depends on what you need
var array = Array<UInt32>(count: numElement, repeatedValue: 0)
for i in 0 ..< numElement {
array[i] = arc4random_uniform(100)
}
Then to access the first variable, you can do
array[0]
And it will give you the random number

Resources