Array extension, Ambiguous reference to member 'subscript' - arrays

I'm trying to write an extension to Array in Xcode's playground. I want to write a function that will modify the array so that it will be filled with 0s when the function is called. The code I'm attempting to use is this:
import Foundation
extension Array {
mutating func zero() {
for i in 0..<self.count {
self[i] = 0 // ERROR - Ambiguous reference to member 'subscript'
}
}
}
This code does not run, because of the error at:
self[i] = 0
However, if I try to 'get' a value from self, I have no issues. For example:
import Foundation
extension Array {
mutating func zero() {
for i in 0..<self.count {
print(self[i])
}
}
}
has no errors and runs as expected.
So my question is; Why can't I modify the array?
Also, Replacing:
self[i] = 0
with,
self.append(0)
also results in an error. (Cannot invoke 'append' with an argument list of type '(Int)')
So it won't let me modify self at all it seems.

It will work if you do the following:
extension Array where Element: IntegerLiteralConvertible {
mutating func zero() {
for i in 0..<self.count {
self[i] = 0
}
}
}
You must constrain the type of the elements allowed, because you can't, for example, zero out an array of Strings.
It's important to remember, when extending something like an Array, that you take into account what types of elements are valid for the method you're adding. If it requires elements of a certain type, you constrain on it and then it will work. That way you can only use your zero() method on Arrays containing ints. You might define a different version for an array of Strings that replaces everything in the array with the string "zero", for example, and that implementation will only be used on the type that you constrain it to as well.

Related

How to constrain the Element type for an Array when it's the Element of another Array

