Why isn't this working? I can use array.contains() on a String but it doesn't work for an Object.
var array = ["A", "B", "C"]
array.contains("A") // True
class Dog {
var age = 1
}
var dogs = [Dog(), Dog(), Dog()]
var sparky = Dog()
dogs.contains(sparky) // Error Cannot convert value of type 'Dog' to expected argument type '#noescape (Dog) throws -> Bool
Your Dog needs to implement Equatable.
class Dog: Equatable {
var age = 1
}
func == (lhs: Dog, rhs: Dog) -> Bool {
return lhs.age == rhs.age
}
To really explain what's happening there, first we have to understand there are two contains methods on Array (or better said, on SequenceType).
func contains(_ element: Self.Generator.Element) -> Bool
with constraints
Generator.Element : Equatable
and
func contains(#noescape _ predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Bool
The first one basically searches for a given element in the array using ==. The second one uses a closure that returns a Bool to search for elements.
The first method cannot be used because Dog doesn't adopt Equatable. The compiler tries to use the second method but that one has a closure as the parameter, hence the error you are seeing.
Solution: implement Equatable for Dog.
If you are looking for object reference comparison, you can use a simple closure:
let result = dogs.contains({ $0 === sparky })
Swift
If you are not using object then you can user this code for contains.
let elements = [ 10, 20, 30, 40, 50]
if elements.contains(50) {
print("true")
}
If you are using NSObject Class in swift. This variables is according to my requirement. you can modify for your requirement.
var cliectScreenList = [ATModelLeadInfo]()
var cliectScreenSelectedObject: ATModelLeadInfo!
This is for a same data type.
{ $0.user_id == cliectScreenSelectedObject.user_id }
If you want to AnyObject type.
{ "\($0.user_id)" == "\(cliectScreenSelectedObject.user_id)" }
Full condition
if cliectScreenSelected.contains( { $0.user_id == cliectScreenSelectedObject.user_id } ) == false {
cliectScreenSelected.append(cliectScreenSelectedObject)
print("Object Added")
} else {
print("Object already exists")
}
This answer isn't relevant for the OP's question, but might be helpful to others who are confronted with the Swift error message
Cannot invoke 'contains' with an argument list of type '(whatever)'
But first a quick quiz: Can you spot the problem here?
internal class FrameworkAdminConnections {
private var _localConnectionKeys = [Int]()
... other code omitted
public func isLocalConnection(_ connectionKey : Int) {
return _localConnectionKeys.contains(connectionKey)
}
}
Swift kept telling me I couldn't invoke contains() with an argument list of type (Int), which was a very unhelpful error message, and I don't dare admit how long it took me to finally figure it out.
The real problem was that Swift's inference engine couldn't figure out what the result of the contains() method should be - because I'd stupidly not specified "-> Bool" on the isLocalConnection() method signature!
Related
We would like to use a dictionary as observable to notify any subscribers upon changes. So far this is implemented as BehaviorRelay<[KeyType: ValueObjectType]>
Now we need to add/remove/change values of the dictionary inside the observable. We tried the following (in simplified terms), but it did not work:
let list = BehaviorRelay<[String: MyClassType]>.init([:])
let newElem = MyClassType()
list.value.updateValue(newElem, forKey: "anykey")
The compiler complains: Cannot use mutating member on immutable value: 'value' is a get-only property
The following works, but I find it cumbersome and probably inefficient performance wise:
let list = BehaviorRelay<[String: MyClassType]>.init([:])
let newElem = MyClassType()
let newList = list.value
newList.updateValue(newElem, forKey: "anykey")
list.accept(newList)
Typical subscribers for the list on the UI side would be e.g. a UITableView or UICollectionView.
Is there a better way to handle this?
Your second way is is the way to go. It can be improved cumbersome wise by writing like this:
let list = BehaviorRelay<[String: MyClassType]>.init(value: [:])
list.accept(list.value.merging(["anykey": MyClassType()]){ (_, new) in new })
If this has to be done too many times, the following extension can come in handy
extension Dictionary where Key == String, Value == MyClassType {
static func + (lhs: [String : MyClassType], rhs: [String : MyClassType]) -> [String : MyClassType] {
return lhs.merging(rhs) { (_, new) in new }
}
}
Now you can just do this list.accept(list.value + ["anykey": MyClassType()])
Please note that if the right side operand has a key that is also present in the left side operand, the right side value will override the left one. According to my understanding this is your desired behaviour.
Also the first way that you were trying would work with Variable. But Variable is considered deprecated now in favour of BehaviorRelay and value property of BehaviourRelay is get only.
After discussing with #JR in the comments, a generic extension can be written for BehaviorRelay where the Element is an Array/ Dictionary
extension BehaviorRelay {
func addElement<T>(element: T) where Element == [T] {
accept(value + [element])
}
func removeElement<T>(element: T) where Element == [T], T: Equatable {
accept(value.filter {$0 != element})
}
func addElement<T, U>(key: T, value: U) where Element == [T: U] {
accept(self.value.merging([key: value]) { (_, new) in new })
}
func removeElemnent<T>(key: T) where Element == [T: Any] {
accept(self.value.filter {dictElemnt in dictElemnt.key != key})
}
}
I am taking some inspiration from
https://marcosantadev.com/swift-arrays-holding-elements-weak-references/
and I want to be able to maintain an array holding weak references to its elements, so that in case those elements get released elsewhere in my code base, I don't retain them in my array.
I would like the implementation to be as type safe as possible, however should be reusable.
The strategy that I am using is declaring a Weak Reference container as so.
class WeakRefContainer<T> where T: AnyObject {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
Then I want to maintain an array of these WeakRefContainers, so I create an array extension:
extension Array where Element: WeakRefContainer<AnyObject> {
func compact() -> [WeakRefContainer<AnyObject>] {
return filter { $0.value != nil }
}
}
When calling the compact method, I am now able to clear up the array in case stuff needs to be cleaned up.
I am now having some compilation issues which am having trouble understanding.
Lets suppose I have a sample class
class SampleClass {
}
And I try to use everything as follows:
var weakReferencesArray = [WeakRefContainer<SampleClass>]()
let obj1 = WeakRefContainer.init(value: SampleClass())
let obj2 = WeakRefContainer.init(value: SampleClass())
weakReferencesArray.append(obj1)
weakReferencesArray.append(obj2)
weakReferencesArray.compact()
When I try to call compact I get the following error message:
MyPlayground.playground:29:21: 'WeakRefContainer<SampleClass>' is not a subtype of 'WeakRefContainer<AnyObject>'
Can anyone unblock me please? Thanks
Your code doesn't work because WeakRefContainer<SampleClass> is not a subclass of WeakRefContainer<AnyObject> because generics are invariant in Swift. Thus weakReferencesArray can't use the compact method added from the extension.
There is a workaround for this, via a protocol:
protocol WeakHolder {
var hasRef: Bool { get }
}
extension WeakRefContainer: WeakHolder {
var hasRef: Bool { return value != nil }
}
extension Array where Element: WeakHolder {
func compacted() -> [Element] {
return filter { $0.hasRef }
}
mutating func compact() {
self = compacted()
}
}
I also renamed compact to compacted, for better Swift semantics, and replaced the original compact by a mutating version.
You probably want the extension to apply to all [WeakRefContainer<T>] where T can be any type extending AnyObject.
extension Array where Element: WeakRefContainer<T> {
However, currently, parameterised extensions are not possible. See this proposal.
You can kind of work around this by making compact generic:
extension Array{
func compact<T>() -> [Element] where Element == WeakRefContainer<T> {
return filter { $0.value != nil }
}
}
I'm trying to create an extension of Array that returns a new array of unique items based on the items with the closure applied.
For example: If I had an array of Apple where apple has properties name and origin, to get one Apple of each origin I would call apple.uniqued(on: { $0.origin })
Here's the code I have so far:
extension Array where Element: Equatable {
func uniqued(on extract: (Element) -> Equatable) { // A
let elementsAndValues = map { (item: $0, extracted: extract($0)) } // 1
var uniqueValues: [Element] = []
var uniqueExtracts: [Equatable] = [] // A
elementsAndValues.forEach { (item, extracted) in
if !uniqueExtracts.contains(extracted) { // 3, B
uniqueValues += [item]
uniqueExtracts += [extracted]
}
}
return uniqueValues
}
}
This should work as follows:
Create an array of tuples with both the original elements (item) and the elements with the closure applied (extracted)
If uniqueExtracts doesn't contain the item, add it and add the item to the uniqueItems array.
The errors I'm getting are:
A) "Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements" (twice)
B) "Missing argument label 'where:' in call"
I'm using the latest version of Xcode. Any advice would be a lot of help. Many thanks.
You have multiple problems that mix together to create the errors you’re seeing. What you should do is use a generic.
extension Array
{
func uniqued<T:Equatable>(on extract:(Array.Element) -> T) -> [Array.Element]
{
let elementsAndValues = self.map{ (item: $0, extracted: extract($0)) }
var uniqueValues:[Element] = []
var uniqueExtracts:[T] = []
for (item, extracted) in elementsAndValues
{
if !uniqueExtracts.contains(extracted)
{
uniqueValues.append(item)
uniqueExtracts.append(extracted)
}
}
return uniqueValues
}
}
The <T:Equatable> declares a generic type parameter T that conforms to Equatable. Then the function signature can expect a closure that returns some generic type T that we know conforms to Equatable from the type constraint in the angle brackets. You also have to change every occurrence of Equatable to the generic parameter T, since Equatable isn’t a real type; see my answer here. If you do that the code should compile.
You have a few other things you should probably change:
Instead of using elementsAndValues.forEach(:), use a for <pattern> in list {} loop.
Although this is contentious, you should probably use the Array().append(:) method instead of += concatenation when adding one element to an array. In the case of +=, as opposed to +, this is purely to convey intent.
You did not declare a return type for your function, so the compiler assumes it returns Void, and so the return uniqueValues statement will cause a compiler error. Add a -> [Array.Element] to the function to fix this.
where Element:Equatable as a constraint on Array itself is superfluous. You are using a key function to determine equability, so whether the elements themselves are equatable is irrelevant.
You may want to use a Set or some other hashed data structure instead of a uniqueExtracts array. Testing for membership in an array is an O(n) operation.
I would do this with a group(by:) function, which would group each element in the sequence by a given key (e.g. the origin), yielding a Dictionary mapping keys to groups (arrays of elements in the group). From there, I would just map over the dictionary and just get the first element in each group.
public extension Sequence {
public typealias Element = Iterator.Element
public typealias Group = [Element]
public func group<Key: Hashable>(by deriveKey: (Element) -> Key) -> [Key: Group] {
var groups = [Key: Group]()
for element in self {
let key = deriveKey(element)
if var existingArray = groups[key] { // Group already exists for this key
groups[key] = nil //performance optimisation to prevent CoW
existingArray.append(element)
groups[key] = existingArray
}
else {
groups[key] = [element] // Create new group
}
}
return groups
}
}
struct Apple {
let name: String
let origin: String
}
let apples = [
Apple(name: "Foo", origin: "Origin 1"),
Apple(name: "Bar", origin: "Origin 1"),
Apple(name: "Baz", origin: "Origin 2")
]
let firstAppleInEachOrigin = apples.group(by: {$0.origin}).flatMap{ _, group in group.first }
firstAppleInEachOrigin.forEach{ print($0) }
I was writing some code from the Apple forum. Everything seems right but I keep getting two errors. Someone please help. The code is below followed by the errors.
protocol Container{
associatedtype ItemType
mutating func append(item: ItemType)
var count:Int{get}
subscript(i:Int)->ItemType{get}
}
extension Array: Container {}
func checkToSeeIfItemsEqual<C1:Container, C2:Container>(container1:C1, container2:C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType:Equatable{
if container1.count != container2.count{
return false
}
for i in 0..<container1.count{
if container1[i] != container2[i]{
return false
}
}
return true
}
var damnArray = [1, 2, 4]
var damnArray2 = [1, 2, 4]
let theBool = checkToSeeIfItemsEqual(container1: damnArray, container2: damnArray2)
print(theBool)
Any is not equatable, so you can't call checkToSeeIfItemsEqual using Any arrays.
I'm not sure what exactly you're trying to do, but I think you might be misunderstanding how equatable works. The types have to be known and identical on both sides of the expression. e.g. Bool == Bool. If you have an Array, you have no way of knowing what the type of the array's Element is, and so cannot compare them.
If you want to compare two arrays of the same equatable type, you can do this:
func arraysAreEqual<ElementType: Equatable>(firstArray: [ElementType], secondArray: [ElementType]) -> Bool {
guard firstArray.count == secondArray.count else {
return false
}
for i in 0..<firstArray.count{
if firstArray[i] != secondArray[i]{
return false
}
}
return true
}
How I can figure out to get index of element which type is array?
For instance, I have property
let videosArrays = [[Video]]()
and some element of this array let videos = [Video]()
The video has some properties:
var thumbnailImageURL: String?
var title: String?
var numberOfViews: NSNumber?
var uploadDate: NSData?
I need to get index of videos in videosArrays
Xcode gives this syntax:
<predicate: (Self.Generator.Element) throws -> Bool(Self.Generator.Element) throws -> Bool>
How I can implement this?
let index = videosArrays.indexOf({ (<#Self.Generator.Element#>) -> Bool in
<#code#>
})
I can't figure out how to use this to find index.
To be able to use indexOf, contains and some other array methods easily, the objects have to conform to Equatable protocol.
Thus, you should probably do the following:
extension Video: Equatable { }
func ==(lhs: Video, rhs: Video) -> Bool {
return lhs.thumbnailImageURL == rhs.thumbnailImageURL &&
lhs.title == rhs.title &&
lhs.numberOfViews == rhs.numberOfViews &&
lhs.uploadDate == rhs.uploadDate
}