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
Related
So my idea, is to once is retrieved from the API, that they are shown alphabatecally by the value 'name' of the Charact object which are all them stored in to pokemons array.
I would like to know how i would be able to sort my list of Pokemons so they appear in alphabetical order
Here is my Object code:
class Charact: Codable {
var id: Int?
var isDefault: Bool?
var isMega : Bool?
var isBattleOnly : Bool?
var name: String?
var sprites: Sprite?
private enum CodingKeys: String, CodingKey {
case isDefault = "is_default"
case isMega = "is_mega"
case isBattleOnly = "is_battle_only"
case id
case name
case sprites
}
}
Here is the code : Code in paste-bin
Assuming that you want to stick to your code, try this out:
func downloadPokemonsInfo(){
for i in 1...MAX_POKEMONS{
connection.getCharact(withId: i) { charact in
if let charact = charact, let id = charact.id{
self.pokemons[id-1] = charact
if let imageURL = charact.sprites?.frontDefault{
self.connection.getSprite(withURLString: imageURL) { image in
self.imagesDownload = self.imagesDownload + 1
if let image = image {
self.images[id - 1] = image
}
if self.imagesDownload == self.MAX_POKEMONS{
let zipped = zip(self.pokemons, self.images)
let zippedSorted = zipped.sorted { one, two in
guard let c1 = one.0, let c2 = two.0, let n1 = c1.name, let n2 = c2.name else { return false }
return n1 < n2
}
self.pokemons = zippedSorted.map { $0.0 }
self.images = zippedSorted.map { $0.1 }
DispatchQueue.main.async {
self.tableView.reloadData()
self.tableView.alpha = 1
}
}
}
}
}
}
}
}
I have following code in Playgraound which doesn't work as I wish.
class Obj: NSObject {
var va1: String? = nil
var va2: Int? = nil
init(_ v1: String,_ v2: Int){
va1 = v1
va2 = v2
}
static func ==(lhs: Obj, rhs: Obj) -> Bool {
guard lhs.va1 == rhs.va1 else { return false }
guard lhs.va2 == rhs.va2 else { return false }
return true
}
}
var objArray1: [Obj] = [Obj("a", 1), Obj("b", 2), Obj("c", 3)]
var objArray2: [Obj] = [Obj("a", 1), Obj("b", 2), Obj("d", 4)]
objArray1 += objArray2
objArray1 = Array(Set(objArray1))
But the objArray1 will contains all duplicated items.
I have check other questions like :
Removing Duplicates From Array of Custom Objects Swift .
Remove duplicate objects in an array .
However, in my case, I can't change Obj's superclass NSObject. The custom type must be a subclass of NSObject.
Question: Is there any elegant way to achieve merge two custom type array and remove duplicate custom type items
var tempArray: [Obj] = []
let removalArray = objArray1.filter { (someObj) -> Bool in
if tempArray.isEmpty {
tempArray.append(someObj)
return true
}
if !tempArray.contains(where: { (anotherObj) -> Bool in
return anotherObj.va2 == someObj.va2 && anotherObj.va1 == someObj.va1
}) {
tempArray.append(someObj)
return true
}
return false
}
I put this at the bottom before the set stuff.
I have resolved this issue based on #Hamish 's comment and the link .
class Obj: NSObject {
var va1: String? = nil
var va2: Int? = nil
var value : Int
init(_ v1: String,_ v2: Int){
va1 = v1
va2 = v2
super.init()
}
override var hashValue : Int {
var hashValueString = va1 ?? ""
hashValueString += "\(va2 ?? 0)"
return hashValueString.hashValue
}
override func isEqual(_ object: Any?) -> Bool {
if let other = object as? Obj {
return self.hashValue == other.hashValue
} else {
return false
}
}
}
var objArray1: [Obj] = [Obj("a", 1), Obj("b", 2), Obj("c", 3)]
var objArray2: [Obj] = [Obj("a", 1), Obj("b", 2), Obj("d", 4)]
objArray1 += objArray2
var objSet = Array(Set(objArray1))
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 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)
Within my struct I have the following:
subscript(index: Int) -> FileSystemObject {
var i: Int = 0
for a in contents! {
if (i == index) {
return a
}
i++
}
}
Where,
var contents: FileSystemObject = [FileSystemObject]?
But when,
let it: FileSystemObject = FileSystemObject()
And I write:
return it.contents![index]
I receive the error
Cannot subscript a value of type [FileSystemObject]
What am I doing wrong here?
Additionally, note that:
Changing each of the objects with the value
FileSystemObject
To,
[FileSystemObject]
Does not help.
EDIT 1:
This is the entirety of the code:
MainWindowController.swift
class MainWindowController: NSWindowController, NSOutlineViewDataSource, NSOutlineViewDelegate {
#IBOutlet weak var sourceView: NSOutlineView!
static var fileManager: NSFileManager = NSFileManager.defaultManager()
static var fileSystem: FileSystemObject = FileSystemObject(path: "/", fs: fileManager)
var outlineSource: OutlinePrep = OutlinePrep(fs: fileSystem)
override func windowDidLoad() {
super.windowDidLoad()
sourceView.setDataSource(self)
sourceView.setDelegate(self)
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
guard let it = item as? OutlinePrep else {
return outlineSource.basePath
}
return it.data[index]
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
// return (item == nil) ? YES : ([item numberOfChildren] != -1);
print(item)
guard let it = item as? OutlinePrep else {
return false
}
for (var i: Int = 0; i < it.data.count; i++) {
guard let _ = it.data[i].contents else {
return false
}
}
return true
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
guard let it = item as? OutlinePrep else {
return outlineSource.data.count
}
var i: Int = 0
for a in it.data {
guard let _ = a.contents else {
continue
}
i++
}
return i
}
}
FileSystem.swift
struct FileSystemObject {
let basePath: String
let name: String
var isDir = ObjCBool(false)
var contents: [FileSystemObject]?
init(path: String, fs: NSFileManager) {
basePath = path
let root: [String]
fs.fileExistsAtPath(path, isDirectory: &isDir)
if (isDir.boolValue) {
do {
root = try fs.contentsOfDirectoryAtPath(path)
}
catch {
root = ["Error"]
}
contents = []
for r in root {
contents!.append(FileSystemObject(path: (path + (r as String) + "/"), fs: fs))
}
}
name = path
}
subscript(index: Int) -> FileSystemObject {
get {
let error: FileSystemObject = FileSystemObject(path: "", fs: NSFileManager.defaultManager())
guard let _ = contents else {
return error
}
var i: Int = 0
for a in contents! {
if (i == index) {
return a
}
i++
}
return error
}
set {
}
}
}
Outline.swift
struct OutlinePrep {
var data: [FileSystemObject]
let basePath: String
private var cell: Int = -1
init (fs: FileSystemObject) {
data = fs.contents!
basePath = fs.basePath
}
mutating func outlineDelegate() -> String {
cell++
return data[cell].name
}
func testFunc(data: [FileSystemObject]) {
for (var i: Int = 0; i < data.count; i++) {
guard let d = data[i].contents else {
print(data[i].name)
continue
}
testFunc(d)
}
}
}
EDIT 2:
To clarify, I am inquiring as to how I might resolve the error, as all other provided code works as intended.
The error message is misleading. The problem becomes more apparent
if you split
return it.data[index]
into two separate statements:
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
guard let it = item as? OutlinePrep else {
return outlineSource.basePath
}
let fso = it.data[index]
return fso // error: return expression of type 'FileSystemObject' does not conform to 'AnyObject'
}
The value of it.data[index] is a
FileSystemObject, which is a struct and therefore it
does not conform to AnyObject and cannot be the return value
of that method. If you want to return a FileSystemObject
then you have to define that as a class instead.
simplified ...
struct FileSystemObject {
var context: [FileSystemObject]?
init() {
context = []
// your init is called recursively, forever ...
let fso = FileSystemObject()
context?.append(fso)
}
}
let s = FileSystemObject()