I have an array, within an array situation, and I want to constrain the internal Array's element type.
// this doesn't work but should illustrate what I want. Is there
// any way to constrain the Element type for an Array when it's
// already declared as the type for an Array
extension Array where Element == Array<Element2: SomeProtocol> {
}
Ultimately, I think that solving this problem will fix the following compile error
protocol Thingy {
associatedtype MeasurementType: Measurement
var topMeasurements: [[MeasurementType]] { get }
}
extension Thingy {
func doStuff() {
// COMPILE ERROR'[[Self.MeasurementType]]' is not convertible to 'Array<Array<Measurement>>'
let compileError = self.topMeasurements.measurements(forUUIDs: ["A"])
// Hack workaround
// The fact that this works leads me to believe that the constraint in the
// extension is inadequate. The extension wants [[Measurement]] and
// event though MeasurementType is a kind of Measurement the compiler won't
// allow it. The hope is that if we could write something like
// [[Element]] where Element: Measurement
let measurements: [[Measurement]] = self.topMeasurements
let thisWorks = measurements.doSomething(ids: ["A"])
print(thisWorks)
}
}
// I'm hoping that if I constrain the following that it'll fix my issue
// constrain this type ----------------------
// |
// \/
extension Array where Element == Array<Measurement> {
func doSomething(ids: [String]) -> [Measurement] {
// do something return some elements,
// I've removed any code that could cause confusion
return []
}
}
This is much easier to implement as an extension on all Collections rather than just on Arrays. I believe the extension you want is this:
extension Collection where Element: Collection, Element.Element: Measurement {
func measurements(forUUIDs: [String]) -> [Measurement] {
return []
}
}
The basic problem is that Swift lacks higher-kinded types, so you can't extend Array (because that's not a proper type). But you can extend Collection (because that's a PAT).

Extend swift Array -- mutability issue

I would like to create a Swift 4 extension on a Swift Array. The function should sort the array in-place.
The compiler complains seems to assume that arrays are immutable, as it complains on the function I created. I'd like to solve this, but do not know how. Please do note that the requirement is to sort the array in-place (with sort) and not creating a new array (like one would do with sorted).
struct MyStruct {
var field: String
}
typealias MyStructList = [MyStruct]
// requirement: extend MyStructList with custom sort
extension Array where Element == MyStruct {
func customSort() -> [MyStruct] {
return sort { $0.field < $1.field }
}
}
Compiler complains: Cannot use mutating member on immutable value: 'self' is immutable
You want to be calling sorted(by:), which returns a new, sorted Array instance rather than sort(by:), which does sorting in place and hence mutates the Array.
extension Array where Element == MyStruct {
func customSort() -> [MyStruct] {
return sorted(by: {$0.field < $1.field})
}
}
If you actually want to sort the Array in place, you have to mark your customSort function as mutating and change the function signature to return Void, since there's no new Array created.
extension Array where Element == MyStruct {
mutating func customSort() {
sort(by: {$0.field < $1.field})
}
}
Two issues:
You have to mark the function as mutating
If you want to sort in place there is no return value
extension Array where Element == MyStruct {
mutating func customSort() {
sort { $0.field < $1.field }
}
}
And please name methods / functions camelCased.
sort is a mutable function that sorts an Array in-place. sorted(by:) is the function you are looking for. Rename sort to sorted.
If you are looking to sort the Array in-place, then rewrite your function declaration to include the mutating qualifier.
So the following:
func custom_sort()
becomes:
mutating func custom_sort()
The mutating function sort(by:) does not return anything, therefore your return is erroneous. Remove -> [MyStruct] as well.

Extend Array constrained when Element is a tuple

Is there a way to extend an Array when the element is a type of tuple ?
public extension Array where Element: (timeZoneName: String, formattedName: String){
}
This declaration returns 4 errors:
Statement cannot begin with a closure expression
Braced block statements is an unused closure
Expected '{' in extension
Expected identifier for type name
I can't tell if the errors shown are accurate.
Any ideas?
Swift 3 version of appzYourLife's answer:
extension Sequence where Iterator.Element == Tuple2 {
func foo() {}
}
It's now possible in Swift 3.1.
extension Array where Element == (String, String) {
...
}
Since (AFAIK) the Tuple type does not conform to a Protocol (and does not even have a name) it's very hard to do what you need.
This is the closest I could get (maybe others can provide more elegant solutions).
Typealias
First of all lets define a couple of typealiases
typealias Tuple2 = (Any, Any)
typealias Tuple3 = (Any, Any, Any)
Yes, some readers now understand where I am going and probably don't like it...
I don't like it neither
SequenceType
Now let's extend the protocol SequenceType adding the foo method when the Element of the sequence is Tuple2...
extension SequenceType where Generator.Element == Tuple2 {
func foo() {}
}
or Tuple3
extension SequenceType where Generator.Element == Tuple3 {
func foo() {}
}
Array
Next lets define and populate an array of Tuple2
let list: [Tuple2] = [(1,2)]
Now the extension is applied and we can write
list.foo()
Disclaimer :D
This does work only if the array is explicitly declared as [Tuple2] (or [Tuple3]).
Something like this does not work
let list = [(1,2)]
list.foo() // compile error
You can't add specific typing like extension Array where Element == Int because this would transform the generic Array into a non-generic version.
You will see an error something like same-type requirement makes generic parameter 'Element' non-generic
Edit
It does actually seem legit (at least in Swift 2.2) to do:
typealias tzTuple = (timeZoneName: String, formattedName: String)
extension Array where Element: tzTuple {
}
You will have to see if this works in runtime though.
I was checking this in a Playground and at present, Playgrounds are not yet fully functional with Swift 2.2-dev
I would suggest this instead:
typealias tzTuple = (timeZoneName: String, formattedName: String)
extension Array {
func formattedName(index: Int) -> String? {
if self[index] is tzTuple {
return (self[index] as! tzTuple).formattedName
}
return nil
}
}
will allow you to do
let foo = [(timezoneName: "PST", formattedName: "Pacific Standard Time"),(timezoneName: "AEST", formattedName: "Australian Eastern Time")]
print(foo.formattedName(0))

Getting the indexes of NSButtons in an array and appending them to an array of integers in swift (Cocoa)

I'm super new to programming so this may be a pretty basic question.
I have an array of NSButtons (checkboxes) in my ViewController that I am calling "buttonArray." I want to go through the array, find out which buttons are checked and add the indexes of those checked buttons to another array of integers (located in a struct), which I am calling "intArray." This is the struct:
struct GetInterval {
var intArray = [Int]()
mutating func putIntIntoArray (intToAdd:Int) {
self.intArray.append(intToAdd)
}
}
I have tried doing this two ways (shown below), both of which have given me compiler errors.
First, I tried to use a function to import the index as an Int and add it to the "intArray" array...
for interval in buttonArray {
if interval.state == NSOnState {
var intervalIndex = find(buttonArray, interval)
GetInterval.putIntIntoArray(Int(intervalIndex!))
}
}
...which gave me the error: "Cannot invoke 'putIntoArray' with argument list of type ((int))"
When that didn't work, I tried appending it directly from the "if" statement in the ViewController...
for interval in buttonArray {
if interval.state == NSOnState {
var intervalIndex = find(buttonArray, interval)
GetInterval.intArray.append(intervalIndex!)
}
}
...which gives me the error: "GetInterval.Type does not have a member named 'intArray'"
How can I fix this?
When you say GetInterval.(something), this refers to a static member (a.k.a., something owned by the GetInterval type). However, your intArray is a member owned by each instance of the GetInterval type. So you need to create an instance first. For example:
var getInt = GetInterval() // create a new instance
getInt.putIntIntoArray(...) // modify this instance, by calling a mutating function
getInt.intArray.append(...) // modify this instance, by directly accessing a property

Pass Array containing either class or struct

In Swift I declared a function that differs from Array.count only in that if array == nil the function returns 0. This is related to my UITableViewDataSource, but that's not important here. The problem is, if I declare the function as:
class func countOfItemsInArray(array: [AnyObject]?) -> Int
and then try to pass it an array of structs, it declares that the structs in the array do not conform to AnyObject. I understand why that is (I think), but is there a way to make this work with classes and structs, or should I just give in to copy and paste?
Generics are probably better suited to this problem than relying on covariance of [AnyObject]. A version of countElements that worked on an optional array and returned 0 in case of nil could go like this:
func countElements<T>(array: [T]?) -> Int {
return array?.count ?? 0
}
When you call countElements with any kind of array, the placeholder T is replaced with the type of the element contained in the array.
Note, this version overloads the existing countElements with a version that takes an optional. If you call it with a non-optional or any other kind of collection, the Swift version would be called, if you pass in an optional array, this one will be called. It’s debatable whether this is a good practice (I think it’s fine) or a bad one (some may disapprove :).
A version that works on any collection type would be:
func countElements<C: CollectionType>(col: C?) -> C.Index.Distance {
return col.map { countElements($0) } ?? 0
}
If you use Any instead of AnyObject you can pass any type, so also structs:
class func countOfItemsInArray(array: [Any]?) -> Int
This is kind of weird.
I used this function:
func countOfItemsInArray(array: [Any]?) -> Int {
return array != nil ? array!.count : 0
}
Declared two of your Assignment structs and put them in an array:
let structOne = Assignment(name: "1", dueDate: NSDate(), subject: "1")
let structTwo = Assignment(name: "2", dueDate: NSDate(), subject: "2")
let myArray: [Assignment] = [structOne, structTwo]
But here's the interesting part.
When calling println(countOfItemsInArray(myArray)) it gives the error:
<stdin>:27:33: error: 'Assignment' is not identical to 'Any'
println(countOfItemsInArray(myArray))
^
<stdin>:17:26: note: in initialization of parameter 'array'
func countOfItemsInArray(array: [Any]?) -> Int {
^
So I tested if myArray is of type [Any]:
println(myArray is [Any])
to which swift says:
<stdin>:25:17: error: 'Any' is not a subtype of 'Assignment'
println(myArray is [Any])
^
But when I change the type annotation of myArray to [Any] it works:
let myArray: [Any] = [structOne, structTwo]
And when simply handing the literal to the function it works, too:
countOfItemsInArray([structOne, structTwo])
The whole code example can be seen here.

Resources