What is the difference between elementsEqual and '==' in Swift? - arrays

I'm going through the Swift Standard Library, and I came across the method elementsEqual for comparing sequences.
I'm not really seeing the value of this function because it will only return true if the order is exactly the same. I figured this would have some use if it could tell me if two sequences contained the same elements, they just happen to be in a different order, as that would save me the trouble of sorting both myself.
Which brings me to my question:
Is there any difference between using elementsEqual and '==' when comparing two sequences? Are there pros and cons for one vs the other?
I am in my playground, and have written the following test:
let values = [1,2,3,4,5,6,7,8,9,10]
let otherValues = [1,2,3,4,5,6,7,8,9,10]
values == otherValues
values.elementsEqual(otherValues)
both of these checks result in true, so I am not able to discern a difference here.

After playing with this for a while to find a practical example for the below original answer I found a much more simple difference: With elementsEqual you can compare collections of different types such as Array, RandomAccessSlice and Set, while with == you can't do that:
let array = [1, 2, 3]
let slice = 1...3
let set: Set<Int> = [1, 2, 3] // remember that Sets are not ordered
array.elementsEqual(slice) // true
array.elementsEqual(set) // false
array == slice // ERROR
array == set // ERROR
As to what exactly is different, #Hamish provided links to the implementation in the comments below, which I will share for better visibility:
elementsEqual
==
My original answer:
Here's a sample playground for you, that illustrates that there is a difference:
import Foundation
struct TestObject: Equatable {
let id: Int
static func ==(lhs: TestObject, rhs: TestObject) -> Bool {
return false
}
}
// TestObjects are never equal - even with the same ID
let test1 = TestObject(id: 1)
let test2 = TestObject(id: 1)
test1 == test2 // returns false
var testArray = [test1, test2]
var copiedTestArray = testArray
testArray == copiedTestArray // returns true
testArray.elementsEqual(copiedTestArray) // returns false
Maybe someone knows for sure, but my guess is that == computes something like memoryLocationIsEqual || elementsEqual (which stops evaluating after the memory location is indeed equal) and elementsEqual skips the memory location part, which makes == faster, but elementsEqual more reliable.

Related

Is Swift array reversed()[n] efficient or not?

When you call reversed() on an array in Swift, you get a ReverseCollection which merely wraps the original array with reversed access. Thus this is extremely efficient:
let arr = [1,2,3,4]
for i in arr.reversed() { print(i) }
Nothing actually got reversed except the access; the time complexity of reversed here is O(1). Cool!
But when I index into reversed() by an integer and check Quick Help, it appears I've lost all that efficiency; I'm shown the Sequence reversed() which generates a new array:
let arr = [1,2,3,4]
let i = arr.reversed()[1] // ???? this is a different `reversed()`!
And this seems to be true, because a reversed() array does not, itself, support indexing by number:
let arr = [1,2,3,4]
let rev = arr.reversed()
let i = rev[1] // compile error!
So my question is: is it really true that indexing by number into a reversed() array, as in my second example, loses the efficiency of the ReverseCollection index reversal?
Yes, indexing by Int is causing you to lose your O(1) access into the reversed array. Quite the gotcha!
As you note, reversed() here is an overloaded method; on Array specifically, you have two definitions to choose from:
BidirectionalCollection.reversed(), which returns a ReversedCollection, and
Sequence.reversed(), which turns any sequence into a reversed [Element]
The overloading here is most confusing for Array itself, because it's the only Sequence type such that type(of: x) == type(of: x.reversed()).
The Swift type checker prefers more specific overloads over less-specific ones, so in general, the compiler will use the BidirectionalCollection overload instead of the Sequence one where possible. The rub: BidirectionalCollection has an opaque index type, and cannot be indexed using an Int; when you do index into the collection with an Int, the compiler is instead forced to choose the Sequence overload over the BidirectionalCollection one. This is also why your second code sample fails to compile: Swift code inference does not take into account surrounding context on other lines; on its own, rev is preferred to be a ReversedCollection<Array<Int>>, so attempting to index into it with an Int fails.
You can see this a little more clearly with the following:
func collType1<T: Collection>(_: T) {
print(T.self) // ReversedCollection<Array<Int>>
print(T.Index.self) // Index
}
func collType2<T: Collection>(_: T) where T.Index == Int {
print(T.self) // Array<Int>
print(T.Index.self) // Int
}
let x: [Int] = [1, 2, 3]
collType1(x.reversed())
collType2(x.reversed())
Lest you wonder whether the compiler can optimize around this when the fact of Int-based indexing appears to not have any other side effects, at the time of writing, the answer appears to be "no". The Godbolt output is a bit too long to reproduce here, but at the moment, comparing
func foo1(_ array: [Int]) {
if array.reversed()[100] > 42 {
print("Wow!")
}
}
with
func foo2(_ array: [Int]) {
if array.reversed().dropFirst(100).first! > 42 {
print("Wow!")
}
}
with optimizations enabled shows foo2 performing direct array access
cmp qword ptr [rdi + 8*rax + 24], 43
having optimized away the ReversedCollection wrapper entirely, while foo1 goes through significantly more indirection.
Ferber explained the reason very well.
Here's an ad-hoc solution (which may not be preferred by everyone, because we are extending types from the standard library):
// RandomAccessCollection ensures fast index creation
extension ReversedCollection where Base: RandomAccessCollection {
subscript(_ offset: Int) -> Element {
let index = index(startIndex, offsetBy: offset)
return self[index]
}
}
[1, 2, 3].reversed()[0] // 3

