I am trying to reduce an array of objects to a set in Swift and this is my code:
objects.reduce(Set<String>()) { $0.insert($1.URL) }
However, I get an error:
Type of expression is ambiguous without more context.
I do not understand what the problem is, since the type of URL is definitely String. Any ideas?
You don't have to reduce an array to get it into a set; just create the set with an array: let objectSet = Set(objects.map { $0.URL }).
With Swift 5.1, you can use one of the three following examples in order to solve your problem.
#1. Using Array's map(_:) method and Set's init(_:) initializer
In the simplest case, you can map you initial array to an array of urls (String) then create a set from that array. The Playground below code shows how to do it:
struct MyObject {
let url: String
}
let objectArray = [
MyObject(url: "mozilla.org"),
MyObject(url: "gnu.org"),
MyObject(url: "git-scm.com")
]
let urlArray = objectArray.map({ $0.url })
let urlSet = Set(urlArray)
dump(urlSet)
// ▿ 3 members
// - "git-scm.com"
// - "mozilla.org"
// - "gnu.org"
#2. Using Array's reduce(into:_:) method
struct MyObject {
let url: String
}
let objectArray = [
MyObject(url: "mozilla.org"),
MyObject(url: "gnu.org"),
MyObject(url: "git-scm.com")
]
let urlSet = objectArray.reduce(into: Set<String>(), { (urls, object) in
urls.insert(object.url)
})
dump(urlSet)
// ▿ 3 members
// - "git-scm.com"
// - "mozilla.org"
// - "gnu.org"
As an alternative, you can use Array's reduce(_:_:) method:
struct MyObject {
let url: String
}
let objectArray = [
MyObject(url: "mozilla.org"),
MyObject(url: "gnu.org"),
MyObject(url: "git-scm.com")
]
let urlSet = objectArray.reduce(Set<String>(), { (partialSet, object) in
var urls = partialSet
urls.insert(object.url)
return urls
})
dump(urlSet)
// ▿ 3 members
// - "git-scm.com"
// - "mozilla.org"
// - "gnu.org"
#3. Using an Array extension
If necessary, you can create a mapToSet method for Array that takes a transform closure parameter and returns a Set. The Playground below code shows how to use it:
extension Array {
func mapToSet<T: Hashable>(_ transform: (Element) -> T) -> Set<T> {
var result = Set<T>()
for item in self {
result.insert(transform(item))
}
return result
}
}
struct MyObject {
let url: String
}
let objectArray = [
MyObject(url: "mozilla.org"),
MyObject(url: "gnu.org"),
MyObject(url: "git-scm.com")
]
let urlSet = objectArray.mapToSet({ $0.url })
dump(urlSet)
// ▿ 3 members
// - "git-scm.com"
// - "mozilla.org"
// - "gnu.org"
reduce() method expects a closure that returns a combined value, while insert() methods of Set value does not return anything but instead it inserts a new element into the existing set.
In order to make it work you would need to do something like:
objects.reduce(Set<String>()) {
$0.union(CollectionOfOne($1.URL))
}
But the above is a bit of an unnecessary complication. If you have a big array, that would mean quite a number of ever-growing sets to be created while Swift goes over all the elements from objects. Better follow the advice from #NRitH and use map() as that would make a resulting set in one go.
Swift 1.0-2.x ONLY:
If URL on your object is a strongly-typed String, you can create a new Set<String> object and use unionInPlace on the set with the mapped array:
var mySet = Set<String>()
mySet.unionInPlace(objects.map { $0.URL as String })
Related
I have an array named bonusCardsTest of type [BonusCard] that is conformed to Identifiable that has an id and an url property of type String.
var bonusCardsTest: [BonusCard] = []
struct BonusCard: Identifiable {
var id = UUID().uuidString
var url: String
}
I also have an array named getBonusURLsArray of type [String] that contains urls.
What I want is to assign each element of getBonusURLsArray to the url property of bonusCardsTest.
For example, if getBonusURLsArray has two elements - "https://test1.com", "https://test2.com", I want the BonusCard array to look like this:
var bonusCardsTest: [BonusCard] = [
BonusCard(url: "https:test1.com"),
BonusCard(url: "https:test2.com"),
]
How do I do that?
As Larme says, you could map your array of URLs to BonusCards:
let bonusCards = getBonusURLsArray.map { BonusCard(url: $0) }
Tried to combine or merging two model to one model
1st model = items [ InboxModel]. (My own Inbox)
2nd model = items2 [MoInboxModel] (SDK Inbox)
1st + 2nd -> combinedItems
private var items: [InboxModel] = []
private var items2: [MoInboxModel] = []
private var combinedItems: [combinedInboxModel] = []
struct InboxModel {
let id: String
let title: String
let message: String
let date: Date
}
struct MoInboxModel {
let id: String
let title: String
let message: String
let date: Date
}
struct combinedInboxModel {
let id: String
let title: String
let message: String
let date: Date
}
self?.combinedItems.append(self?.items). //No exact matches in call to instance method 'append
self?.combinedItems.append(contentsOf: self?.items2 ?? []) //No exact matches in call to instance method 'append
Why there is an error while merge it ? How to merge it correctly?
You have three unrelated types - InboxModel, MoInboxModel and combinedInboxModel (Which should be CombinedInboxModel. Even though they all have properties with the same name, they are different types.
There is no append function on an array of combinedInboxModel that accepts an array of InboxModel or MoInboxModel.
You could use map on each of your two input arrays to convert them to an array of CombinedInboxModel which you can then put into combinedItems.
Presumably you are writing this code in a closure, which is why you have a weak self. Best to deal with that first and then process your arrays.
guard let self = self else {
return
}
self.combinedItems = self.items.map { CombinedInboxModel(id:$0.id,title:$0.title,message:$0.message,date:$0.date) }
let items2 = self.items2.map { CombinedInboxModel(id:$0.id,title:$0.title,message:$0.message,date:$0.date) }
self.combinedItems.append(contentsOf:items2)
You haven't shown where items and items2 come from; Is it possible just to fetch them as instances of the same struct to start with?
The fact that you have three structs with the same properties is a bit fishy. I would consider a different design if I were you.
However, if you must go with this approach, you might want to consider starting with a protocol and getting rid of the combinedInboxModel struct.
protocol InboxModelable {
var id: String { get }
var title: String { get }
var message: String { get }
var date: Date { get }
}
Now make your two structs conform to InboxModelable.
struct InboxModel: InboxModelable {
let id: String
let title: String
let message: String
let date: Date
}
struct MoInboxModel: InboxModelable {
let id: String
let title: String
let message: String
let date: Date
}
Since both of your types conform to InboxModelable you can directly store both types in an array of type Array<InboxModelable> without having to map the elements.
class SomeClass {
private var items: [InboxModel] = []
private var items2: [MoInboxModel] = []
private var combinedItems: [InboxModelable] = []
func combineItems() {
doSomething { [weak self] in
guard let self = self else { return }
self.combinedItems.append(contentsOf: self.items)
self.combinedItems.append(contentsOf: self.items2)
}
}
}
how can I use this type of chaining
let numbers = [20,17,35,4,12]
let evenSquares = numbers.filter{$0 % 2 == 0}.map{$0 * $0}
in such usecase
I have array of objects, I want to filter it by keys of external dictionary and then assign to those filtered objects value of dictionary where object id = dictionary key and then sort result object by dictionary value
here is code I have now:
let scoreDict: [String: Double]
var objects: [Object]
var filteredObjects = objects.filter { scoreDict.keys.contains.contains($0.id.uuidString.lowercased()) }
// .. and what next?
var scoresAssigned = filteredObjects.map { $0.score = scoreDict[$0.id.uuidString.lowercased()] } // This do not compile ""Cannot assign to property: '$0' is immutable"
Assuming Object is a struct, based on the error message...
This just shows that higher-order functions shouldn't be used everywhere. map transforms each element in a sequence into something else. So instead of assigning score, you need to return a new Object with its score changed.
var scoresAssigned = filteredObjects.map { $0.withScore(scoreDict[$0.id.uuidString.lowercased()]) }
withScore will look something like:
func withScore(_ score: Int) -> Object {
var copy = self
copy.score = score
return copy
}
But, if you just want to assign the score a new value, I recommend a simple for loop.
for i in 0..<filteredObjects.count {
filteredObjects[i].score = scoreDict[$0.id.uuidString.lowercased()]
}
Also note that you only need to access the dictionary once. If it's nil, the key doesn't exist.
var filteredObjects = [Object]()
for i in 0..<objects.count {
if let score = scoreDict[$0.id.uuidString.lowercased()] {
objects[i].score = score
filteredObjects.append(objects[i])
}
}
I'm starting to learn about closures and want to implement them in a project I'm working on and I'd like some help.
I have a class defined as follows:
class MyObject {
var name: String?
var type: String?
var subObjects: [MyObject]?
}
And I want to use closures or higher oder functions (something like flatMap comes to mind) to flatten an [MyObject] and joining all MyObject and subOjects into one array.
I've tried using [MyObject].flatMap() but this operation doesn't return the nested subObjects.
First, I would highly recommend making the type of subObjects be non-optional. There's rarely a reason for optional arrays. Do you really need to distinguish between "no array" and "an empty array?" This is very uncommon. If you make subObjects just be an array, you can write what you're describing as a simple recursive function:
func flattenMyObjects(myObjects: [MyObject]) -> [MyObject] {
return myObjects.flatMap { (myObject) -> [MyObject] in
var result = [myObject]
result.appendContentsOf(flattenMyObjects(myObject.subObjects))
return result
}
}
If you need it to be optional, the changes are minor (you'll need to add an if-let or something similar).
One approach to flattening a recursive class structure is with a recursive function.
Here is the class that we would like flattened:
public class Nested {
public let n : Int
public let sub : [Nested]?
public init(_ n:Int, _ sub:[Nested]?) {
self.n = n
self.sub = sub
}
}
Here is the function that demonstrates how this could be done:
func test() {
let h = [
Nested(1, [Nested(2, nil), Nested(3, nil)])
, Nested(4, nil)
, Nested(5, [Nested(6, nil), Nested(7, [Nested(8, nil), Nested(9, nil)])])
]
func recursiveFlat(next:Nested) -> [Nested] {
var res = [Nested]()
res.append(next)
if let subArray = next.sub {
res.appendContentsOf(subArray.flatMap({ (item) -> [Nested] in
recursiveFlat(item)
}))
}
return res
}
for item in h.flatMap(recursiveFlat) {
print(item.n)
}
}
The heart of this approach is recursiveFlat local function. It appends the content of the nested object to the result, and then conditionally calls itself for each element to add their contents as well.
I have a Protocol called Composite.
This protocol has an array composites: [Composite]
I also have a generic subclass GenericSubclass<T>: Composite
When iterating over the array the best I can come up with looks like this:
for item in composites {
if let item = item as? GenericSubclass<A> {
let sc = SomeOtherClass<A>
} else if let item = item as? GenericSubclass<B> {
let sc = SomeOtherClass<B>
} //and so on...
}
Is there any way to get a hold of GenericSubclass without specifying the Generic? In my use case there is absolutely no need for me to know about the T. I just have to instantiate another class with the same generic type.
Any help is much appreciated.
It's not clear what you're trying to accomplish with the "generic" (pun intended) class names you've chosen. I don't think there's a way to directly accomplish what you want. I.e. you can't just leave it as a generic T because the compiler needs some way to determine what T will be in use at runtime.
However, one way to solve the issue is to hoist the API into the Composite protocol:
protocol Composite {
var composites: [Composite] { get set }
func otherClass() -> OtherProtocol
}
protocol OtherProtocol { }
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
func otherClass() -> OtherProtocol {
return SomeOtherClass<T>()
}
}
class SomeOtherClass<T>: OtherProtocol {}
So now when you implement your loop, you can rely on the fact that since each element is a Composite, you know it must provide an instance of OtherProtocol via the otherClass() method:
var c = GenericSubclass<Int>()
c.composites = [GenericSubclass<Double>(), GenericSubclass<Int>(), GenericSubclass<Character>()]
for item in c.composites {
let sc = item.otherClass()
print(sc)
}
Alternatively, if only GenericSubclass should vend an OtherProtocol, you can make the return type Optional and define an extension for all the other implementations of Composite:
protocol Composite {
var composites: [Composite] { get set }
func optionalClass() -> OtherProtocol?
}
extension Composite {
func optionalClass() -> OtherProtocol? {
return nil
}
}
I did some experiment on this in the playground and i came up with this
protocol Composite {
var composites: [Composite] { get set }
}
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
}
let subclass = GenericSubclass<String>()
for item in subclass.composites {
let className = String(describing: type(of: item))
let aClassType = NSClassFromString(className) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(instance) //Output: GenericSubclass<String>
}
Hope this will help someone.
I think it's not possible to do that in array.
While you creat some different GenericSubclass<T> then put it in array , you will lose <T> no matter the composites is [Composite] or [Any].
// this line won't compile
let array = [GenericSubclass<Int>(),GenericSubclass<Double>()]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
You want donging something like this func below, the param should be GenericSubclass<T> to compile success
func genericFunc<T>(param:GenericSubclass<T>) {
let sc = SomeOtherClass<T>()
print(sc)
}
Anyway you can implement it with member var for the instance like the code below:
class Subclass {
var type : Any
init(type : Any) {
self.type = type
}
}
class SomeOtherClass : CustomDebugStringConvertible{
var type : Any
init(type : Any) {
self.type = type
}
var debugDescription: String{
return String(describing: type.self)
}
}
let array : [Subclass] = [Subclass(type : Int.self),Subclass(type : Double.self),Subclass(type : String.self)]
let scArray = array.flatMap {SomeOtherClass(type:$0.type.self)}
print(scArray) // prints [Int, Double, String]
You need to add one method to protocol which creates new item of Type supported this protocol. So now you can use enums, structs and classes without any knowledge of creating object of specific type.
You can play in playground with the following code:
import UIKit
//This is your protocol
protocol MyAwesomeProtocol {
//this methods leaves implementaion detailes
//to concrete type
func createNewObject()->MyAwesomeProtocol
}
//Just create empty string
extension String: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return String()
}
}
//create Enum with default value
extension UIControlState: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return UIControlState.normal
}
}
//create viewController of any type
extension UIViewController: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return type(of:self).init()
}
}
//This is test function
//it creates array of newly created items and prints them out
//in terminal
func doSomeCoolStuffWith(items:[MyAwesomeProtocol]){
var newItems = [MyAwesomeProtocol]()
for anItem in items {
let newOne = anItem.createNewObject()
newItems.append(newOne)
}
print("created new ones:\n\(newItems)\nfrom old ones:\n\(items)\n")
}
doSomeCoolStuffWith(items: [UIControlState.focused,UIControlState.disabled])
doSomeCoolStuffWith(items: [UISplitViewController(),UINavigationController(),UICollectionViewController()])
doSomeCoolStuffWith(items: ["I","love","swift"])
This will produce the following result:
created new ones:
[__C.UIControlState(rawValue: 0), __C.UIControlState(rawValue: 0)]
from old ones:
[__C.UIControlState(rawValue: 8), __C.UIControlState(rawValue: 2)]
created new ones:
[<UISplitViewController: 0x7fa8ee7092d0>, <UINavigationController: 0x7fa8f0044a00>, <UICollectionViewController: 0x7fa8ee705f30>]
from old ones:
[<UISplitViewController: 0x7fa8ee7011e0>, <UINavigationController: 0x7fa8f004e600>, <UICollectionViewController: 0x7fa8ee708fb0>]
created new ones:
["", "", ""]
from old ones:
["I", "love", "swift"]