Why Do Two Distinct Array Literals Equal Each Other in Swift

Why do two distinct array literals equal each other in Swift?

Josh's answer is close, but not quite right. Option-click on the equals operator. Your literals are Foundation.CharacterSets.

public static func == (lhs: CharacterSet, rhs: CharacterSet) -> Bool

For literal resolution, the compiler is going to search

  1. The module you're working in.
  2. Your imports.
  3. The Swift standard library. (Which has some special module-scope-disambiguation rule where implicitly-typed literals are Arrays, because that makes working with the language easier for the most part.)

Sample Image

Is this a bug of ambiguity? Yes. Is it resolvable? I doubt it. I bet it's broken because nobody has been able to get the performance to be good enough if it did an exhaustive search. But please, log a bug, find out, and report back!

Difference between == and ===

In short:

== operator checks if their instance values are equal, "equal to"

=== operator checks if the references point the same instance, "identical to"

Long Answer:

Classes are reference types, it is possible for multiple constants and variables to refer to the same single instance of a class behind the scenes. Class references stay in Run Time Stack (RTS) and their instances stay in Heap area of Memory. When you control equality with == it means if their instances are equal to each other. It doesn't need to be same instance to be equal. For this you need to provide a equality criteria to your custom class. By default, custom classes and structures do not receive a default implementation of the equivalence operators, known as the “equal to” operator == and “not equal to” operator != . To do this your custom class needs to conform Equatable protocol and it's static func == (lhs:, rhs:) -> Bool function

Let's look at example:

class Person : Equatable {
let ssn: Int
let name: String

init(ssn: Int, name: String) {
self.ssn = ssn
self.name = name
}

static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.ssn == rhs.ssn
}
}

P.S.: Since ssn(social security number) is a unique number, you don't need to compare if their name are equal or not.

let person1 = Person(ssn: 5, name: "Bob")
let person2 = Person(ssn: 5, name: "Bob")

if person1 == person2 {
print("the two instances are equal!")
}

Although person1 and person2 references point two different instances in Heap area, their instances are equal because their ssn numbers are equal. So the output will be the two instance are equal!

if person1 === person2 {
//It does not enter here
} else {
print("the two instances are not identical!")
}

=== operator checks if the references point the same instance, "identical to". Since person1 and person2 have two different instance in Heap area, they are not identical and the output the two instance are not identical!

let person3 = person1

P.S: Classes are reference types and person1's reference is copied to person3 with this assignment operation, thus both references point the same instance in Heap area.

if person3 === person1 {
print("the two instances are identical!")
}

They are identical and the output will be the two instances are identical!

Remove duplicates from a multi-dimensional array

Are arrays equatable? Can I compare them using ==

Prior to Swift 4.1, Array didn't conform Equatable. There was however an overload of == that compared two arrays with Equatable elements, which is what enabled this to compile:

if ["1", "2"] == ["1", "2"] { // using <T : Equatable>(lhs: [T], rhs: [T]) -> Bool
print("true")
}

However in Swift 4.1 (available with Xcode 9.3), Array<Element> now conforms to Equatable when its Element conforms to Equatable. This change is given in the changelog:

Swift 4.1


[...]

  • SE-0143 The standard library types Optional, Array, ArraySlice, ContiguousArray, and Dictionary now conform to the Equatable protocol when their element types conform to Equatable. This allows the == operator to compose (e.g., one can compare two values of type [Int : [Int?]?] with ==), as well as use various algorithms defined for Equatable element types, such as index(of:).

Your example with multiDimArr.removeDups() compiles and runs as expected in 4.1, yielding the result [[1, 2, 3], [1, 2, 4]].

In Swift 4.0.3, you could hack it by adding another overload of removeDups() for nested arrays:

extension Array {
func removeDups<T : Equatable>() -> [Element] where Element == [T] {

var result = [Element]()

for element in self{
if !result.contains(where: { element == $0 }) {
result.append(element)
}
}

return result
}
}

let multiDimArr = [[1, 2, 3], [1, 2, 3], [1, 2, 4]]
print(multiDimArr.removeDups()) // [[1, 2, 3], [1, 2, 4]]