Compared values have no visual difference with jest

I got a problem with my jest/chai unit test.
As you can see I received the same output but the test failed with message: "compared values have no visual difference". How can I fix this?
This is common in javascript. As you probably know already, an array is an object, so I would be explaining using objects.
TLDR: Variable assigned to an object is only a pointer to such value in memory. Hence it is impossible for two variables despite having the different objects containing the same PHYSICAL value but not referring to the same address in memory to be equal.
Javascript compares Objects by identity which means that for the case of the objects a and b below:
Example 1:
a = {name: 'tolumide'}
b = {name: 'tolumide'}
a === b // False
a !== b //True
Although a and b have the same content, there identity differs hence =>
a !== b //True
However, this is different from example 2 below:
Example 2:
c = {name: 'tolumide'}
c = d
c === d // True
c !== d // False
This is because c and d refer to the same value in memory.
What you need is for testing in jest would be something like this:
const expected = ['Alice', 'Bob', 'Eve']
expect.arrayContaining(array)
expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(expected));
OR
const shoppingList = ['beer', 'whiskey']
expect(shoppingList).toContain('beer');
Jest source
Forgive me if I state anything you already know, but the issue appears to be in the underlying object comparison performed by jest.
Array's in javascript are just objects with numbered properties, i.e.
{
0: "value at first index",
1: "value at second index"
}
As such the comparison is done on this underlying object. According to this issue raised by someone on the jest github page, it is possible that the function you are using to create the sorted array is returning a none standard array back, so the comparison fails on the underlying object.
Similar looking issue:
https://github.com/facebook/jest/issues/5998
The solution suggested by one commenter is to Json.Stringify() each array in the test and perform a comparison on that, as this will compare the values themselves.
This is however, as stated "A work around not a fix".
Edit
The stringify option does not work in all cases and should be avoided.
The complete solution for testing if two arrays are equal is found in the first answer to this question: How to check if two arrays are equal with JavaScript?
Disclaimer: Not my code!
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
// If you don't care about the order of the elements inside
// the array (i.e. [1, 2, 3] == [3, 2, 1]), you should sort both arrays here.
// Please note that calling sort on an array will modify that array.
// you might want to clone your array first.
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}

Compare arrays in swift

