I have two different classes / struct and I want to be able to use:
let index = [T].indexOf(U)
Some sample data for playground:
struct Foo: Hashable {
let name: String
var hashValue: Int { return name.hashValue }
}
struct Bar: Hashable {
let name: String
var hashValue: Int { return name.hashValue }
}
func ==(lhs: Foo, rhs: Foo) -> Bool { return lhs.name == rhs.name }
func ==(lhs: Bar, rhs: Bar) -> Bool { return lhs.name == rhs.name }
func ==(lhs: Bar, rhs: Foo) -> Bool { return lhs.name == rhs.name }
Attempts:
let test1 = Foo(name: "John")
let test2 = Foo(name: "Amy")
let test3 = Bar(name: "Mary")
let test4 = Bar(name: "John")
let arrays = [test1, test2]
let result = arrays.indexOf(test3) // cannot convert value of type 'Bar' to expected argument type 'Foo'
Should I just use a protocol instead?
Using protocols:
protocol Names {
var name: String { get set }
}
extension Equatable where Self: Names { }
struct Foo: Names { var name: String }
struct Bar: Names { var name: String }
func ==<T: Names>(lhs: T, rhs: T) -> Bool {
return lhs.name == rhs.name
}
Attempts:
let arrays: [Names] = [test1, test2]
let result = arrays.indexOf(test2) // cannot convert value of type 'Foo' to expected argument type '#noescape (Names) throws -> Bool'
And:
protocol Names: Equatable {
var name: String { get set }
}
let arrays: [Foo] = [test1, test2]
let result = arrays.indexOf(test3) // cannot convert value of type 'Bar' to expected argument type 'Foo'
The issue is that the definition of the operator overload for Equatable is:
func ==(_ lhs: Self, _ rhs: Self) -> Bool
Therefore it is contained to the type of object it is operating upon - in your case Foo or Bar, but not a mix of both. The following declaration will never be used by Equatable:
func ==(lhs: Bar, rhs: Foo) -> Bool { return lhs.name == rhs.name }
Can you change your structs to classes that subclass a common ancestor? Along the lines of:
class FooBar: Hashable {
var name: String!
var hashValue: Int { return name.hashValue }
init() { }
}
class Foo: FooBar {
}
class Bar: FooBar {
}
func ==(lhs: FooBar, rhs: FooBar) -> Bool { return lhs.name == rhs.name }
var test1 = Foo(); test1.name = "John"
var test2 = Foo(); test2.name = "Amy"
var test3 = Bar(); test3.name = "Mary"
var test4 = Bar(); test4.name = "John"
let arrays: [FooBar] = [test1, test2]
let result = arrays.indexOf(test4)
print(result)
Related
In this code snippet
protocol MyProtocol {}
extension Int: MyProtocol {}
let a: Array<MyProtocol> = Array<Int>()
let b: ArraySlice<MyProtocol> = a[...]
let c: Array<Int> = a as! Array<Int>
let d: ArraySlice<Int> = b as! ArraySlice<Int>
d warns with Cast from 'ArraySlice<MyProtocol>' to unrelated type 'ArraySlice<Int>' always fails.
Why can't a Slice be cast in the same way as the original Array? Can this snippet be modified to give the Array casting behaviour to the Slice?
This is basically due to how generic variance in Swift works.
Only few types are variant in Swift, including Array<T> and Set<T>. Most other types, and the types you define, are invariant.
Invariance means that T<A> and T<B> are unrelated types even if A and B are related.
Array<T> and Set<T> are covariant, which means that a Array<A> can be assigned to a variable of type Array<B> if A is a subtype of B. You can force it to go the other way (like you did in the third line) by using as!.
ArraySlice<T>, like many other types, is simply invariant. You need to do this to convert:
let d: ArraySlice<Int> = ArraySlice(b.map { $0 as! Int })
As an addendum answer to the correct answer by #Sweeper for people who are looking for type-flexible performant copy-by-ref arrays, I ended up rolling a solution which wraps an array in a class and exposes some of the API for an array.
Not a great solution, but it does what I need it to. Boo Apple for not keeping their APIs for this sort of thing consistent.
class ArrayReference<T>: Collection {
private(set) var array : Array<T>
init(_ encapsulating: Array<T>? = nil) {
self.array = encapsulating ?? []
}
var startIndex: Int {
get {
return array.startIndex
}
}
var endIndex: Int {
get {
return array.endIndex
}
}
var count : Int {
get {
return array.count
}
}
func index(after i: Int) -> Int {
return array.index(after: i)
}
subscript (index: Int) -> T {
get { return array[index] }
set(newValue) { array[index] = newValue }
}
func append(_ newValue: T) {
array.append(newValue)
}
func removeAll() {
array.removeAll()
}
var first: T? {
if array.count > 0 {
return array[0]
} else {
return nil
}
}
var last: T? {
if array.count > 0 {
return array[array.count - 1]
} else {
return nil
}
}
func asType<C>(_ type: C.Type) -> ArrayReference<C>? {
if let array = self.array as? Array<C> {
return ArrayReference<C>(array)
} else {
return nil
}
}
}
extension ArrayReference: Equatable where T: Equatable {
static func == (lhs: ArrayReference<T>, rhs: ArrayReference<T>) -> Bool {
if lhs.count == rhs.count {
var equal = true
for (lhs, rhs) in zip(lhs, rhs) {
equal = equal && (lhs == rhs)
}
return equal
} else {
return false
}
}
}
I have an array of objects like this, but with 16 properties:
class anObject: NSObject {
#objc var number: Int
#objc var name: String
#objc var price: Double
subscript(key: String) -> Any? {
return self.value(forKey: key)
}
}
I am able to sort my array on any property very easily by, for instance:
sortedArray = unsortedArray.sorted(by: { $0.name < $1.name } )
Now I am grouping my array so that I can populate a UITableView with sections and rows. I group it like this:
var groupedArray = Dictionary<String, Array<myObject>>()
for item in myArray {
// Verify each grouping is initialized only once
if groupedArray[item[byProperty] as! String] == nil {
groupedArray[item[byProperty] as! String] = Array<Items>()
}
// Add the item into the correct subarray
groupedArray[item[byProperty] as! String]?.append(item)
}
I can then sort the grouped array by doing this:
return groupedArray.sorted { $0.0 < $1.0 }
And this works great, except that two of my properties are Doubles. When I sort on those two properties, Swift sorts the groups alphabetically:
10.5, 11.5, 12, 1.5, 2.0 . . .
rather than numerically
1.5, 2.0, 10.5, 11.5, 12 . . .
I have managed to pad the Doubles by checking to see if they are too short and inserting a 0 at the front of the String. This works in that they are now sorted in correct order, but eventually I am going to have to strip that leading 0 off the front, and it seems like an ugly solution.
How do I properly sort the grouped array given that the Doubles have to be used as Strings?
When you start casting strings all over the place, you likely need to start changing the design of things. Why not make the dictionary keys some object you've designed instead of a string? This is a sample of what I mean:
struct DoubleKey {
let value: Double
}
extension DoubleKey: Hashable {
var hashValue: Int {
return value.hashValue
}
static func ==(lhs: DoubleKey, rhs: DoubleKey) -> Bool {
return lhs.value == rhs.value
}
}
extension DoubleKey: Comparable {
static func <(lhs: DoubleKey, rhs: DoubleKey) -> Bool {
return lhs.value < rhs.value
}
}
let a = DoubleKey(value: 10.0)
let b = DoubleKey(value: 20.0)
let c = DoubleKey(value: -10.0)
let dictionary: [DoubleKey: String] = [a: "10", b: "20", c: "-10"]
let sortedDictionary = dictionary.sorted { $0.0 < $1.0 }
So instead of [String: Array<myobject>] you have: [DoubleKey: Array<MyObject> or [IntegerKey: Array<MyObject>] or even [StringKey: Array<MyObject>
You could implement many variations of your own specialized key, and write some protocols if you need additional functionality. If you need to store a string in your key, then add a property, or better yet, conform it to a protocol that defines the behavior of what you need and implement it.
Additional Key
struct StringKey {
let value: String
}
extension StringKey: Hashable {
var hashValue: Int {
return value.hashValue
}
static func ==(lhs: StringKey, rhs: StringKey) -> Bool {
return lhs.value == rhs.value
}
}
extension StringKey: Comparable {
static func <(lhs: StringKey, rhs: StringKey) -> Bool {
return lhs.value < rhs.value
}
}
let a = StringKey(value: "a")
let b = StringKey(value: "c")
let c = StringKey(value: "b")
let dictionary: [StringKey: String] = [a: "10", b: "20", c: "-10"]
let sortedDictionary = dictionary.sorted { $0.0 < $1.0 }
Now what?
//EXAMPLE
protocol ViewableString {
func view() -> String
}
extension StringKey: ViewableString {
func view() -> String {
return value
}
}
extension DoubleKey: ViewableString {
func view() -> String {
return String(value)
}
}
let array: [ViewableString] = [a, b, c]
array[0].view()
Program to the protocols!
Hopefully this helps!
Ok. Completely different answer. Again, you are trying to fit many objects of different types in the same container. This feels like a bad idea to me. Maybe you have to. But here is one way using enums:
enum SpecialKey {
case integer(Int)
case double(Double)
case string(String)
func asString() -> String {
switch self {
case let .integer(a):
return String(a)
case let .double(a):
return String(a)
case let .string(a):
return a
}
}
}
extension SpecialKey: Comparable {
static func <(lhs: SpecialKey, rhs: SpecialKey) -> Bool {
switch (lhs, rhs) {
case let (.double(a), .double(b)):
return a < b
case let (.integer(a), .integer(b)):
return a < b
case let (.string(a), .string(b)):
return a < b
default:
return false //Add more cases with different comparisons!
}
}
static func ==(lhs: SpecialKey, rhs: SpecialKey) -> Bool {
switch (lhs, rhs) {
case (.integer(_), .integer(_)),
(.double(_), .double(_)),
(.string(_), .string(_)):
return true
default:
return false
}
}
}
extension SpecialKey: Hashable {
var hashValue: Int {
switch self {
case let .integer(a):
return a.hashValue
case let .double(a):
return a.hashValue
case let .string(a):
return a.hashValue
}
}
}
let a = SpecialKey.integer(10)
let b = SpecialKey.string("something")
let c = SpecialKey.double(10.5)
let dictionary: [SpecialKey: String] = [a: "a", b: "b", c: "c"]
This is probably more like what you're looking for.
I have my two objects (Obj1 & Obj2) defined as below:
class Obj1: NSObject {
var code : String
init(code: String) {
self.code = code
}
}
class Obj2: NSObject {
var codeObj : Obj1
var value : Double
init(primary: Currency, value: Double) {
self.primary = primary
self.value = value
}
}
I have an array of Obj2 and I'm trying to update the array [Obj2] in such a way that the array only contains Obj2 whose codeObj.code are equal. Will including the Equatable protocol help in this?
I have tried this:
let filteredArray = array1.filter( { (c1: Obj2) -> Bool in
return conversion2.contains(where: { (c2: Obj2) -> Bool in
return c1.codeObj.code == c2.codeObj.code;
})
}) + array2.filter( { (c2: Obj2) -> Bool in
return conversion1.contains(where: { (c1: Obj2) -> Bool in
return c1.codeObj.code == c2.codeObj.code;
})
})
IS there a way to simplify this?
The only way for me is adding equatable to the objects like this:
class Obj1: NSObject {
var code : String
init(code: String) {
self.code = code
}
static func ==(lhs: Obj1, rhs: Obj1) -> Bool {
return lhs.code == rhs.code
}
}
class Obj2: NSObject {
var codeObj : Obj1
var value : Double
init(obj: Obj1, value: Double) {
self.codeObj = obj
self.value = value
}
static func ==(lhs: Obj2, rhs: Obj2) -> Bool {
return lhs.codeObj == rhs.codeObj
}
}
And to filter for equals, use for example:
// Test objects
let obj1A = Obj1(code: "aaa")
let obj1B = Obj1(code: "aba")
let obj1C = Obj1(code: "aaa")
let obj1D = Obj1(code: "cca")
let obj1E = Obj1(code: "aba")
let obj1F = Obj1(code: "xca")
let obj2A = Obj2(obj: obj1A, value: 12.0)
let obj2B = Obj2(obj: obj1B, value: 12.0)
let obj2C = Obj2(obj: obj1C, value: 23.0)
let obj2D = Obj2(obj: obj1D, value: 46.0)
let obj2E = Obj2(obj: obj1E, value: 23.0)
let obj2F = Obj2(obj: obj1F, value: 4.0)
var array = [obj2A, obj2B, obj2C, obj2D, obj2E, obj2F]
var onlyEqual = [Obj2]()
for object in array {
let count = array.filter({ $0 == object }).count
if count > 1 {
onlyEqual.append(object)
}
}
Where onlyEqual contains:
aaa
aba
aaa
aba
I'm trying to create an extension but somehow it keeps saying:
Ambiguous reference to member '=='.
class Foo: Equatable {
var string = ""
var number = 0
init(string: String, number: Int) {
self.string = string
self.number = number
}
}
extension Array where Iterator.Element: Foo {
mutating func replace(object: Foo) {
if let index = index(where: { $0.number == object.number}) {
self[index] = object
}
}
}
func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.number == rhs.number
}
What am I doing wrong?
Try this:
extension Array where Element: Foo {
mutating func replace(object: Element) {
if let index = index(where: {$0.number == object.number}) {
self[index] = object
}
}
}
To make self[index] = object valid, object needs to be an Element of the Array, which can be any subclass of Foo.
And unfortunately, Swift cannot infer the type of Element from the constraint to Iterator.Element. You may need to declare the constraint to Element directly.
(And to test the code above, I have removed : Equatable from your Foo, which is irrelevant when you use index(where:) in the extension.)
Swift 5 version and support multiple object replacement(add if not exist).
extension Foo {
public static func == (lhs: Object, rhs: Object) -> Bool {
return lhs.id == rhs.id
}
}
extension Array where Element: Foo {
mutating func replace(object: Element) -> Bool {
if let index = firstIndex(where: {$0 == object}) {
self[index] = object
return true
}
return false
}
mutating func replace(objects: Array<Element>) {
objects.forEach { (object) in
if replace(object: object) == false {
self.append(object)
}
}
}
}
I want to sort this kind of Dictionary:
var data = [NSDate: [String: AnyObject]]()
e.g.
data: [2015-08-29 12:08:29 +0000: ["foo": "bar"]],
[2015-08-29 12:05:13 +0000: ["foo": "bar"]],
[2015-08-29 12:11:02 +0000: ["foo": "bar"]]
should then be:
dataSorted: [2015-08-29 12:05:13 +0000: ["foo": "bar"]],
[2015-08-29 12:08:29 +0000: ["foo": "bar"]],
[2015-08-29 12:11:02 +0000: ["foo": "bar"]]
I tried:
let dataSorted = Array(data.keys).sorted(<)
// Could not find an overload for '<' that accepts the supplied arguments
and
let dataSorted = sorted(data) { $0.0 < $1.0 }
// Could not find member '0'
You cannot sort dictionaries as they do not have order, so I use array in my answer instead.
By default NSDate does not conform to Comparable protocol. You have to use compare method:
let results = Array(data).sort({ $0.0.compare($1.0) == .OrderedAscending })
This extension can be used to make NSDate conform to Comparable:
extension NSDate: Comparable {}
public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedSame
}
public func >(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedDescending
}
public func <(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == NSComparisonResult.OrderedAscending
}
public func <=(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs < rhs || lhs == rhs
}
public func >=(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs > rhs || lhs == rhs
}
Then you can simply use:
let results = Array(data).sort({ $0.0 < $1.0})
An (at least for me) easier to read solution without $0 stuff would be:
let kartoffel = data.keys.sort { (date, date2) in
return date.compare(date2) == NSComparisonResult.OrderedAscending
}