This does unfortunately lead to some code duplication, but at least you'll be able to get rid of it when updating to 4.1.

The fact that this example doesn't compile in either 4.0.3 or 4.1:

if [1, 2] == [1, 2] { // error: Ambiguous use of operator '=='
print("true")
}

is due to the bug SR-5944 – the compiler is considering it to be ambiguous due to == overloads for IndexSet and IndexPath (both of which are ExpressibleByArrayLiteral). But Swift should default an array literal to Array though, resolving the ambiguity.

Saying either:

if [1, 2] as [Int] == [1, 2] {
print("true")
}

or not importing Foundation resolves the issue.


Finally, it's worth noting that the performance of removeDups() can be improved if the Element type is also Hashable, allowing it to run in linear, rather than quadratic time:

extension Array where Element : Hashable {

func removeDups() -> [Element] {
var uniquedElements = Set<Element>()
return filter { uniquedElements.insert($0).inserted }
}
}

Here we're using a set to store the elements that we've seen, omitting any that we've already inserted into it. This also allows us to use filter(_:), as @Alexander points out.

And in Swift 4.2, Array also conditionally conforms to Hashable when its Element is Hashable:

Swift 4.2


[...]

  • SE-0143 The standard library types Optional, Array, ArraySlice, ContiguousArray, Dictionary, DictionaryLiteral, Range, and ClosedRange now conform to the Hashable protocol when their element or bound types (as the case may be) conform to Hashable. This makes synthesized Hashable implementations available for types that include stored properties of these types.

How to compare two arrays and remove matching elements from one array in Swift iOS

Converting the arrays to Sets and using subtract is a simple and efficient method:

let array1 = ["Lahari", "Vijayasri"]
let array2 = ["Lahari", "Vijayasri", "Ramya", "Keerthi"]

let resultArray = Array(Set(array2).subtracting(Set(array1)))

If maintaining the order of array2 is important then you can use filter with a set -

let compareSet = Set(array1)

let resultArray = array2.filter { !compareSet.contains($0) }

Expression was too complex initializing a Set from an Array literal

Large literals just don't quite work in Swift. If you want such a big literal, you need to store it in a file and read it. In this case, I recommend using the JSON format.

Create a json file named myData.json or whatever and write your literal:

[
"aardvark",
"aardvarks",
"abaci",
"aback",
// ... 81,000 more words ...
"zygote",
"zygotes",
"zygotic",
"zymurgies",
"zymurgy"
]

Fortunately, JSON array syntax is almost the same as Swift array literals!

Now you can read the file like this:

if let path = Bundle.main.url(forResource: "myData", withExtension: "json"), let data = try? Data(contentsOf: path) {
let decoder = JSONDecoder()
if let stringArray = try? decoder.decode([String].self, from: data) {
let someSet = Set(stringArray)
}
}

When filtering an array literal in swift, why does the result contain optionals?

I don’t think it’s necessarily a bug though it is confusing. It’s a question of where the promotion to optional happens to make the whole statement compile. A shorter repro that has the same behavior would be:

let i: Int? = 1
// x will be [Int?]
let x = [1,2,3].filter { $0 == i }

Bear in mind when you write nonOptional == someOptional the type of the lhs must be promoted to optional implicitly in order for it to work, because the == that you are using is this one in which both sides must be optional:

public func ==<T>(lhs: T?, rhs: T?) -> Bool

The compiler needs to promote something in this entire statement to be an optional, and what it chose was the integer literals inside [1,2,3]. You were instead expecting the promotion to happen at the point of the ==, so you could compare the non-optional sourceElement with the optional result of Int(_:String), but this isn’t necessarily guaranteed (not sure to what extent the ordering/precedence of these promotions is specced vs just the way the compiler was coded…)

The reason this doesn’t happen in the two-line version is when you write as one line let seq = [1,2,3], the type of seq is decided there. Then on the next line, the compiler doesn’t have as much latitude, therefore it must promote sourceElement to be an Int? so it can be compared with Int(removeElement) using ==.