Trying to understand how swift compares arrays.
var myArray1 : [String] = ["1","2","3","4","5"]
var myArray2 : [String] = ["1","2","3","4","5"]
// 1) Comparing 2 simple arrays
if(myArray1 == myArray2) {
println("Equality")
} else {
println("Equality no")
}
// -> prints equality -> thanks god
// 2) comparing to a "copy" of an array
// swift copies arrays when passed as parameters (as per doc)
func arrayTest(anArray: [String]) -> Bool {
return anArray == myArray1
}
println("Array test 1 is \(arrayTest(myArray1))")
println("Array test 2 is \(arrayTest(myArray2))")
// equality works for both
myArray2.append("test")
println("Array test 2 is \(arrayTest(myArray2))")
// false (obviously)
myArray2.removeAtIndex(5)
println("Array test 2 is \(arrayTest(myArray2))")
// true
Apple says there are optimisations behind the scene on array copies. Looks like sometimes - not always - structures are actually copied or not.
That said,
1) is == iterating over all the array to perform a element-based comparison ? (looks like it)
-> How about performance / memory usage on very large arrays then ?
2) Are we sure == will ever return true if all elements are equal ? I have bad memories of == on Java Strings
3) Is there a way to check if myArray1 and myArray2 are technically using the same "memory location" / pointer / etc. ? i'm after understanding how the optimisation works and potential caveats.
Thanks.
You’re right to be slightly nervous about ==:
struct NeverEqual: Equatable { }
func ==(lhs: NeverEqual, rhs: NeverEqual)->Bool { return false }
let x = [NeverEqual()]
var y = x
x == y // this returns true
[NeverEqual()] == [NeverEqual()] // false
x == [NeverEqual()] // false
let z = [NeverEqual()]
x == z // false
x == y // true
y[0] = NeverEqual()
x == y // now false
Why? Swift arrays do not conform to Equatable, but they do have an == operator, defined in the standard library as:
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
This operator loops over the elements in lhs and rhs, comparing the values at each position. It does not do a bitwise compare – it calls the == operator on each pair of elements. That means if you write a custom == for your element, it’ll get called.
But it contains an optimization – if the underlying buffers for the two arrays are the same, it doesn’t bother, it just returns true (they contain identical elements, of course they’re equal!).
This issue is entirely the fault of the NeverEqual equality operator. Equality should be transitive, symmetric and reflexive, and this one isn't reflexive (x == x is false). But it could still catch you unawares.
Swift arrays are copy-on-write – so when you write var x = y it doesn’t actually make a copy of the array, it just points x’s storage buffer pointer at y’s. Only if x or y are mutated later does it then make a copy of the buffer, so that the unchanged variable is unaffected. This is critical for arrays to behave like value types but still be performant.
In early versions of Swift, you actually could call === on arrays (also in early versions, the mutating behaviour was a bit different, if you mutated x, y would also change even though it had been declared with let – which freaked people out so they changed it).
You can kinda reproduce the old behaviour of === on arrays with this (very implementation-dependent not to be relied-on except for poking and prodding investigations) trick:
let a = [1,2,3]
var b = a
a.withUnsafeBufferPointer { outer in
b.withUnsafeBufferPointer { inner in
println(inner.baseAddress == outer.baseAddress)
}
}
== in Swift is the same as Java's equals(), it compares values.
=== in Swift is the same as Java's ==, it compares references.
In Swift you can compare array content values as easy as this:
["1", "2"] == ["1", "2"]
But this will not work if you want to compare references:
var myArray1 = [NSString(string: "1")]
var myArray2 = [NSString(string: "1")]
myArray1[0] === myArray2[0] // false
myArray1[0] == myArray2[0] // true
So the answers:
I think the performance is optimal for doing value (not reference)
comparisons
Yes, if you want to compare values
Swift arrays are value type and not reference type. So the memory
location is the same only if you compare it to itself (or use unsafe
pointers)
It depends on how do you want to compare. For example:
["1", "2"] == ["1", "2"] // true
but
["1", "2"] == ["2", "1"] // false
If you need that second case to also be true and are ok with ignoring repetitive values, you can do:
Set(["1", "2"]) == Set(["2", "1"]) // true
(use NSSet for Swift 2)
For compare arrays of custom objects we can use elementsEqual.
class Person {
let ID: Int!
let name: String!
init(ID: Int, name: String) {
self.ID = ID
self.name = name
}
}
let oldFolks = [Person(ID: 1, name: "Ann"), Person(ID: 2, name: "Tony")]
let newFolks = [Person(ID: 2, name: "Tony"), Person(ID: 4, name: "Alice")]
if oldFolks.elementsEqual(newFolks, by: { $0.ID == $1.ID }) {
print("Same people in same order")
} else {
print("Nope")
}
Arrays conform to Equatable in Swift 4.1, negating the caveats mentioned in previous answers. This is available in Xcode 9.3.
https://swift.org/blog/conditional-conformance/
But just because they implemented == did not mean Array or Optional conformed to Equatable. Since these types can store non-equatable types, we needed to be able to express that they are equatable only when storing an equatable type.
This meant these == operators had a big limitation: they couldn’t be used two levels deep.
With conditional conformance, we can now fix this. It allows us to write that these types conform to Equatable—using the already-defined == operator—if the types they are based on are equatable.
If you have an array of custom objects, one has to be careful with the equality test, at least with Swift 4.1:
If the custom object is not a subclass of NSObject, the comparison uses the static func == (lhs: Nsobject, rhs: Nsobject) -> Bool, which has to be defined.
If it is a subclass of NSObject, it uses the func isEqual(_ object: Any?) -> Bool, which has to be overridden.
Please check the following code, and set breakpoints at all return statements.
class Object: Equatable {
static func == (lhs: Object, rhs: Object) -> Bool {
return true
}
}
The following class inheritates Equatable from NSObject
class Nsobject: NSObject {
static func == (lhs: Nsobject, rhs: Nsobject) -> Bool {
return true
}
override func isEqual(_ object: Any?) -> Bool {
return true
}
}
They can be compared with:
let nsObject1 = Nsobject()
let nsObject2 = Nsobject()
let nsObjectArray1 = [nsObject1]
let nsObjectArray2 = [nsObject2]
let _ = nsObjectArray1 == nsObjectArray2
let object1 = Object()
let object2 = Object()
let objectArray1 = [object1]
let objectArray2 = [object2]
let _ = objectArray1 == objectArray2
Whereas comparing arrays in swift, must be clear what are the comparison entities.
Ex:
A = [1,2,3]
B = [2,1,3]
return A == B -> false
The comparison is being made traversing both arrays and comparing its index's data.
return Set(A) == Set(B) -> true
Because of function Set(), the comparison is made by whole single value collection.
"Set operations are not limited to use with other sets. Instead, you
can perform set operations with another set, an array, or any other
sequence type."
Use the “equal to” operator (==) to test whether two sets contain > the same elements.
https://developer.apple.com/documentation/swift/set
Can find more here:
https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html

