The code below successfully take a list of favourites names and matches it to a smaller list of names, and upon each match or mismatch, it creates a new data struct indicating if it was favourited. (However this seem to be (N^2) complexity)
listOfNames.forEach {
var faved = false
if namesFavorited.contains($0) {
faved = true
}
let model = DetailNameModel(name: $0, favorited: faved)
detailNameModelArray.append(model)
}
Is there a better way to do this that can SCALE to 10s of thousands say down to N or NLogN?
You can use Set collection. It provides efficient ways to interact with collections. More detail here.
Updated code:
let listOfNames: Set = ["name1", "name2", "name3", "name4", "name5", "name6", "name7", "name8"]
let namesFavorited: Set = ["name2", "name3", "name5", "name6", "name8", "name9", "name10", "name11"]
let nonFav = listOfNames.subtracting(namesFavorited) // O(m*n)
let fav = listOfNames.intersection(namesFavorited) // O(m*n)
var detailNameModelArray: [DetailNameModel] = []
let nonFavDetailNames = nonFav.map { // O(nonFav.count)
DetailNameModel(name: $0, favorited: false)
}
detailNameModelArray.append(contentsOf: nonFavDetailNames)
let favDetailNames = fav.map { // O(fav.count)
DetailNameModel(name: $0, favorited: true)
}
detailNameModelArray.append(contentsOf: favDetailNames)
print(detailNameModelArray) // O(fav.count)
You can generate a lot of data and compare my method and yours. I think my method will win.
Related
Im trying to sort the columns of a CSV file,the contents of the CSV is provided in string
Beth,Charles,Danielle,Adam,Eric\n
17945,10091,10088,3907,10132\n
2,12,13,48,11
Converted String to 2D Array
[["Beth", "Charles", "Danielle", "Adam", "Eric"], ["17945", "10091", "10088", "3907",
"10132"], ["2", "12", "13", "48", "11"]]
How can i sort the only the first dimension of the 2D array or the Names in the 2D Array and still keep the mappings of the other dimension, i don't know how to explain this properly, but i hope the details below will help you understand what i want to achieve.
Adam,Beth,Charles,Danielle,Eric\n
3907,17945,10091,10088,10132\n
48,2,12,13,11
I want to achieve this with the names sorted and the other values in the other arrays mapping to the names like below,
[["Adam", "Beth", "Charles", "Danielle", "Eric"], ["3907", "17945", "10091", "10088",
"10132"], ["48", "2", "12", "13", "11"]]
Using this approach is not working but sorts the whole array
let sortedArray = 2dArray.sorted(by: {($0[0] as! String) < ($1[0] as! String) })
[["3907", "17945", "10091", "10088", "10132"], ["48", "2", "12", "13", "11"], ["Adam", "Beth", "Charles", "Danielle", "Eric"]]
Below if the full code
var stringCSV =
"Beth,Charles,Danielle,Adam,Eric\n17945
,10091,10088,3907,10132\n2,12,13,48,11";
var csvFormatted = [[String]]()
stringCSV.enumerateLines { line , _ in
var res = line.split(separator: ",",omittingEmptySubsequences:
false).map{ String($0) }
for i in 0 ..< res.count {
res[i] = res[i]
}
csvFormatted.append(res)
}
print(csvFormatted)
let sortedArray = csvFormatted.sorted(by: {($0[0] as! String)
< ($1[0] as! String) })
print(sortedArray)
Using "associated" arrays always ends up being messy.
I would start by creating a struct to represent each object (You haven't said what the numbers are, so I have picked a couple of property names. I have also kept String as their type, but converting to Int is possibly better, depending on what the data actually represents).
struct Person {
let name: String
let id: String
let age: String
}
Now you can combine the arrays and use that to build an array of these structs. Then you can sort by the name property.
let properties = zip(sourceArray[1],sourceArray[2])
let namesAndProperties = zip(sourceArray[0],properties)
let structArray = namesAndProperties.map { (name,properties) in
return Person(name: name, id: properties.0, age: properties.1)
}
let sortedArray = structArray.sorted {
return $0.name < $1.name
}
I have two dictionaries in Swift with few similar values which are in dynamic mode:
dict1 = ["a1":"value 1", "b1":"value2", "c1":"value 3"]
dict2 = ["b1": "value2", "d1": "value4"]
If I want to compare these two dictionaries and want to extract only the matching keys even nested, how do I about to do that?
If you want the common keys with the value in one of them :
let intersectionDict = dict1.filter { dict2.keys.contains($0.key) }
//Or
let intersectionDict2 = dict2.filter { dict1.keys.contains($0.key) }
If you want the values to match too:
let intersectionDict3 = dict1.filter { dict2[$0.key] == $0.value }
And the result is:
print(intersectionDict3) //["b1": "value2"]
As others have shown, you can do this using a filter statement. You can make it even quicker by always filtering the smaller of the two dicts, improving the time complexity from O(dict1.size) to O(min(dict1.size, dict2.size).
extension Dictionary {
func intersectingByKeys(with other: Dictionary) -> Dictionary {
let (smallerDict, largerDict) = (self.count < other.count) ? (self, other) : (other, self)
return smallerDict.filter { key, _ in largerDict.keys.contains(key) }
}
}
let dict1 = ["a1":"value 1", "b1":"value2", "c1":"value 3"]
let dict2 = ["b1": "value2", "d1": "value4"]
print(dict1.intersectingByKeys(with: dict2))
You can create a Set from the keys of one of the dictionaries and call intersection on the Set with the keys of the other dictionary.
let matchingKeys = Set(dict1.keys).intersection(dict2.keys) // {"b1"}
I have an array of dictionaries with the following type of structure (which is already sorted) :
[
[
"id": 1,
"name": "ItemA",
"url": "http://url.com"
],
[
"id": 32,
"name": "ItemB",
"url": "http://url.com"
],
...
]
Declared as an array of dictionaries for AnyObject :
var arrayApps = [[String:AnyObject]]()
This array of dictionaries is generated using SwiftyJson :
[..]
if let resData = swiftyJsonVar["data"].arrayObject {
self.arrayItems = resData as! [[String:AnyObject]]
}
[..]
My Goal is to display those items in sections by using the sections headers but after trying to figure it out and looking for an answer, i'm unable to move on.
I've tried to groupe the dictionaries by letters to get a result like this:
[
"A":{[foo1],[foo2]},
"D":{[foo3],[foo5]},
"F":{[foo4],[foo6]}
...
]
But no luck, i've always ended up with errors because my array contains "Optionals".
In summary :
How can I generate Alphabetical section headers based on the name inside a TableView using an array of dictionaries not grouped like the one given above in Swift 3 ?
Thank you in advance !!
You can use the .sorted(by: ) method of Array to compare to elements of you array with each other.
This yields a sortedArray:
let sortedArray = arrayOfApps.sorted(by: {($0["name"] as! String) <= ($1["name"] as! String)})
This will crash if the itemName is not a String but I left it to you to handle any errors. For example changing it to:
$0["name"] as? String ?? ""
EDIT:
// Removed examples and added extension to create desired result
I found one of my old projects where I wrote such extension. Changed it a bit to suit your needs, tell me if it needs some change still:
extension Array {
func sectionTitlesForArray(withName name: (Element) -> String) -> Array<(title: String, elements: NSMutableArray)> {
var sectionTitles = Array<(title: String, elements: NSMutableArray)>()
self.forEach({ element in
var appended = false
sectionTitles.forEach({ title, elements in
if title == name(element) {
elements.add(element)
appended = true
}
})
if appended == false {
sectionTitles.append((title: name(element), elements: [element]))
}
})
return sectionTitles
}
}
// Usage single letter as Section title:
let sectionTitles = arrayOfApps.sectionTitlesForArray(withName: {
let name = $0["name"] as! String
return String(name[name.startIndex])
})
// Quick dirty pretty-print:
sectionTitles.forEach({ sectionTitle in
print("Section title: \(sectionTitle.title) \n")
sectionTitle.elements.forEach({ object in
let element = object as! Dictionary<String,Any>
print("Element name: \(element["name"]!)")
})
print("")
})
Hi I am making an app that show me interesting places. Its showing places in radius. I am using REALM to store values.
However realm don't know how to make Unique values. I am using this for Unique rows.
let result:[String] = realm.objects(E21).sorted("name").uniqueValue("Id_prov", type: String.self)
This for finding things in region around me
var datasourceE21Distance:Results<E21> = realm.findInRegion(E21.self, region: curentRegion).filter(tempRequest)
But i don't know how to combine these things to one and then sort it from closes one to me to the most far.
I will be glad for any help here.
EDIT i am using these two extensions found:
func findInRegion<T: Object>(type: T.Type, region: MKCoordinateRegion, latitudeKey: String = "lat", longitudeKey: String = "lng") -> Results<T> {
// Query
return self
.objects(type)
.filterGeoBox(region.geoBox, latitudeKey: latitudeKey, longitudeKey: longitudeKey)
}
func uniqueValue<U : Equatable>(paramKey: String, type: U.Type)->[U]{
var uniqueValues : [U] = [U]()
for obj in self {
if let val = obj.valueForKeyPath(paramKey) {
if (!uniqueValues.contains(val as! U)) {
uniqueValues.append(val as! U)
}
}
}
return uniqueValues
}
RealmGeoQueries, the library you're using for filtering your entities by a bounding box, supports sorting objects by distance via sortByDistance. This returns an array as this operation has to be done in memory with cached distances.
You would need to make sure that you're uniqueValue method is defined in an extension on Array.
I came up with something like this. But it can't filter right away :-/
let currentLocation = CLLocationCoordinate2D(latitude: currentLat!, longitude: currentLng!)
sortedObjectsByDistance = realm.findNearby(E21.self, origin: currentLocation, radius: 50000.0, sortAscending: true, latitudeKey: "lat", longitudeKey: "lng")
var seenIdProv:[String:Bool] = [:]
sortedObjectsByDistance = sortedObjectsByDistance.filter {
seenIdProv.updateValue(false, forKey: $0.Id_prov) ?? true
}
I have NSArray() which is include names but there's duplicated names how can i remove them ?
After parse query adding the objects to the NSArray and its duplicated
var names = NSArray()
let query = PFQuery(className: "test")
query.whereKey("receivers", equalTo: PFUser.currentUser()!.username!)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
self.names = objects!
let set = NSSet(array: self.names as [AnyObject])
print(objects!.count)
// count is 4
// database looks like this (justin , kevin , kevin , joe)
If your names are strings you could create NSSet from array and it will have only different names.
let names = ["John", "Marry", "Bill", "John"]
println(names)
let set = NSSet(array: names)
println(set.allObjects)
prints:
"[John, Marry, Bill, John]"
"[Bill, John, Marry]"
Update #1
With new information in question (code fragment) it may look like this
var set = Set<String>()
for test in objects as [Test] {
set.insert(test.sender)
}
self.names = Array(set)
To expand on John's answer, an NSSet will, by definition, only contain a single copy of each object that hashes to be equal. So, a common pattern is to convert the array to a set and back.
This will work for any object type that has a reasonable implementation of -hash and -isEqual:. As John shows, String is one such object.
You could also do it with pure Swift:
let arrayWithDuplicates = [ "x", "y", "x", "x" ]
let arrayWithUniques = Array(Set(arrayWithDuplicates)) // => [ "y", "x" ]
But, it looks like you're already working with NSArray, so I think the John's example is more applicable.
Also, as my example shows, be aware that the order of the final array is not guaranteed to be in the same order as your original. If you want that, I think you can use NSOrderedSet instead of NSSet.
Here is a more complicated way to approach this that works. You could just run through a loop of the array and create a new one from the original. For example:
var check = 0
let originalArray:NSMutableArray = ["x", "y", "x", "z", "y", "z"]
let newArray: NSMutableArray = []
println(originalArray)
for var int = 0; int<originalArray.count; ++int{
let itemToBeAdded: AnyObject = originalArray.objectAtIndex(int)
for var int = 0; int<newArray.count; ++int{
if (newArray == ""){
break;
}
else if ((newArray.objectAtIndex(int) as! String) != itemToBeAdded as! String){
}
else if ((newArray.objectAtIndex(int) as! String) == itemToBeAdded as! String){
check = 1
break
}
}
if (check == 0){
newArray.addObject(itemToBeAdded)
}
}
Basically I set a check var = 0. for every object in the originalArray, it loops through the newArray to see if it already exists and if it does the check var gets set to 1 and the object does not get added twice.