Related
Let's assume I have a Set of Custom objects (i.e. Person), something like this:
struct Person: Hashable {
let name: String
let age: Int
}
let persons: Set<Person> = [Person(name: "Willy", age: 25), Person(name: "Jenny", age: 22)]
If I was to remove all items that have a specific name, I cannot find a Set method for this case.
In Array there is such a method called:
#inlinable public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows
But for some reason I cannot find an equivalent in Set.
So my solution was to perform a quick conversion for the removing procees something like this:
var arr = Array(persons)
arr.removeAll {
$0.name == "Willy"
}
persons = Set(arr)
Is there any direct solution In Set rather than using Array as a middle-man?
set = set.filter { $0.value != 1 }
You can use a higher order filter to create a new set that excludes objects with the name property "Willy":
let persons: Set<Person> = [
Person(name: "Willy", age: 25),
Person(name: "Jenny", age: 22)
]
let set = persons.filter{$0.name != "Willy"}
This question already has answers here:
Find Object with Property in Array
(5 answers)
Closed 3 years ago.
I have an array of such , such:
struct elephants {
var name: String
var age: Int
var location: String
}
I then have an array of structs
var elephantArray [elephants] = [elephant1, elephant2, elephant3]
What I want to be able to do is to search based on elephants.name - so if elephant2.name was "Bob" then when I searched the array based on the name "Bob" then it would return the index position of elephant2.
How would I go about doing this?
Here's simple func which will help you:
func searchElephantIndex(name: String, elephArray: [elephants]) -> Int? {
return elephArray.firstIndex { $0.name == name }
}
If the elephant with the requested name does not exist, the function will return nil.
firstIndex
Here the implementation
import UIKit
import Foundation
struct elephants {
var name: String
var age: Int
var location: String
}
let elephant1 = elephants.init(name: "A", age: 10, location: "BD")
let elephant2 = elephants.init(name: "B", age: 20, location: "UK")
let elephant3 = elephants.init(name: "C", age: 30, location: "IND")
var elephantArray : [elephants] = [elephant1, elephant2, elephant3]
if let i = elephantArray.firstIndex(where: { $0.name == "A" }) {
print("get the \(i)")
}
Here's a simple way of getting the index:
func getIndex(for name: String) {
for i in 0..<elephantArray.count {
if elephantArray[i].name == name {
print(i)
}
}
}
We iterate through the array and check if the name property of the element matches the one we are looking for, which is specified in the function parameters.
For example:
let elephant1 = Elephant(name: "A", age: 1)
let elephant2 = Elephant(name: "B", age: 2)
let elephant3 = Elephant(name: "C", age: 3)
let elephantArray = [elephant1, elephant2, elephant3]
getIndex(for: "B") // prints 1
The for loop compares each element's name property to "B" and prints out the index when it matches. This only works because i starts at 0 rather than 1, so that it is incremented parallel to the array index, which also starts at 0.
Hope this helps!
Could someone point me in the right direction on how to solve this issue, please?
I am creating table cells with the values from the structure below. The cells are created and the data is displayed in the cells by the time they were created which works fine.
The issue is some of the cells have the same name and I have an individual id for each cell from the struc Data but I need the user to know which one of the duplicates was created first within the duplicates. Kind of like a sub-number.
For example: 1:apple -1 , 2:pear -1, 3:apple -2
1(position in all the cell) - Apple (name of the cell) - 1 (value based on how many cells are named apple)
The func idName() I created tells us how many occurrences of a name happens but how could I break this down so the data would display like above?
struct Data {
var id: Int
var name: String
var color: String
var time: TimeInterval
var sessionId: Int
var userId: Int
}
func idName () {
let idElement = elements //another variable is used to grab the Array
var counts: [String: Int] = [:]
var idValue: Int
for id in idElement {
counts[id.name] = (counts[id.name] ?? 0) + 1
for (key, value) in counts {
print("\(key) occurs \(value) time(s)")
}
}
}
"I need the user to know which one of the duplicates was created first."
How a bout adding a date to each item when it is created?
var date: Date = Date()
So you can sort them...
myObjects = myObjects.sorted(by: {
$0.date.compare($1.date) == .orderedDescending
})
Another way is to add a UUID, this will give you a unique Identifier to reference:
var uuid: UUID = UUID()
var someObjectID = myObject.uuid.uuidString
Update:
When an Element (your data struct) is created, you should be checking your array of elements prior to adding one of the same name, if one of the same name exists then you can increment a counter not stored as a property of the struct.
You can use map and Dictionary(_:uniquingKeysWith:).
to return an array of mapped elements (an array of your data structs).
let mappedElements = elements.map($0.name, 1)
then, count duplicates and create a dictionary containing the number of matching items.
let counts = Dictionary(mappedElements, uniquingKeysWith: +)
this will result in ["apple": 3, "pear": 2, "peach": 1] etc.
I Just added nameCounter property in Data which will indicate the occurrence of particular name. Like this -
struct Data1 {
var id: Int
var name: String
var nameCOunter: Int? = 1
init(id: Int, name: String) {
self.id = id
self.name = name
}
static func addTestData() ->[Data1] {
var arr = [Data1]()
let model = Data1(id: 1, name: "apple")
let model1 = Data1(id: 2, name: "peer")
let model2 = Data1(id: 3, name: "apple")
let model3 = Data1(id: 4, name: "orange")
let model4 = Data1(id: 5, name: "grape")
let model5 = Data1(id: 6, name: "peer")
let model6 = Data1(id: 7, name: "apple")
arr = [model,model1,model2,model3,model4,model5,model6]
return arr
}
}
func idName() {
let idElement = Data1.addTestData()
var countedElement = [Data1]()
var nameArr = [String]()
for var dataModel in idElement {
nameArr.append(dataModel.name)
let count = nameArr.filter{$0 == dataModel.name}.count
dataModel.nameCOunter = count
countedElement.append(dataModel)
}
print(countedElement)
}
I have this code in my viewController
var myArray :Array<Data> = Array<Data>()
for i in 0..<mov.count {
myArray.append(Data(...))
}
class Data {
var value :CGFloat
var name :String=""
init({...})
}
My input of Data is as:
10.5 apple
20.0 lemon
15.2 apple
45
Once I loop through, I would like return a new array as:
sum(value) group by name
delete last row because no have name
ordered by value
Expected result based on input:
25.7 apple
20.0 lemon
and nothing else
I wrote many rows of code and it is too confused to post it. I'd find easier way, anyone has a idea about this?
First of all Data is reserved in Swift 3, the example uses a struct named Item.
struct Item {
let value : Float
let name : String
}
Create the data array with your given values
let dataArray = [Item(value:10.5, name:"apple"),
Item(value:20.0, name:"lemon"),
Item(value:15.2, name:"apple"),
Item(value:45, name:"")]
and an array for the result:
var resultArray = [Item]()
Now filter all names which are not empty and make a Set - each name occurs one once in the set:
let allKeys = Set<String>(dataArray.filter({!$0.name.isEmpty}).map{$0.name})
Iterate thru the keys, filter all items in dataArray with the same name, sum up the values and create a new Item with the total value:
for key in allKeys {
let sum = dataArray.filter({$0.name == key}).map({$0.value}).reduce(0, +)
resultArray.append(Item(value:sum, name:key))
}
Finally sort the result array by value desscending:
resultArray.sorted(by: {$0.value < $1.value})
---
Edit:
Introduced in Swift 4 there is a more efficient API to group arrays by a predicate, Dictionary(grouping:by:
var grouped = Dictionary(grouping: dataArray, by:{$0.name})
grouped.removeValue(forKey: "") // remove the items with the empty name
resultArray = grouped.keys.map { (key) -> Item in
let value = grouped[key]!
return Item(value: value.map{$0.value}.reduce(0.0, +), name: key)
}.sorted{$0.value < $1.value}
print(resultArray)
First of all, you should not name your class Data, since that's the name of a Foundation class. I've used a struct called MyData instead:
struct MyData {
let value: CGFloat
let name: String
}
let myArray: [MyData] = [MyData(value: 10.5, name: "apple"),
MyData(value: 20.0, name: "lemon"),
MyData(value: 15.2, name: "apple"),
MyData(value: 45, name: "")]
You can use a dictionary to add up the values associated with each name:
var myDictionary = [String: CGFloat]()
for dataItem in myArray {
if dataItem.name.isEmpty {
// ignore entries with empty names
continue
} else if let currentValue = myDictionary[dataItem.name] {
// we have seen this name before, add to its value
myDictionary[dataItem.name] = currentValue + dataItem.value
} else {
// we haven't seen this name, add it to the dictionary
myDictionary[dataItem.name] = dataItem.value
}
}
Then you can convert the dictionary back into an array of MyData objects, sort them and print them:
// turn the dictionary back into an array
var resultArray = myDictionary.map { MyData(value: $1, name: $0) }
// sort the array by value
resultArray.sort { $0.value < $1.value }
// print the sorted array
for dataItem in resultArray {
print("\(dataItem.value) \(dataItem.name)")
}
First change your data class, make string an optional and it becomes a bit easier to handle. So now if there is no name, it's nil. You can keep it as "" if you need to though with some slight changes below.:
class Thing {
let name: String?
let value: Double
init(name: String?, value: Double){
self.name = name
self.value = value
}
static func + (lhs: Thing, rhs: Thing) -> Thing? {
if rhs.name != lhs.name {
return nil
} else {
return Thing(name: lhs.name, value: lhs.value + rhs.value)
}
}
}
I gave it an operator so they can be added easily. It returns an optional so be careful when using it.
Then lets make a handy extension for arrays full of Things:
extension Array where Element: Thing {
func grouped() -> [Thing] {
var things = [String: Thing]()
for i in self {
if let name = i.name {
things[name] = (things[name] ?? Thing(name: name, value: 0)) + i
}
}
return things.map{$0.1}.sorted{$0.value > $1.value}
}
}
Give it a quick test:
let t1 = Thing(name: "a", value: 1)
let t2 = Thing(name: "b", value: 2)
let t3 = Thing(name: "a", value: 1)
let t4 = Thing(name: "c", value: 3)
let t5 = Thing(name: "b", value: 2)
let t6 = Thing(name: nil, value: 10)
let bb = [t1,t2,t3,t4,t5,t6]
let c = bb.grouped()
// ("b",4), ("c",3) , ("a",2)
Edit: added an example with nil for name, which is filtered out by the if let in the grouped() function
I have an array of Person's objects:
class Person {
let name:String
let position:Int
}
and the array is:
let myArray = [p1,p1,p3]
I want to map myArray to be a Dictionary of [position:name] the classic solution is:
var myDictionary = [Int:String]()
for person in myArray {
myDictionary[person.position] = person.name
}
is there any elegant way by Swift to do that with the functional approach map, flatMap... or other modern Swift style
Since Swift 4 you can do #Tj3n's approach more cleanly and efficiently using the into version of reduce It gets rid of the temporary dictionary and the return value so it is faster and easier to read.
Sample code setup:
struct Person {
let name: String
let position: Int
}
let myArray = [Person(name:"h", position: 0), Person(name:"b", position:4), Person(name:"c", position:2)]
Into parameter is passed empty dictionary of result type:
let myDict = myArray.reduce(into: [Int: String]()) {
$0[$1.position] = $1.name
}
Directly returns a dictionary of the type passed in into:
print(myDict) // [2: "c", 0: "h", 4: "b"]
Okay map is not a good example of this, because its just same as looping, you can use reduce instead, it took each of your object to combine and turn into single value:
let myDictionary = myArray.reduce([Int: String]()) { (dict, person) -> [Int: String] in
var dict = dict
dict[person.position] = person.name
return dict
}
//[2: "b", 3: "c", 1: "a"]
In Swift 4 or higher please use the below answer for clearer syntax.
Since Swift 4 you can do this very easily. There are two new initializers that build a dictionary from a sequence of tuples (pairs of key and value). If the keys are guaranteed to be unique, you can do the following:
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]
Dictionary(uniqueKeysWithValues: persons.map { ($0.position, $0.name) })
=> [1: "Franz", 2: "Heinz", 3: "Hans"]
This will fail with a runtime error if any key is duplicated. In that case you can use this version:
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 1)]
Dictionary(persons.map { ($0.position, $0.name) }) { _, last in last }
=> [1: "Hans", 2: "Heinz"]
This behaves as your for loop. If you don't want to "overwrite" values and stick to the first mapping, you can use this:
Dictionary(persons.map { ($0.position, $0.name) }) { first, _ in first }
=> [1: "Franz", 2: "Heinz"]
Swift 4.2 adds a third initializer that groups sequence elements into a dictionary. Dictionary keys are derived by a closure. Elements with the same key are put into an array in the same order as in the sequence. This allows you to achieve similar results as above. For example:
Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.last! }
=> [1: Person(name: "Hans", position: 1), 2: Person(name: "Heinz", position: 2)]
Dictionary(grouping: persons, by: { $0.position }).mapValues { $0.first! }
=> [1: Person(name: "Franz", position: 1), 2: Person(name: "Heinz", position: 2)]
How about a KeyPath based solution?
extension Array {
func dictionary<Key, Value>(withKey key: KeyPath<Element, Key>, value: KeyPath<Element, Value>) -> [Key: Value] {
reduce(into: [:]) { dictionary, element in
let key = element[keyPath: key]
let value = element[keyPath: value]
dictionary[key] = value
}
}
}
This is how you use it:
struct HTTPHeader {
let field: String, value: String
}
let headers = [
HTTPHeader(field: "Accept", value: "application/json"),
HTTPHeader(field: "User-Agent", value: "Safari")
]
headers.dictionary(withKey: \.field, value: \.value) // ["Accept": "application/json", "User-Agent": "Safari"]
You may write custom initializer for Dictionary type, for example from tuples:
extension Dictionary {
public init(keyValuePairs: [(Key, Value)]) {
self.init()
for pair in keyValuePairs {
self[pair.0] = pair.1
}
}
}
and then use map for your array of Person:
var myDictionary = Dictionary(keyValuePairs: myArray.map{($0.position, $0.name)})
This is what I have been using
struct Person {
let name:String
let position:Int
}
let persons = [Person(name: "Franz", position: 1),
Person(name: "Heinz", position: 2),
Person(name: "Hans", position: 3)]
var peopleByPosition = [Int: Person]()
persons.forEach{peopleByPosition[$0.position] = $0}
Would be nice if there was a way to combine the last 2 lines so that peopleByPosition could be a let.
We could make an extension to Array that does that!
extension Array {
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
var map = [T: Element]()
self.forEach{ map[block($0)] = $0 }
return map
}
}
Then we can just do
let peopleByPosition = persons.mapToDict(by: {$0.position})
You can use a reduce function. First I've created a designated initializer for Person class
class Person {
var name:String
var position:Int
init(_ n: String,_ p: Int) {
name = n
position = p
}
}
Later, I've initialized an Array of values
let myArray = [Person("Bill",1),
Person("Steve", 2),
Person("Woz", 3)]
And finally, the dictionary variable has the result:
let dictionary = myArray.reduce([Int: Person]()){
(total, person) in
var totalMutable = total
totalMutable.updateValue(person, forKey: total.count)
return totalMutable
}
Maybe something like this?
myArray.forEach({ myDictionary[$0.position] = $0.name })
extension Array {
func mapToDict<T>(by block: (Element) -> T ) -> [T: Element] where T: Hashable {
var map = [T: Element]()
self.forEach{ map[block($0)] = $0 }
return map
}
}
extension Array {
func toDictionary() -> [Int: Element] {
self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
}
}