Swift Dictionary of Arrays

I am making an app that has different game modes, and each game mode has a few scores. I am trying to store all the scores in a dictionary of arrays, where the dictionary's key is a game's id (a String), and the associated array has the list of scores for that game mode. But when I try to initialize the arrays' values to random values, Swift breaks, giving me the error below. This chunk of code will break in a playground. What am I doing wrong?
let modes = ["mode1", "mode2", "mode3"]
var dict = Dictionary<String, [Int]>()
for mode in modes
{
dict[mode] = Array<Int>()
for j in 1...5
{
dict[mode]?.append(j)
let array:[Int] = dict[mode]!
let value:Int = array[j] //breaks here
}
}
ERROR:
Execution was interrupted, reason: EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP, subcode=0x0).
Your problem is array subscripts are zero-based. So when you write:
var a: [Int] = []
for i in 1...5 {
a.append(42)
println(a[i])
}
you will get a runtime error, because first time around the loop you are subscripting a[1] when there is only an a[0]. In your code, you either need to do for j in 0..<5 or let value = array[j-1].
By the way, even though it’s perfectly safe to do dict[mode]! (since you just added it), it’s a habit best avoided as one of these days your code won’t be as correct as you think, and that ! will explode in your face. There’s almost always a better way to write what you want without needing !.
Also, generally speaking, whenever you use array subscripts you are risking an accidental screw-up by accidentally addressing an out-of-bounds index like here. There are lots of alternatives that mean actually using a[i] is easy to avoid:
If you want the indices for a collection (like an array), instead of:
for i in 0..<a.count { }
you can write
for i in indices(a) { }
If you want to number the elements in an array, instead of
for i in indices(a) { println("item \(i) is \(a[i])" }
you can write
for (i, elem) in enumerate(a) { println("item \(i) is \(elem)") }
If the collection happens to have an Int for an index (such as Array), you can use i as an index, but if it doesn’t (such as String) an alternative to get the index and element is:
let s = "hello"
for (idx, char) in Zip2(indices(s),s) { }
If you want the first or last element of an array, instead of:
if a.count > 0 { let x = a[0] }
if a.count > 0 { let x = a[a.count - 1] }
you can write
if let first = a.first { let x = first }
if let last = a.last { let x = first }
Prefer map, filter and reduce to for loops in general (but don’t obsess over it, sometimes a for loop is better)

Swift: optional array count

In Objective-C, if I had the following property:
#property (strong, nonatomic) NSArray * myArray;
A method to return a number of objects in myArray would look like:
- (NSInteger) numberOfObjectsInMyArray
{
return [self.myArray count];
}
This would return either the number of objects in the array, or 0 if myArray == nil;
The best equivalent I can think of for doing this in Swift is:
var myArray: Array<String>?
func numberOfObjectsInMyArray() -> Int
{
return myArray ? myArray!.count : 0
}
So checking the optional array contains a value, and if so unwrap the array and return that value, otherwise return 0.
Is this the correct way to do this? Or is there something simpler?
Try using the nil coalescing operator.
According to the Apple Documentation:
The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil.
So your function could look like this:
func numberOfObjectsInMyArray() -> Int {
return (myArray?.count ?? 0)
}
I agree with others that this could be a bad idea for a number of reasons (like making it look like there is an array with a count of "0" when there isn't actually an array at all) but hey, even bad ideas need an implementation.
EDIT:
So I'm adding this because two minutes after I posted this answer, I came across a reason for doing exactly what the author wants to do.
I am implementing the NSOutlineViewDataSource protocol in Swift. One of the functions required by the protocol is:
optional func outlineView(_ outlineView: NSOutlineView,
numberOfChildrenOfItem item: AnyObject?) -> Int
That function requires that you return the number of children of the item parameter. In my code, if the item has any children, they will be stored in an array, var children: [Person]?
I don't initialize that array until I actually add a child to the array.
In other words, at the time that I am providing data to the NSOutlineView, children could be nil or it could be populated, or it could have once been populated but subsequently had all objects removed from it, in which case it won't be nil but it's count will be 0. NSOutlineView doesn't care if children is nil - all it wants to know is how many rows it will need to display the item's children.
So, it makes perfect sense in this situation to return 0 if children is nil. The only reason for calling the function is to determine how many rows NSOutlineView will need. It doesn't care whether the answer is 0 because children is nil or because it is empty.
return (children?.count ?? 0) will do what I need. If children is nil it will return 0. Otherwise it will return count. Perfect!
That looks like the simpler way.
The Objective-C code is shorter only because nil is also a form of 0, being a C-based language.
Since swift is strongly typed you don't have such a shorthand. In this specific case it requires a little more effort, but in general it saves you most of the headaches caused by loose typing.
Concerning the specific case, is there a reason for making the array optional in the first place? You could just have an empty array. Something like this might work for you:
var myArray: Array<String> = []
func numberOfObjectsInMyArray() -> Int {
return myArray.count
}
(Source for this information)
How about using optional for return value?
var myArray: Array<String>?
func numberOfObjectsInMyArray() -> Int? {
return myArray?.count
}
I think that this way is safer.
(Source for this information)

Resources