get distinct elements in an array by object property - arrays

I have an array of object.
I want to get distinct elements in this array by comparing objects based on its name property
class Item {
var name: String
init(name: String) {
self.name = name
}
}
let items = [Item(name:"1"), Item(name:"2"), Item(name:"1"), Item(name:"1"),Item(name:"3"), Item(name:"4")]
result:
let items = [Item(name:"1"), Item(name:"2"),Item(name:"3"), Item(name:"4")]
how can I do this in swift?

Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(by: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(by(value)) {
set.insert(by(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
For your example you can do:
let uniqueBasedOnName = items.unique{$0.name}

Hope this will help you:
class Item:Equatable, Hashable {
var name: String
init(name: String) {
self.name = name
}
var hashValue: Int{
return name.hashValue
}
}
func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.name == rhs.name
}
let items = [Item(name:"1"), Item(name:"2"), Item(name:"1"), Item(name:"1"),Item(name:"3"), Item(name:"4")]
var uniqueArray = Array(Set(items))

In Swift you can use Equatable protocol to distinguish unique element in an Array of object.
struct Item:Equatable{
var name:String
var price:Double
init(name:String,price:Double) {
self.name = name
self.price = price
}
static func ==(lhs: Item, rhs: Item) -> Bool{
return lhs.name == rhs.name
}
}
class ViewController: UIViewController {
var books = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
items.append(Item(name: "Example 1", price: 250.0))
items.append(Item(name: "Example 2", price: 150.0))
items.append(Item(name: "Example 1", price: 150.0))
items.append(Item(name: "Example 1", price: 150.0))
items.append(Item(name: "Example 3", price: 100.0))
items.unique().forEach { (item) in
print(item.name)
}
}
}
extension Sequence where Iterator.Element: Equatable {
func unique() -> [Iterator.Element] {
return reduce([], { collection, element in collection.contains(element) ? collection : collection + [element] })
}
}

I used the sweet answer of #Ciprian Rarau and then realised I don't even need to add the elements in the first place if they are not unique. So I wrote a little extension for that (inspired by the answer).
extension Array {
public mutating func appendIfUnique<T: Hashable>(_ newElement: Element, check property: ((Element) -> (T))) {
for element in self {
if property(element) == property(newElement) { return }
}
append(newElement)
}
}
Append elements only if unique:
array.appendIfUnique(newElement, check: { $0.name })

Solution that uses keypaths instead of closures:
extension Sequence {
func uniqued<Type: Hashable>(by keyPath: KeyPath<Element, Type>) -> [Element] {
var set = Set<Type>()
return filter { set.insert($0[keyPath: keyPath]).inserted }
}
}
Example for struct Mock { var uuid: UUID; var num: Int }
let uuid = UUID()
let arr = [
Mock(uuid: uuid, num: 1),
Mock(uuid: uuid, num: 2),
Mock(uuid: UUID(), num: 3)
]
let unique = arr.uniqued(by: \.uuid)
unique array will contain first (num = 1) and last (num = 3) elements.

Related

Searching and Editing Values in Swift Array or Dictionary

I have a method which is supposed to return a Set of Strings. Here is a method description:
Returns: 10 product names containing the specified string.
If there are several products with the same name, producer's name is added to product's name in the format "<producer> - <product>",
otherwise returns simply "<product>".
Can't figure out how to check if there are duplicate names in the array and then edit them as required
What I've got so far:
struct Product {
let id: String; // unique identifier
let name: String;
let producer: String;
}
protocol Shop {
func addNewProduct(product: Product) -> Bool
func deleteProduct(id: String) -> Bool
func listProductsByName(searchString: String) -> Set<String>
func listProductsByProducer(searchString: String) -> [String]
}
class ShopImpl: Shop {
private var goodsInTheShopDictionary: [String: Product] = [:]
func addNewProduct(product: Product) -> Bool {
let result = goodsInTheShopDictionary[product.id] == nil
if result {
goodsInTheShopDictionary[product.id] = product
}
return result
}
func deleteProduct(id: String) -> Bool {
let result = goodsInTheShopDictionary[id] != nil
if result {
goodsInTheShopDictionary.removeValue(forKey: id)
}
return result
}
func listProductsByName(searchString: String) -> Set<String> {
var result = Set<String>()
let searchedItems = goodsInTheShopDictionary.filter{ $0.value.name.contains(searchString) }
let resultArray = searchedItems.map{ $0.value }
result = Set(searchedItems.map{ $0.value.name })
if result.count > 10 {
result.removeFirst()
}
return result
}
}
If you want to achieve this you would need to iterate over you resultArray and save producer and product into another array. On each iteration you would need to check if the array allready contains either the product name itself or an allready modified version.
A possible implementation would look like this:
var result = [(producer: String, product: String)]()
// iterate over the first 10 results
for item in resultArray.prefix(10){
if let index = result.firstIndex(where: { _ , product in
product == item.name
}){
// the result array allready contains the exact product name
// so we need to convert the name allready in the list
let oldProduct = (producer: result[index].producer, product: "\(result[index].producer) \(result[index].product)")
result[index] = oldProduct
// add the new one
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
}
else if !result.filter({ $0.product.components(separatedBy: " ").contains(item.name)}).isEmpty {
// if the result array allready contains a modified version of the name
result.append((producer: item.producer, product: "\(item.producer) \(item.name)"))
} else{
// if the result array does not contain the product yet
result.append((producer: item.producer, product: "\(item.name)"))
}
}
let productNames = result.map{ $0.product}
Please be aware: As you are using a [String: Product], which is a unsorted dictionary, to hold your values this will yield different results (if the resultArray collection is larger than 10) each time you search.
Tested with searchString = name1:
var goodsInTheShopDictionary: [String: Product] = Dictionary(uniqueKeysWithValues: (0...20).map { index in
("\(index)",Product(id: "", name: "name\(index)", producer: "producer\(index)"))
})
goodsInTheShopDictionary["100"] = Product(id: "11", name: "name1", producer: "producer11")
goodsInTheShopDictionary["101"] = Product(id: "12", name: "name1", producer: "producer12")
Result:
["name13", "producer12 name1", "name10", "name19", "producer11 name1",
"name17", "name14", "name18", "producer1 name1", "name16"]

Simple way to replace an item in an array if it exists, append it if it doesn't

Swift 4.2
I have multiple functions that replace an object or struct in an array if it exists, and if it does not exist, it adds it.
func updateFruit(_ fruit: Fruit)
{
if let idx = fruitArray.firstIndex(where: { $0.id == fruit.id })
{
fruitArray[idx] = fruit
}
else
{
fruitArray.append(fruit)
}
}
Obviously I could make this into extension on Array:
extension Array
{
mutating func replaceOrAppend(_ item: Element, whereFirstIndex predicate: (Element) -> Bool)
{
if let idx = self.firstIndex(where: predicate)
{
self[idx] = item
}
else
{
append(item)
}
}
}
However, is there a simpler, easier way of expressing this? Preferably using a closure or build-in function.
NOTE: current implementation does not allow using a set.
Given your use case, in which you're always checking $0.<prop> == newthing.<prop>, you can lift this a little more by adding:
mutating func replaceOrAppend<Value>(_ item: Element,
firstMatchingKeyPath keyPath: KeyPath<Element, Value>)
where Value: Equatable
{
let itemValue = item[keyPath: keyPath]
replaceOrAppend(item, whereFirstIndex: { $0[keyPath: keyPath] == itemValue })
}
You can then use it like:
struct Student {
let id: Int
let name: String
}
let alice0 = Student(id: 0, name: "alice")
let alice1 = Student(id: 1, name: "alice")
let bob = Student(id: 0, name: "bob")
var array = [alice0]
array.replaceOrAppend(alice1, firstMatchingKeyPath: \.name) // [alice1]
array.replaceOrAppend(bob, firstMatchingKeyPath: \.name) // [alice1, bob]
And of course if you do this a lot, you can keep lifting and lifting.
protocol Identifiable {
var id: Int { get }
}
extension Student: Identifiable {}
extension Array where Element: Identifiable {
mutating func replaceOrAppendFirstMatchingID(_ item: Element)
{
replaceOrAppend(item, firstMatchingKeyPath: \.id)
}
}
array.replaceOrAppendFirstMatchingID(alice0) // [alice1, alice0]
I can suggest to create protocol Replacable with replaceValue that will represent identifier which we can use to enumerate thru objects.
protocol Replacable {
var replaceValue: Int { get }
}
now we can create extension to Array, but now we can drop predicate from example code like this
extension Array where Element: Replacable {
mutating func replaceOrAppend(_ item: Element) {
if let idx = self.firstIndex(where: { $0.replaceValue == item.replaceValue }) {
self[idx] = item
}
else {
append(item)
}
}
}
Since Set is not ordered collection, we can simply remove object if set contains it and insert new value
extension Set where Element: Replacable {
mutating func replaceOrAppend(_ item: Element) {
if let existItem = self.first(where: { $0.replaceValue == item.replaceValue }) {
self.remove(existItem)
}
self.insert(item)
}
}
Assuming your Types are Equatable, this is a generic extension:
extension RangeReplaceableCollection where Element: Equatable {
mutating func addOrReplace(_ element: Element) {
if let index = self.firstIndex(of: element) {
self.replaceSubrange(index...index, with: [element])
}
else {
self.append(element)
}
}
}
Though, keep in mind my (and your) function will only replace one of matching items.
Full Working playground test:

extend an Array of Dictionary<String, Any> Swift 3

var dicts = [["key1": "value1", "key2": "value2"]]
dicts.values(of: "key1") // prints - value1
I am working on a project where I want to store the array of dictionary and then fetch the data from there on condition if array of dictionary contains the particular value.
Swift 3.0
You can try this way.
var dicts:[[String:Any]] = []
var check:Bool = false
dicts = [["search_date": "17/03/17", "search_title": ""],["search_date": "17/02/19", "search_title": "parth"],["search_date": "20/02/19", "search_title": "roy"]]
for item in dicts {
if let title = item["search_title"] as? String {
if title == "parth" {
check = true
break
}else {
check = false
}
}
else {
check = false
}
}
print(check)
We can Use Model to solve the Problem
class Person: NSObject, NSCoding {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.age = decoder.decodeInteger(forKey: "age")
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(age, forKey: "age")
}
}
Class
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// setting a value for a key
let newPerson = Person(name: "Joe", age: 10)
var people = [Person]()
people.append(newPerson)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)
UserDefaults.standard.set(encodedData, forKey: "people")
// retrieving a value for a key
if let data = UserDefaults.standard.data(forKey: "people"),
let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Person] {
myPeopleList.forEach({print( $0.name, $0.age)}) // Joe 10
} else {
print("There is an issue")
}
}
}
All Thanks to Leo Dabus
[Link] (https://stackoverflow.com/a/37983027/3706845)
Your question is very vague. But what I understood is that you want to filter the array of dictionaries so it only contains dictionaries that have a certain value, and this can be done this way:
let filteredDicts = dicts.filter({ $0.values.contains("value2") })

Formatting an array like a shopping list

I have an array formatted like this:
["Trousers : 15.50", "Trousers : 15.50", "Jumper : 12.99", "Shoes: 50.00"]
I would like to format it like this:
["2x Trousers : 31.00", "1x Jumper : 12.99", "1x Shoes: 50.00"]
I tried formatting using this:
var counts: [String:Int] = [:]
var shoppingList = ["Trousers : 15.50", "Trousers : 15.50", "Jumper : 12.99", "Shoes: 50.00"]
var formattedShoppingList = [String]()
for item in shoppingList {
counts[item] = (counts[item] ?? 0) + 1
}
for (key, value) in counts {
let display:String = String(value) + "x " + key
formattedShoppingList.append(display)
}
But I get this
["2x Trousers : 15.50", "1x Jumper : 12.99", "1x Shoes: 50.00"]
If I use a dictionary I cannot have duplicates. How shall I proceed with this?
I would make a struct to represent Item name/price pairs (and perhaps other data in the future, like quantity in stock, for example).
struct Item: Hashable {
let name: String
let price: Double
public var hashValue: Int { return name.hashValue ^ price.hashValue }
public static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.name == rhs.name && rhs.price == rhs.price
}
}
let shoppingList = [
Item(name: "Trousers", price: 15.50),
Item(name: "Trousers", price: 15.50),
Item(name: "Jumper", price: 12.99),
Item(name: "Shoes", price: 50),
]
let counts = shoppingList.reduce([Item: Int]()){counts, item in
var counts = counts
counts[item] = (counts[item] ?? 0) + 1
return counts
}
let formattedShoppingList = counts.map{ item, count in "\(count)x \(item.name): £\(item.price)" }
print(formattedShoppingList)
["2x Trousers: £15.5", "1x Shoes: £50.0", "1x Jumper: £12.99"]
You could use this struct, which accepts your strings as parameter for the constructor:
struct ShoppingItem: Hashable {
let name: String
let price: NSDecimalNumber
//Expects a String in the form "ElementName : Price"
init?(description: String) {
let splitDescription = description.components(separatedBy: ":")
guard splitDescription.count == 2 else { return nil }
name = splitDescription[0].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
price = NSDecimalNumber(string: splitDescription[1])
}
public var hashValue: Int {
return "\(name)\(price)".hashValue
}
}
func ==(lhs: ShoppingItem, rhs: ShoppingItem) -> Bool {
return lhs.hashValue == rhs.hashValue
}
With it, you could convert your shopping list, to a list of shopping items like this (take into account, that this discards items it can't transform, you could check for nil items to make sure all could be converted):
var shoppingItems = shoppingList.flatMap(ShoppingItem.init(description:))
and then, you just do what you did before, only multiplying the price at the end:
var counts = [ShoppingItem: Int]()
for item in shoppingItems {
counts[item] = (counts[item] ?? 0) + 1
}
for (key, value) in counts {
let multipliedPrice = key.price.multiplying(by: NSDecimalNumber(value: value))
let display = "\(value)x \(key.name) : \(multipliedPrice)"
formattedShoppingList.append(display)
}
You don't need a struct or class for a simple pair; use an array of tuples:
var shoppingList = [("Trousers", 15.50), ("Trousers", 15.50), ("Jumper", 12.99), ("Shoes", 50.00)]
for (name, price) in shoppingList {
print(name, price)
}

List in dictionary as a template definition

I'm using arrays which are stored in a dictionray for fast access. Because I need this logic for different data types, I like to define it as a template, but I don't have any idea how to pass types. In my own description it should look like this:
struct KeyList {
let key : MyType1
var list = [MyType2]()
init(key : MyType1, value : MyType2) {
self.key = key
self.list.append(value)
}
}
var dicList = [String: KeyList]()
value = ...of MyType2
key = ... of MyType1
if dicList[key] == nil {
// new entry
dicList[key] = KeyList(key: key, value: value)
}
else {
// add it to existing list
dicList[key]!.list.append(value)
}
}
But I want to use Swift 3. Any idea, if this is possible?
You'll need a couple of things:
generics
encapsulation
Snippet
Here's an example
struct Container<Key, Value> where Key: Hashable {
private var dict: [Key:[Value]] = [:]
func list(by key: Key) -> [Value]? {
return dict[key]
}
mutating func add(value: Value, to key: Key) {
dict[key] = (dict[key] ?? []) + [value]
}
}
Usage
Now you can create a Container specifying the Key and the Value types
var container = Container<String, Int>()
container.add(value: 1, to: "a")
container.add(value: 2, to: "a")
container.list(by: "a") // [1, 2]
Update
You asked in the comments how to implement a remove functionality. In this case the Value needs to be Equatable. Here's the code
struct Container<Key, Value> where Key: Hashable, Value: Equatable {
private var dict: [Key:[Value]] = [:]
func list(by key: Key) -> [Value]? {
return dict[key]
}
mutating func add(value: Value, to key: Key) {
dict[key] = (dict[key] ?? []) + [value]
}
mutating func remove(value: Value, from key: Key) {
guard var list = dict[key] else { return }
guard let index = list.index(of: value) else { return }
list.remove(at: index)
dict[key] = list
}
}

Resources