This question already has answers here:
How to group by the elements of an array in Swift
(16 answers)
Closed 6 years ago.
I want to create array of unique elements by specific property.
Ex:
I have array of objects (Person) :
struct Person {
var name: String?
var secondName: String?
init (name: String, secondName: String) {
self.name = name
self.secondName = secondName
}
}
let person1 = Person(name: "name1", secondName: "secondName1")
let person2 = Person(name: "name2", secondName: "secondName2")
let person3 = Person(name: "name1", secondName: "secondName3")
let personsArray = [person1, person2, person3]
I want to get new array, that will contain person objects with unique name
something like this $0.name == $1.name
What is the best way to achieve that ?
Result should be arrays of objects with unique name param = [[person1, person3], [person2]]
This is my personal interpretation of your question
Given an array of Person(s) you want in output several dictionaries where the key is the name of a person and the value is a list of persons with that name.
Here's the code
let dict = persons.reduce([String:[Person]]()) { (dict, person) -> [String:[Person]] in
var dict = dict
dict[person.name] = (dict[person.name] ?? []) + [person]
return dict
}
One approach: You could add them one by one to a dictionary where "name" is the key (consider using lowercase for it), and "array of Persons" is the value. When done, the keys array will have all your unique "name" values, and each key's value will be the array of Persons with that "name". You could then "trim" your dictionary by removing any key with an array that has a count less than 2.
Alternative: Sort the array by "name", then you can easily remove any that don't appear twice (if an element doesn't match one of it's neighbors, then remove it).
Related
I m mapping data that come from service with using dictionary [String: String]. I collect them dictionary array. For example, if their parent ids are the same, I want to add their values by array value.
["ParentId": "1","Value": "["Giyim","Aksesuar","Ayakkabı"]"]
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
Here is my code and its output.
struct Categories {
let parentId: String
let values: [String]
}
for result in results {
if result?.parentCategoryId != "" {
for docId in self.docIds {
if result?.parentCategoryId == docId {
//print(result?.name)
var values = [String]()
values.append(result?.name ?? "")
self.newCat.append(Categories(parentId: docId, values: values))
}
}
}
}
Problem
As far as I understand from the description you want to map some service data structure to a dictionary where key is parentId and value is an array of some items referred to parentId.
I believe your problem comes from a misunderstanding of the concept of dictionary as a data structure.
[String: String] is dictionary where keys and their associated values are of String type. For example:
["firstKey": "firsthValue", "secondKey": "secondValue", ...]
That means you cannot store associated values of String and Array types in the same dictionary, as you already told the compiler you would like to store only strings.
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
This is because key-value pairs are stored in the dictionary without order. This is how dictionaries work :) I'd strongly recommend reading some short official materials to get used to them.
New Swift 5.4 version has a new OrderedDictionary data structure, where keys are ordered, but there is absolutely 100500% no reason to use it for your problem*
Possible solutions
In your case i would suggest either use some struct:
struct SomeData {
let parentID: Int
var values: [String]
}
var storage: [SomeData] // alternative to emptyDic
// Filling storage with data from service
for result in inputData {
// search for SomeData with required id, add value
// OR create SomeData if there is no such id in array yet
}
OR [personally this appeals to me more]
Store data in [String: [String]] dictionary, where the key is parentID and the associated value is an array of values.
The algorithm of filling this dictionary is pretty the same:
You add new key-value pair for every new parentID
You append new values for parentIDs that are already in the dictionary.
Using the struct approach, you could do something like this (you'll need to adapt it to your code, but that should be straightforward):
struct Categories {
let parentId: String
var values: [String] //notice this needs to be a var, not a let
}
func addItem(categories : inout [Categories], docId: String, name: String) {
if let index = categories.firstIndex(where: { $0.parentId == docId }) {
categories[index].values.append(name)
} else {
categories.append(Categories(parentId: docId, values: [name]))
}
}
func addValues() {
var categories = [Categories]()
addItem(categories: &categories, docId: "4", name: "Test1")
addItem(categories: &categories, docId: "1", name: "Test")
addItem(categories: &categories, docId: "4", name: "Test2")
addItem(categories: &categories, docId: "4", name: "Test3")
print(categories)
//in your code, it'll look more like:
// addItem(categories: &self.newCat, docId: docId, name: result?.name ?? "")
}
Which yields this:
[
StackOverflowPlayground.Categories(parentId: "4", values: ["Test1", "Test2", "Test3"]),
StackOverflowPlayground.Categories(parentId: "1", values: ["Test"])
]
I still wonder whether you maybe just want a Dictionary that is keyed by the parentId, but not knowing your use case, it's hard to say.
lets say you have the classes:
class Artwork {
var title = ""
var location =""
var author = ""
}
class GroupedArtworks {
var location = ""
var artworks = [Artworks]()
}
then you have an variable that contains several objects of the class "Artwork":
var arts = [artwork1, artwork2...]
How would I group the objects of the class "Artwork" by "location"?
I would like to end up with another variable containing objects of the class "GroupedArtworks"
and for each object, in the attribute "artworks" have all the objects that have the same "location"
so something like:
var orderedArtworks = [groupedartworks1, groupedartworks2...]
No doubt for loops are involved here.
The solution is super easy with Swift's Dictionary init(grouping:by:)
let artworks: [Artwork] = []
// dictionary type will be [String: [Artwork]]
let dictionary = Dictionary(grouping: artworks, by: \.location)
// now we can easy iterate over (key: String, value: [Artwork]) pairs
let groups: [GroupedArtworks] = dictionary.map { location, artworks in
GroupedArtworks(location: location, artworks: artworks)
}
// or simply
let groups = dictionary.map(GroupedArtworks.init)
// Swift will infer the types of dictionary and the init
but you will need to add this init to your GroupedArtworks
class GroupedArtworks {
let location: String
let artworks: [Artwork]
init(location: String, artworks: [Artwork]) {
self.location = location
self.artworks = artworks
}
}
Documentation
As someone correctly pointed out in the comments, since dicts are unordered collections, your array of GroupedArtworks will be unordered one as-well. But this should not be a problem since you can easily sort it by lets say location.
let groups = dictionary.map(GroupedArtworks.init).sorted(by: \.location)
Hey i have array of objects, my goal is to split them into several arrays by common property value for example:
struct Person {
let name: String
let city: String
}
let persons: [Person] = [Person(name: "John", city: "San Francisco"),
Person(name: "Tim", city: "San Francisco"),
Person(name: "Bob", city: "Atlanta")]
my goal is to get arrays that contain only persons from same city.
In that example result will be two arrays first contain John and Tim's objects, and 2nd contain only Bob's object.
Thanks
If I got you correctly, you should use a dictionary and group items by the city property:
let grouped = Dictionary(grouping: persons) { $0.city }
If you don't need the keys, then you can map them on values like:
let groupedWOKeys = grouped.map { $1 }
Pretty new so apologies if this is noobish.
I'm trying to get the key and the value from an associative array to print out as the label text in a cell.
At the moment I have the array:
let users = ["John","James","Liam"]
and I am getting the value for the cell text like so:
cell.textLabel!.text = self.users[indexPath.row]
Which will give me rows of the names in. I am struggling when I add in user ages like so
lets users = ["John" : 36, "James": 12, "Liam": 30]
I get this error:
Ambiguous reference to member 'subscript'
How do I get the cell text to display both the name and age?
If you really need to refer to the data via an array, there are at least two ways you can easily accomplish this. Create a Person object like in the comments, or you can quickly use a tuple structure. The following code works in Xcode Playground (Swift 3, 4).
// - First Approach
struct Person {
var name: String
var age: Int
}
let users = [Person(name: "John", age: 36), Person(name: "James", age: 12), Person(name: "Liam", age: 30)]
// say indexPath.row = 0
print("name: \(users[0].name), age: \(users[0].age)") // name: John, age: 36
// - Second Approach
let usersTuple = [("John", 36), ("James", 12), ("Liam", 30)]
print("name: \(usersTuple[0].0), age: \(usersTuple[0].1)") // name: John, age: 36
You need to define a dictionary and with ages and names and in each cell get from the dictionary the age with the name as key, or you can define a model with name and age like Person and put and use as your datasource Array, which I think is better
class Person{
var age : Int = 0
var name : String = ""
init(name:String,age:Int){
self.name = name
self.age = age
}
}
declare an array of Person in your ViewController
var users = [Person(name: "John", age: 36),Person(name: "James", age: 12),Person(name: "Liam", age: 30)]
In the cellForRow method
let currUser = self.users[indexPath.row]
cell.textLabel!.text = currUser.name + "Age: \(currUser.age)"
If you're coming from a PHP style background, it can be confusing that dictionaries look like associated arrays but there's no way to reference them by index like in PHP. You'll need to reference by the key (the string) or convert them into something which you can retrieve later.
Luckily in Swift you can just use something called a tuple, which looks like this:
let user = ("John", 36)
And you reference the values like this:
let name = user.0
let age = user.1
You can store tuples in arrays, so you can do this:
let users = [("John", 36), ("James", 12)]
let johnAge = users[0].1
You can also give the tuple named parameters but by this point you might as well create a struct. Here's what it looks like anyway:
let user = (name: "John", age: 36)
let johnAge = user.age
Another neat trick with tuples is that if you typealias it you can create it the same way you would a struct, without having to device the properties individually, like this:
typealias User = (name: String, age: Int)
let john = User(name: "John", age: 36)
By this point it's just a case of saving a few lines of code but it really depends on how lightweight you need the user object to be. You wouldn't be able to make it conform to anything in the future, which is why a struct can be a better option. If all you ever need is a lightweight object, tuples are great.
I'm new to Swift and taking a class to learn iOS programming. I find myself stumped on how to search in an array of dictionaries for a string value and dump the string value into an array. This is taken from my Xcode playground.
I'm trying to figure out how to:
1) search through an array of dictionaries
2) dump the results of the search into an array (which I've created)
These are the character dictionaries.
let worf = [
"name": "Worf",
"rank": "lieutenant",
"information": "son of Mogh, slayer of Gowron",
"favorite drink": "prune juice",
"quote" : "Today is a good day to die."]
let picard = [
"name": "Jean-Luc Picard",
"rank": "captain",
"information": "Captain of the USS Enterprise",
"favorite drink": "tea, Earl Grey, hot"]
This is an array of the character dictionaries listed above.
let characters = [worf, picard]
This is the function I'm trying to write.
func favoriteDrinksArrayForCharacters(characters:Array<Dictionary<String, String>>) -> Array<String> {
// create an array of Strings to dump in favorite drink strings
var favoriteDrinkArray = [String]()
for character in characters {
// look up favorite drink
// add favorite drink to favoriteDrinkArray
}
return favoriteDrinkArray
}
let favoriteDrinks = favoriteDrinksArrayForCharacters(characters)
favoriteDrinks
I would be grateful for any assistance on how to move forward on this. I've dug around for examples, but I'm coming up short finding one that's applicable to what I'm trying to do here.
Inside the loop, you need to fetch the "favorite drink" entry from the dictionary, and append it to the array:
for character in characters {
if let drink = character["favorite drink"] {
favoriteDrinkArray.append(drink)
}
}
Note, the if let drink = guards against the possibility there is no such entry in the array – if there isn't, you get a nil back, and the if is checking for that, only adding the entry if it's not nil.
You might sometimes see people skip the if let part and instead just write let drink = character["favorite drink"]!, with an exclamation mark on the end. Do not do this. This is known as "force unwrapping" an optional, and if there is ever not a valid value returned from the dictionary, your program will crash.
The behavior with the first example is, if there is no drink you don't add it to the array. But this might not be what you want since you may be expecting a 1-to-1 correspondence between entries in the character array and entries in the drinks array.
If that's the case, and you perhaps want an empty string, you could do this instead:
func favoriteDrinksArrayForCharacters(characters: [[String:String]]) -> [String] {
return characters.map { character in
character["favorite drink"] ?? ""
}
}
The .map means: run through every entry in characters, and put the result of running this expression in a new array (which you then return).
The ?? means: if you get back a nil from the left-hand side, replace it with the value on the right-hand side.
Airspeed Velocity's answer is very comprehensive and provides a solution that works. A more compact way of achieving the same result is using the filter and map methods of swift arrays:
func favoriteDrinksArrayForCharacters(characters:Array<Dictionary<String, String>>) -> Array<String> {
// create an array of Strings to dump in favorite drink strings
return characters.filter { $0["favorite drink"] != nil }.map { $0["favorite drink"]! }
}
The filter takes a closure returning a boolean, which states whether an element must be included or not - in our case, it checks for the existence of an element for key "favorite drink". This method returns the array of dictionaries satisfying that condition.
The second step uses the map method to transform each dictionary into the value corresponding to the "favorite drink" key - taking into account that a dictionary lookup always returns an optional (to account for missing key), and that the filter has already excluded all dictionaries not having a value for that key, it's safe to apply the forced unwrapping operator ! to return a non optional string.
The combined result is an array of strings - copied from my playground:
["prune juice", "tea, Earl Grey, hot"]
let drinks = characters.map({$0["favorite drink"]}) // [Optional("prune juice"), Optional("tea, Earl Grey, hot")]
or
let drinks = characters.filter({$0["favorite drink"] != nil}).map({$0["favorite drink"]!}) // [prune juice, tea, Earl Grey, hot]
It may help you
var customerNameDict = ["firstName":"karthi","LastName":"alagu","MiddleName":"prabhu"];
var clientNameDict = ["firstName":"Selva","LastName":"kumar","MiddleName":"m"];
var employeeNameDict = ["firstName":"karthi","LastName":"prabhu","MiddleName":"kp"];
var attributeValue = "karthi";
var arrNames:Array = [customerNameDict,clientNameDict,employeeNameDict];
var namePredicate = NSPredicate(format: "firstName like %#",attributeValue);
let filteredArray = arrNames.filter { namePredicate.evaluateWithObject($0) };
println("names = ,\(filteredArray)");
Use the following code to search from NSArray of dictionaries whose keys are ID and Name.
var txtVal:NSString
let path = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist")
var list = NSArray(contentsOfFile: path!) as [[String:String]]
var namePredicate = NSPredicate(format: "ID like %#", String(forId));
let filteredArray = list.filter { namePredicate!.evaluateWithObject($0) };
if filteredArray.count != 0
{
let value = filteredArray[0] as NSDictionary
txtVal = value.objectForKey("Name") as String
}
i have array of customer ,each customer having name,phone number and other stubs .so i used the below code to search by phone number in the array of dictionary in search bar
for index in self.customerArray
{
var string = index.valueForKey("phone")
if let phoneNumber = index.valueForKey("phone") as? String {
string = phoneNumber
}
else
{
string = ""
}
if string!.localizedCaseInsensitiveContainsString(searchText) {
filtered.addObject(index)
searchActive = true;
}
}