When I have a reference to an item in an array, I'd like to find another item closest to it that matches a certain criteria.
For example, I have this array:
struct Person {
var name = ""
var age = ""
var gender = ""
var address = ""
}
let array = [Person(name:"ahmad",age:"22",gender:"male",address:"USA"),
Person(name:"ahmad",age:"23",gender:"male",address:"KSA")]
Now let's say I have an object as:
let object = Person(name:"ahmad",age:"25",gender:"male",address:"USA")
based on that, I need to find the closest person to it on array, based on the values of all its properties.
As an example, based on my above code, the closest object in array to object is the first one because they have the same name, gender and address (3 matched properties), instead of the second one because there is only 2 matched properties.
First you have to define a function which measures the “closeness”
of two persons. Based on your example that could be
struct Person {
let name: String
let age: String
let gender: String
let address: String
// Compute a number measuring the “closeness” to another person,
// with smaller numbers meaning “closer”.
// For example: the number of non-matching properties.
func distance(to other: Person) -> Int {
return (name == other.name ? 0 : 1)
+ (age == other.age ? 0 : 1)
+ (gender == other.gender ? 0 : 1)
+ (address == other.address ? 0 : 1)
}
}
Then the closest person can be determined using min(by:):
let object = Person(name:"ahmad", age:"25", gender:"male", address:"USA")
let closest = array.min(by: { $0.distance(to: object) < $1.distance(to: object) })!
print(closest) // Person(name: "ahmad", age: "22", gender: "male", address: "USA")
The above distance method is just an example, you have to adjust it
for your exact needs.
Related
I have an array that I populate from firestore that uses a struct. Is there a way to count the number of times there is a matching string for the productName var.
This is my struct...
struct PlayerStock: Codable, Identifiable {
#DocumentID var id: String?
var productName: String
var qty: Int
var saleUID: String
var totalPrice: Int
var uid: String
var unitPrice: Int
}
This is what's in my VC, I populate this from firestore and then want to count matching strings in productName
var playerStock: [PlayerStock] = []
Is there a way to do this without using a for loop?
Strings I'd like to count in productName include "smartphone" or "laptop" I want to store the matching total count as an int like this:
var smartphoneTotal =
var laptopTotal =
etc etc..
I've tried using filters and compact map but can't find anything that works, I think its because the array is multidimensional or because its using a dictionary?
Pretty noob here so any help appreciated!
First group the array by productName
let groupedProducts = Dictionary.init(grouping: playerStock, by: \.productName)
you'll get
["smartphone":[PlayerStock(..), PlayerStock(..), PlayerStock(..)],
"laptop":[PlayerStock(..), PlayerStock(..)]
then map the values to their amount of items
.mapValues(\.count)
The result is
["smartphone":3, "laptop":2]
If you want to use filter, something like this should work with your struct:
var laptopTotal = playerStock.filter { $0.productName == "laptop" }.count
This may help
let wordsToFind = ["smartphone", "laptop"]
var foundCounts: [String: Int] = [:]
for p in playerStock {
for word in wordsToFind {
if p.name.contains(word) {
foundCounts[word] = foundCounts[word, default: 0] + 1
}
}
}
foundCounts
If you really want a functional "no for-loops" version, and if you mean you want to find things that contain your search terms, then:
let wordsToFind = ["smartphone", "laptop"]
let founds = wordsToFind.map { word -> (String, Int) in
playerStock.reduce(("", 0)) { partialResult, player in
(word, partialResult.1 + (player.name.contains(word) ? 1 : 0))
}
}
You could use the higher order functions filter() or reduce(). #ShawnFrank already gave an answer using filter(). (voted.)
For a small number of items, there isn't a big difference between filter() and reduce(). For large datasets, though, filter creates a second array containing all the items that match the filter criteria. Arrays are value types, so they hold copies of the entries they contain. This would increase the memory footprint needed to do the counting. (You'd have the original array and a copy containing all the matching elements in memory).
The higher order function reduce() works differently. it takes a starting value (a total starting at 0 in our case) for the result, and a closure. The closure takes the current result, and an element from the array you are parsing. At runtime, the reduce() function calls your closure over and over, passing in each element from the array you are reducing. In the first call to the closure, it sends the closure the initial value for result (a zero total, in our case.) In each subsequent call to the closure, it passes the result of the previous call. (The running total, for our implementation.) The reduce() function returns the result returned by the last call to your closure.
You can use reduce to count the number of items that match a given test without having to build a temporary array. Below is a sample implementation using reduce(). Note that I tweaked your PlayerStock type to add default values for all the properties other than productName since I don't care about those.
// Define the PlayerStock type, but use default values for everything but `productName`
struct PlayerStock: Codable, Identifiable {
var id: String? = nil
var productName: String
var qty: Int = Int.random(in: 1...10)
var saleUID: String = ""
var totalPrice: Int = Int.random(in: 10...200)
var uid: String = ""
var unitPrice: Int = Int.random(in: 10...200)
}
// Create an array of test data
let players = [
PlayerStock(productName: "smartphone"),
PlayerStock(productName: "CD Player"),
PlayerStock(productName: "laptop"),
PlayerStock(productName: "CD Player"),
PlayerStock(productName: "smartphone"),
PlayerStock(productName: "laptop"),
PlayerStock(productName: "smartphone"),
PlayerStock(productName: "boom box"),
PlayerStock(productName: "laptop"),
PlayerStock(productName: "smartphone"),
]
/// This is a function that counts and returns the number of PlayerStock items who's productName property matches a the string nameToFind.
/// If you pass in printResult = true, it logs its result for debugging.
/// - Parameter nameToFind: The `productName` to search for
/// - Parameter inArray: The array of `PlayerStock` items to search
/// - Parameter printResult: a debugging flag. If true, the function prints the count if items to the console. Defaults to `false`
/// - Returns: The number of `PlayerStock` items that have a `productName` == `nameToFind`
#discardableResult func countPlayers(nameToFind: String, inArray array: [PlayerStock], printResult: Bool = false) -> Int {
let count = array.reduce(0, { count, item in
item.productName == nameToFind ? count+1 : count
})
if printResult {
print("Found \(count) players with productName == \(nameToFind)")
}
return count
}
let smartphoneCount = countPlayers(nameToFind: "smartphone", inArray: players, printResult: true)
let laptopCount = countPlayers(nameToFind: "laptop", inArray: players, printResult: true)
let cdPlayerCount = countPlayers(nameToFind: "CD Player", inArray: players, printResult: true)
This sample code produces the following output:
Found 4 players with productName == smartphone
Found 3 players with productName == laptop
Found 2 players with productName == CD Player
What is the easiest way to search an ObjectArray in Kotlin by a property belonging to that Object?
For example I have a data class
data class Cat(
var name: String,
var age: Int,
var type: String,
)
and I have a Array<Cat> and would like to find the first occurrence of a cat with age == 4.
You can use find function to find the element with age == 4:
val cats = arrayOf(
Cat("Name1", 2, "Type1"),
Cat("Name2", 4, "Type2")
)
val cat: Cat? = cats.find { it.age == 4 }
find function returns the first element matching the given predicate, or null if no such element was found.
I would to know how to get key if I have the values. Which class get higher marks?
let higherMarks = [
"ClassA": [10,20,30,40,50,60],
"ClassB": [15,25,35,45,55,65],
"ClassC": [18,28,38,48,58,68],
]
var largest = 0
var className = ""
for (classTypes, marks) in higherMarks {
for mark in marks {
if mark > largest {
largest = mark
}
}
}
print(largest)
What I'm saying in my comment is that you need to get the classTypes when you get the mark. Because when you get the higher mark, you want to also get the corresponding key value.
Keeping your code's logic I would do something like this:
let higherMarks = [
"ClassA": [10,20,30,40,50,60],
"ClassB": [15,25,35,45,55,65],
"ClassC": [18,28,38,48,58,68],
]
func findBestClass(in results: [String: [Int]]) -> (name: String, score: Int) {
var largest = 0
var type = ""
for (classType, marks) in results {
if let max = marks.max(), max > largest {
largest = max
type = classType
}
}
return (type, largest)
}
let best = findBestClass(in: higherMarks)
print("The best class is \(best.name) with a score of \(best.score).")
I just replaced your inner loop with .max() and changed the name of the key variable because it should not be plural. My method also returns a tuple because I find it relevant in this situation. But I didn't change your logic, so you can see what I meant by "also get the classTypes".
is it possible to have a dictionary, that contains multiple values for one key?
For example I have multiple IDs (101,102,103,104...) and all of those IDs have some values (colour: green, number: 4, city: New York). I am new to programming and not sure how should I store it.
I was thinking about custom class:
class myClass: NSObject {
var ID: Int
var colour: String
var number: Int
var city: String
init(ID: Int, colour: String, number: Int, city: String) {
self.ID = ID
self.colour = colour
self.number = number
self.city = city
}
}
And the create some variable var myData = myClass() and just everything there, but then how could I access lets say ID 102 and modify its colour?
So my question how to store data provided in my example with an option to be able select specific ID and modify values belonging to that ID.
A dictionary must contain exactly one value for each key. However, that value could be an array, or a dictionary, or an object etc. etc. so that is no practical restriction.
Since instance of classes are stored as references, the dictionary values could be references to class instances. So you can extract the reference to an instance from the dictionary, and then modify the instance. Be aware that anyone holding a reference to the same instance will see the same changes.
Assuming you have two instances of your class
let item101 = MyClass(ID: 101, colour: "red", number: 4, city: "NewYork")
let item102 = MyClass(ID: 102, colour: "green", number: 7, city: "Chicago")
You can store them in a dictionary using the ID as key
let dictionary = [101: item101, 102: item102]
and retrieve a value by ID with
if let id101 = dictionary[101] {
// use id101
} else {
print("ID 101 not found")
}
or you can use an array
let array = [item101, item102]
and get an item by ID with the filter function
let filtered = array.filter{ $0.ID == 102 }
if !filtered.isEmpty {
let id102 = filtered[0]
} else {
print("ID 102 not found")
}
Declare you id with Array and foreach in that ID
var ID: [Int]
I am not expert in Swift and I have been using it for few months to build Mac Apps. I would like to represent in memory a data structure like that of PHP associative arrays but in Swift. Let's imagine that I have a table of data to load in memory with the following fields/records:
ID Surname Name
1 XXX YYY
2 ZZZ WWW
3 JJJ KKK
What I would like to obtain is an associative array like the one I would be able to obtain in PHP:
$arr[1]["Surname"] = "XXX"
$arr[1]["Name"] = "YYY"
$arr[2]["Surname"] = "ZZZ"
$arr[2]["Name"] = "WWW"
I just cannot find the right data structure in Swift to obtain the same result. I tried with the following piece of code:
class resObject: NSObject {
private var cvs = [Int: [String: String]]()
override init() {
self.cvs[0] = ["Name" : "XXX"]
self.cvs[0] = ["Surname" : "YYY"]
self.cvs[1] = ["Name" : "ZZZ"]
self.cvs[1] = ["Surname" : "WWW"]
for (key, arr) in cvs {
let sur = arr["Surname"]
let nam = arr["Name"]
println("Row \(key) - Surname: \(sur), Name: \(nam)")
}
super.init()
}
}
It looks to me pretty close, but it does not work. What I get in the output is the following (I don't care about the "Optional(s)":
Row 0 - Surname: Optional("XXX"), Name: nil
Row 1 - Surname: Optional("ZZZ"), Name: nil
I tried to make some tests in debug and I noticed that the data that are saved in memory are just that of the last key:value pair used (i.e. if I assign Surname first and Name second I get Surname as nil and Name with the correct value).
Please consider that, as in the example, I don't know the data structure when I declare the variable, so I declare it empty and fill it programmatically later.
I don't know if it is just me not declaring the data structure correctly, or if it is Swift that does not allow to do that. Any help will be greatly appreciated.
Thanks a lot.
Regards,
Alessio
One way is a Dictionary of structs. Consider:
struct Person {
var firstName: String
var lastName: String
}
var peopleByID = [ Int: Person ]()
peopleByID[1] = Person(firstName: "First", lastName: "Last")
peopleByID[27] = Person(firstName: "Another", lastName: "LastName")
var myID = 1 // Try changing this to 2 later
if let p = peopleByID[myID] {
println("Found: \(p.firstName) with ID: \(myID)")
}
else {
println("No one found with ID: \(myID)")
}
You can then update the structure:
peopleByID[1].firstName = "XXX"
peopleByID[27].lastName = "ZZZ"
You can iterate freely:
for p in peopleByID.keys {
println("Key: \(p) value: \(peopleByID[p]!.firstName)")
}
Note that a mere array of [Person] isn't so hot, because the IDs:
-- may not be Ints, but are often Strings
-- even if they remain Ints, an array takes up storage in proportion to the highest numbered index, whereas a Dictionary only takes up storage in proportion to the number of stored objects. Imagine storing just two ID's: 523123, and 2467411.
EDIT
It seems like you don't know the attributes ahead of time that will go into each Person object. That's odd, but you should then do:
struct Person {
var attributes = [String : String]() // A dictionary of String keys and String values
}
var peopleByID = [ Int : Person ]()
// and then:
var p1 = Person()
var p2 = Person()
p1.attributes["Surname"] = "Somename"
p1.attributes["Name"] = "Firstname"
p2.attributes["Address"] = "123 Main St."
peopleByID[1] = p1
peopleByID[2] = p2
if let person1 = peopleByID[1] {
println(person1.attributes["Surname"]!)
for attrKey in person1.attributes.keys {
println("Key: \(attrKey) value: \(person1.attributes[attrKey]!)")
}
}