Given an array of dictionaries some of which have ParentId I need to sort them in ancestry order.
I have a working algorithm, but I am not sure it is actually efficient.
How can this be improved?
Sample data:
var dicts = [["ParentId": "eee82", "Id":"a0dq1"],
["ParentId": "pqrst", "Id":"eee82"],
["ParentId": "aaa1", "Id":"pqrst"]]
Sample output
["pqrst", "eee82", "a0dq1"]
I ran below in playground
import Foundation
// GIVEN this source...
var dicts = [["ParentId": "eee82", "Id":"a0dq1"],
["ParentId": "pqrst", "Id":"eee82"],
["ParentId": "aaa1", "Id":"pqrst"]]
func findParents(source: [[String:String]], this: [String:String]) -> [[String:String]] {
var results = [[String:String]]()
if let parentId = this["ParentId"],
let parent = source.first(where: { $0["Id"] == parentId }) {
results.append(parent)
results.append(contentsOf: findParents(source: source, this: parent))
}
return results
}
var this = dicts.first!
var accounts = (findParents(source: dicts, this: this) + [this])
var sorted = [[String:String]]()
var hasParentMap = [String: Bool]()
for account in accounts {
let parentId = account["ParentId"]
let hasParent = accounts.first(where: { $0["Id"] == parentId }) != nil
hasParentMap[account["Id"]!] = !(parentId == nil || !hasParent)
}
while sorted.count != accounts.count {
for account in accounts {
if sorted.first(where: { $0["Id"] == account["Id"] }) != nil {
continue
}
if hasParentMap[account["Id"]!] == false {
sorted.insert(account, at: 0)
continue
} else if let parentId = account["ParentId"] {
let parentIndex = sorted.firstIndex(where: { $0["Id"] == parentId })
if parentIndex == nil {
continue
}
sorted.insert(account, at: parentIndex! + 1)
}
}
}
dump (accounts.map({ $0["Id"]! })) // ["eee82", "pqrst", "a0dq1"]
// ...we want to get this output
dump (sorted.map({ $0["Id"]! })) // ["pqrst", "eee82", "a0dq1"]
Update removed the numerical ids to avoid confusion
Here's the visual illustration of what I am trying to achieve
To make things easier I created a Person type:
struct Person: Comparable, CustomStringConvertible {
let id: String
let parentID: String
var description: String {
return "[\(id), \(parentID)]"
}
static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.id < rhs.id
}
init?(dict: [String: String]) {
guard let id = dict["Id"] else { return nil }
guard let parentID = dict["ParentId"] else { return nil }
self.id = id
self.parentID = parentID
}
func toDictionary() -> [String: String] {
return ["Id": id, "ParentId": parentID]
}
}
Here is our data:
var dicts = [
["ParentId": "2", "Id":"3"],
["ParentId": "1", "Id":"2"],
["ParentId": "42", "Id":"1"],
["ParentId": "100", "Id":"88"],
["ParentId": "88", "Id":"77"],
["ParentId": "77", "Id":"66"],
["ParentId": "77", "Id":"55"],
]
Here are our people converted to structs:
var people = dicts.compactMap { Person(dict: $0) }
Here are a few methods to operate on our array of people:
extension Array where Element == Person {
func tree(root: Person) -> [Person] {
[root] + children(of: root)
.flatMap { tree(root: $0) }
}
func topLevelParents() -> [Person] {
return filter { parent(of: $0) == nil }
}
func children(of parent: Person) -> [Person] {
return filter { $0.parentID == parent.id }.sorted()
}
func parent(of child: Person) -> Person? {
return first { child.parentID == $0.id }
}
}
Get all people who don't have parents:
let topLevelParents = people.topLevelParents().sorted()
print("topLevelParents: \(topLevelParents)")
Construct the tree of descendants for each parent and flatten into an array:
let results = topLevelParents.flatMap({ people.tree(root: $0) })
print("results: \(results)")
Convert back to a dictionary:
let dictionaryResults = results.map { $0.toDictionary() }
print("dictionaryResults: \(dictionaryResults)")
Related
I have a problem
Value of type [String] has no member replacingOccurrences
When I call replacingOccurrences it does not seem to work for Array types.
func translateEquipement(equipment: [String]) -> [String]{
var result = equipment
let equipmentsSonoVideo = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
for equipmentSonoVideo in equipmentsSonoVideo {
result = result.replacingOccurrences(of: equipmentSonoVideo, with: NSLocalizedString(equipmentSonoVideo, comment: "") options: NSString.CompareOptions.literal, range: nil)
}
return result
}
My function below is for showing equipment:
func showEquipments(place: Place) {
var sonoShowed = false
var videoShowed = false
if let equipments = place.equipments {
for equipment in equipments {
// equipement
if equipment == "Sono" {
// equipement sono
self.sonoLabel.text = equipment
sonoShowed = true
}
else if equipment == "Video projecteur" {
// equipement video
self.videoLabel.text = equipment
videoShowed = true
}
}
}
if !sonoShowed {
self.sonoContainer.isHidden = true
}
if !videoShowed {
self.videoContainer.isHidden = true
}
}
As the error says you can not use that method on an array so you need to access each string in the array individually. I did this using map like this
func translateEquipement(equipment: [String]) -> [String]{
var result = equipment
let equipmentsSonoVideo = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
for item in equipmentsSonoVideo {
result = result.map {
$0.replacingOccurrences(of: item,
with: NSLocalizedString(item, comment: ""),
options: NSString.CompareOptions.literal, range: nil)
}
}
return result
}
If you like, you can create an extension for [String] so you can use replacingOccurrences as you are in your current code:
extension Array where Element == String {
func replacingOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions) -> Self
{
return self.map( { $0.replacingOccurrences(of: target, with: replacement, options: options) } )
}
}
If you require the NSRange parameter that you're specifying nil for simply add it.
This code with id is accepted:
router.post([Page].self, at: "/fetchStatusOfManagedReleases") { (req, pages) -> Future<[Page]> in
let eventIds = pages.map { $0.events }.flatMap { $0 }.map { $0.id }
return Release.query(on: req).filter(\.fbId ~~ eventIds).all().flatMap { releases in
let releaseInnerIds = releases.map {$0.id}
return TestPrice.query(on: req).filter(\.id ~~ releaseInnerIds).all().flatMap { testPrices in
return req.future([])
}
but this here with releaseId is not:
router.post([Page].self, at: "/fetchStatusOfManagedReleases") { (req, pages) -> Future<[Page]> in
let eventIds = pages.map { $0.events }.flatMap { $0 }.map { $0.id }
return Release.query(on: req).filter(\.fbId ~~ eventIds).all().flatMap { releases in
let releaseInnerIds = releases.map {$0.id}
return TestPrice.query(on: req).filter(\.releaseId ~~ releaseInnerIds).all().flatMap { testPrices in
return req.future([])
}
TestPrice model:
final class TestPrice: Content {
var id: Int?
var price: String
var releaseId: Release.ID
init(id: Int?, price: String, releaseId: Release.ID) {
self.id = id
self.price = price
self.releaseId = releaseId
}
}
Why? What workaround I need to do?
I work with unit-tests and encounter this problem:
I have some classes and each have their own isEqual() method. At some point I came to a situation where an unit-test sometimes fails and sometimes succeeds.
I check the equality of two objects that contain a set of objects. Here the the problem arises. Sometimes the test "obj1.mySet == obj2.mySet" fails - sometimes not. I test this with only one object in each set (mySet). The test for the equality of this objects (in mySet) itself succeeds.
I tried some hours to find a mistake in my code, but couldn't find any. Now I have a workaround that helps to pass the test, but I do not understand, what's going on. I have a method within the test-objects, that returns the objects of the set as an (ordered) array. When I test the equality of this arrays, the test always succeeds.
Do someone know, what’s going on?
Update:
In my BaseClass
func hash(into hasher: inout Hasher) { hasher.combine(firebaseID) }
static func == (lhs: FirebaseObject, rhs: FirebaseObject) -> Bool { return lhs.isEqual(to: rhs) }
func isEqual(to object: Any?) -> Bool {
guard object != nil && object is FirebaseObject else { return false }
let value = object as! FirebaseObject
return firebaseID == value.firebaseID && name == value.name
}
In the SubClass
override func isEqual(to object: Any?) -> Bool {
guard object != nil && object! is MealPlanned else { return false }
let obj = object as! MealPlanned
var result = ""
if !super.isEqual(to:obj) { result.append("fbObject ") }
if portions != obj.portions { result.append("portions ") }
if imgID != obj.imgID { result.append("imgID ") }
if meal != obj.meal { result.append("meal ") }
if date != obj.date { result.append("date ") }
if portionsInBaseMeal != obj.portionsInBaseMeal {result.append("portionsInBaseMeal ") }
if getIngrediencesInMeals() != obj.getIngrediencesInMeals() { result.append("ingrediencesInMeals ") }
if result.count > 0 {
if (showsDifference) { print("difference in MealPlanned <\(obj.name ?? "Fehler")>: \(result)") }
return false
}
return true
}
I did it this way, to find and print the problem.
This version succeeds.
if getIngrediencesInMeals() != obj.getIngrediencesInMeals() { result.append("ingrediencesInMeals ")
getIngrediencesInMeals() returns the set as an ordered array.
In this way the test sometimes succeeds sometimes fails:
if ingrediences != ingrediences { result.append("ingrediencesInMeals ")
This returns the ordered array:
func getIngrediencesInMeals() -> [IngredienceInMeals] { return ingrediences.sorted{ $0.position < $1.position } }
in IngredienceInMeals
override func isEqual(to object: Any?) -> Bool {
guard object != nil && object! is IngredienceInMeals else { return false }
let obj = object as! IngredienceInMeals
var result = ""
if !super.isEqual(to:obj) { result.append("fbObject ")}
if unit != obj.unit { result.append("unit ")}
if quantity != obj.quantity { result.append("quantity ")}
if ingredience != obj.ingredience { result.append("ingredience ")}
if position != obj.position { result.append("position ")}
if result.count > 0 {
if (showsDifference) { print("difference in IngredienceInMeal <\(obj.name ?? "Fehler")>: \(result)") }
return false
}
return true
}
if you want to compare two objects use Equatable protocol method in your object class
example of compare two objects
class ItemModel : Equatable {
var TypeOfOffer : String?
var TypeOfSelling : String?
var Address : String?
var NumberOfRoom : String?
var Price : String?
var Image : String?
var ID : String?
var itemId : String?
init(TypeOfOffer : String? , TypeOfSelling : String?, Address : String?, NumberOfRoom : String? , Price : String?, Image : String?, ID : String?, itemId : String? )
{
self.TypeOfOffer = TypeOfOffer
self.TypeOfSelling = TypeOfSelling
self.Address = Address
self.NumberOfRoom = NumberOfRoom
self.Price = Price
self.Image = Image
self.ID = ID
self.itemId = itemId
}
static func == (lhs: ItemModel, rhs: ItemModel) -> Bool {
var isIt = true
isIt = (lhs.TypeOfOffer == "" || lhs.TypeOfOffer == rhs.TypeOfOffer)
&& (lhs.TypeOfSelling == "" || lhs.TypeOfSelling == rhs.TypeOfSelling)
&& (lhs.Address == "" || lhs.Address == rhs.Address)
&& (lhs.NumberOfRoom == "" || lhs.NumberOfRoom == rhs.NumberOfRoom)
&& (lhs.Price == "" || lhs.Price == rhs.Price)
return isIt
}
}
Compare two instances of an object in Swift!
I'm having an array of models:
struct Contact {
var givenName: String!
var familyName: String!
var organizationName: String!
}
I want to filter those contacts using ONE UITextField. My current problem is to define between words and filter only contacts matching all words.
For example:
var contacts: [Contact] = [Contact(givenName: "David", familyName: "Seek", organizationName: "Aldi"),
Contact(givenName: "Johne", familyName: "Doe", organizationName: "Kaisers"),
Contact(givenName: "Jane", familyName: "Doe", organizationName: "Tengelmann"),
Contact(givenName: "Marco", familyName: "Seidl", organizationName: "Rewe"),
Contact(givenName: "Filip", familyName: "Halbig", organizationName: "Aldi")]
I want to enter: David Aldi and only find David who works at Aldi. I don't want to see Filip who also works at Aldi.
Also if I enter David Do, I don't want to see any contacts, because none should match.
func getSearchResults(_ filterKey: String) {
self.presentActivityIndicator()
var processed: Int = 0
self.filteredContacts.removeAll()
DispatchQueue.global(qos: .background).async {
for contact in self.unfilteredContacts {
processed += 1
let lowercasedGivenName = contact.givenName.replacingOccurrences(of: " ", with: "").lowercased()
let lowercasedFamilyName = contact.familyName.replacingOccurrences(of: " ", with: "").lowercased()
let lowercasedOrganizationName = contact.organizationName.replacingOccurrences(of: " ", with: "").lowercased()
let name = lowercasedGivenName.appending(lowercasedFamilyName)
if name.range(of: filterKey.replacingOccurrences(of: " ", with: "")) != nil {
if !self.filteredContacts.contains(contact) {
self.filteredContacts.append(contact)
}
}
for word in filterKey.components(separatedBy: " ") {
if lowercasedOrganizationName.range(of: word.lowercased()) != nil {
if !self.filteredContacts.contains(contact) {
self.filteredContacts.append(contact)
}
}
}
if processed == self.unfilteredContacts.count {
self.reloadTableViewInMainThread()
}
}
}
}
This is one of the several approaches I've tried today. But with every try, I've been ending up, filtering out David and by entering the second name, I have found other contacts not matching the first name David, but match for example part of the family name or company name.
What am I missing and would be the best approach for this? Help is very appreciated.
First separate the filterKey into space separated components
let components = filterKey.components(separatedBy: " ")
then use the filter function with the closure syntax
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if contact.givenName != string && contact.familyName != string && contact.organizationName != string {
return false
}
}
return true
}
The closure returns true if the contact matches all components.
Wrapped in the function it's
func getSearchResults(_ filterKey: String) {
let components = filterKey.components(separatedBy: " ")
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if contact.givenName != string && contact.familyName != string && contact.organizationName != string {
return false
}
}
return true
}
}
Note:
Please, please never declare properties / members as implicit unwrapped optionals which are initialized with an init method. It perfectly legal (and recommended) to declare the members without the exclamation marks. However if they are supposed to be optional declare them as real optional (question mark).
And if the values won't change declare the members as constants (let).
struct Contact {
let givenName: String
let familyName: String
let organizationName: String
}
Edit:
To filter the contacts whose properties contain filterKey write
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if !contact.givenName.lowercased().contains(string) &&
!contact.familyName.lowercased().contains(string) &&
!contact.organizationName.lowercased().contains(string) {
return false
}
}
return true
}
To filter the contacts whose properties begin with filterKey write
self.filteredContacts = contacts.filter { contact -> Bool in
for string in components {
if !contact.givenName.lowercased().hasPrefix(string) &&
!contact.familyName.lowercased().hasPrefix(string) &&
!contact.organizationName.lowercased().hasPrefix(string) {
return false
}
}
return true
}
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ")
var contactsFiltered = contacts
keys.forEach { key in
contactsFiltered = contactsFiltered.filter {
$0.givenName == key || $0.familyName == key || $0.organizationName == key
}
}
return contactsFiltered
}
I splitted the filterKeys by blank spaces. Then, for each key, I check if the value exists in contact attribute.
If do you want a pure functional solution, you can use Set and intersection:
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ")
return contacts.filter {
Set([$0.givenName, $0.familyName, $0.organizationName]).intersection(keys).count >= keys.count
}
}
And, if do you want a crazy solution with Mirror, for when adding a new attribute in Contact, you do not need to update getSearchResults:
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ")
return contacts.filter {
let stringAttr = Mirror(reflecting: $0).children.filter { ($0.value as? String) != nil }
let contactValues = stringAttr.map { $0.value as! String }
return Set(contactValues).intersection(keys).count >= keys.count
}
}
Use with caution my last code (or never use it)
Edit
For match part of string in key.
func getSearchResults(contacts: [Contact], filterKeys: String) -> [Contact] {
let keys = filterKeys.components(separatedBy: " ").map { $0.lowercased() }
var contactsFiltered = contacts
keys.forEach { key in
contactsFiltered = contactsFiltered.filter {
$0.givenName.lowercased().range(of: key) != nil ||
$0.familyName.lowercased().range(of: key) != nil ||
$0.organizationName.lowercased().range(of: key) != nil
}
}
return contactsFiltered
}
** REWRITE **
OK, it turns out I'm really asking a different question. I understand about hashValue and ==, so that's not relevant.
I would like my wrapper class BUUID to "do the right thing" and act just like NSUUID's act in a Dictionary.
See below, where they don't.
import Foundation
class BUUID: NSObject {
init?(str: String) {
if let uuid = NSUUID(UUIDString: str) {
_realUUID = uuid
}
else {
return nil
}
}
override init() {
_realUUID = NSUUID()
}
private var _realUUID: NSUUID
override var description: String { get { return _realUUID.UUIDString } }
override var hashValue: Int { get { return _realUUID.hashValue } }
var UUIDString: String { get { print("WARNING Use description or .str instead"); return _realUUID.UUIDString } }
var str: String { get { return _realUUID.UUIDString } }
}
func ==(lhs: BUUID, rhs: BUUID) -> Bool { return lhs._realUUID == rhs._realUUID }
let a = BUUID()
let b = BUUID(str: a.str)!
print("a: \(a)\nb: \(b)")
print("a === b: \(a === b)")
print("a == b: \(a == b)")
var d = [a: "Hi"]
print("\(d[a]) \(d[b])")
let nA = NSUUID()
let nB = NSUUID(UUIDString: nA.UUIDString)!
print("na: \(nA)\nnB: \(nB)")
print("nA === nB: \(nA === nB)")
print("nA == nB: \(nA == nB)")
var nD = [nA: "Hi"]
print("\(nD[nA]) \(nD[nB])")
Results. Note that I can look up using NSUUID nB and get back what I put under nA. Not so with my BUUID.
a: 9DE6FE91-D4B5-4A6B-B912-5AAF34DB41C8
b: 9DE6FE91-D4B5-4A6B-B912-5AAF34DB41C8
a === b: false
a == b: true
Optional("Hi") nil
nA: <__NSConcreteUUID 0x7fa193c39500> BB9F9851-93CF-4263-B98A-5015810E4286
nB: <__NSConcreteUUID 0x7fa193c37dd0> BB9F9851-93CF-4263-B98A-5015810E4286
nA === nB: false
nA == nB: true
Optional("Hi") Optional("Hi")
Inheriting from NSObject also assumes isEqual(object: AnyObject?) -> Bool method overloading:
import Foundation
class BUUID: NSObject {
init?(str: String) {
if let uuid = NSUUID(UUIDString: str) {
_realUUID = uuid
}
else {
return nil
}
}
override init() {
_realUUID = NSUUID()
}
private var _realUUID: NSUUID
override func isEqual(object: AnyObject?) -> Bool {
guard let buuid = object as? BUUID else {
return false
}
return buuid._realUUID == _realUUID
}
override var description: String { get { return _realUUID.UUIDString } }
override var hashValue: Int { get { return _realUUID.hashValue } }
var UUIDString: String { get { print("WARNING Use description or .str instead"); return _realUUID.UUIDString } }
var str: String { get { return _realUUID.UUIDString } }
}
func ==(lhs: BUUID, rhs: BUUID) -> Bool { return lhs._realUUID == rhs._realUUID }
let a = BUUID()
let b = BUUID(str: a.str)!
print("a: \(a)\nb: \(b)")
print("a === b: \(a === b)")
print("a == b: \(a == b)")
var d = [a: "Hi"]
print("\(d[a]) \(d[b])")
let nA = NSUUID()
let nB = NSUUID(UUIDString: nA.UUIDString)!
print("na: \(nA)\nnB: \(nB)")
print("nA === nB: \(nA === nB)")
print("nA == nB: \(nA == nB)")
var nD = [nA: "Hi"]
print("\(nD[nA]) \(nD[nB])")
So the answer is to not make BUUID inherit from NSObject, which undercuts the Swiftiness of overriding ==.
So:
extension BUUID: Hashable {}
class BUUID: CustomStringConvertible {
// take away all 'override' keywords, nothing to override
// otherwise same as above
}
Interesting!
This answer is relevant to initially asked question: Why that's possible to get two key-value pairs with identical key's hashes in a dictionary
This example illustrates that keys in Dictionary can have identical hashes, but equality operation should return false for different keys:
func ==(lhs: FooKey, rhs: FooKey) -> Bool {
return unsafeAddressOf(lhs) == unsafeAddressOf(rhs)
}
class FooKey: Hashable, Equatable {
var hashValue: Int {
get {
return 123
}
}
}
var d = Dictionary<FooKey, String>()
let key1 = FooKey()
let key2 = FooKey()
d[key1] = "value1"
d[key2] = "value2"
Output
[FooKey: "value1", FooKey: "value2"]
That's definitely not good to have all keys with the same hash. In this case we are getting that worst case when search element complexity fells down to O(n) (exhaustive search). But it will work.