Another way of making the code perform the conversion at the point you expect would be:

let r = [1,2,3].filter { sourceElement in
return !["1", "2"].contains { (removeElement: String)->Bool in
// force the optional upgrade to happen here rather than
// on the [1,2,3] literal...
Optional(sourceElement) == Int(removeElement)
}
}

What is the syntax for multidimensional array in Swift?

Yes, there is nothing built-in (as far as I know). You can define a custom class/struct (as in Most efficient way to access multi-dimensional arrays in Swift?
or How to Declare a Multidimensional Boolean array in Swift?) with a subscript operator, so that a[0,0] = 1 works.

Here is a mixture of those solutions, but as a
generic struct instead of class. I have also changed the order of
the rows and columns parameters because I find that more natural:

struct Array2D<T : IntegerLiteralConvertible > {
let rows : Int
let cols : Int
var matrix: [T]

init(rows : Int, cols : Int) {
self.rows = rows
self.cols = cols
matrix = Array(count : rows * cols, repeatedValue : 0)
}

subscript(row : Int, col : Int) -> T {
get { return matrix[cols * row + col] }
set { matrix[cols*row+col] = newValue }
}
}

I don't see how to create such a thing from a literal like
[2, 3 ;; -1, 0]. But you could initialize it from a nested array:

extension Array2D {

init(_ elements: [[T]]) {
let rows = elements.count
let cols = elements[0].count
self.init(rows: rows, cols: cols)
for i in 0 ..< rows {
assert(elements[i].count == cols, "Array must have same number of elements for each row")
self.matrix.replaceRange(cols * i ..< cols * (i+1), with: elements[i])
}
}
}

Example:

let array = Array2D([[1, 2, 3], [4, 5, 6]])
println(array.rows) // 2
println(array.cols) // 3
println(array[1, 2]) // 6
println(array[1, 0]) // 4

You can additionally implement the ArrayLiteralConvertible protocol to initialize a 2d array from a nested array literal:

extension Array2D : ArrayLiteralConvertible {

init(arrayLiteral elements: [T]...) {
self.init(elements)
}
}

Example:

let array : Array2D = [[1, 2, 3], [4, 5, 6]]

For square arrays (rows == columns) you could alternatively initialize it from a plain array:

extension Array2D {

init(_ elements: [T]) {
let rows = Int(sqrt(Double(elements.count)))
assert(rows * rows == elements.count, "Number of array elements must be a square")
self.init(rows: rows, cols: rows)
self.matrix = elements
}
}

Example:

let squareArray = Array2D([2, 3, -1, 0])
println(squareArray.rows) // 2
println(squareArray.cols) // 3
println(squareArray[1, 0]) // -1

How do I concatenate or merge arrays in Swift?

You can concatenate the arrays with +, building a new array

let c = a + b
print(c) // [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]

or append one array to the other with += (or append):

a += b

// Or:
a.append(contentsOf: b) // Swift 3
a.appendContentsOf(b) // Swift 2
a.extend(b) // Swift 1.2

print(a) // [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]

How to create array of unique object list in Swift

As of Swift 1.2 (Xcode 6.3 beta), Swift has a native set type.
From the release notes:

A new Set data structure is included which provides a generic
collection of unique elements, with full value semantics. It bridges
with NSSet, providing functionality analogous to Array and Dictionary.

Here are some simple usage examples:

// Create set from array literal:
var set = Set([1, 2, 3, 2, 1])

// Add single elements:
set.insert(4)
set.insert(3)

// Add multiple elements:
set.unionInPlace([ 4, 5, 6 ])
// Swift 3: set.formUnion([ 4, 5, 6 ])

// Remove single element:
set.remove(2)

// Remove multiple elements:
set.subtractInPlace([ 6, 7 ])
// Swift 3: set.subtract([ 6, 7 ])

print(set) // [5, 3, 1, 4]

// Test membership:
if set.contains(5) {
print("yes")
}

but there are far more methods available.

Update: Sets are now also documented in the "Collection Types" chapter of the Swift documentation.



Related Topics



Leave a reply



Submit