Swift: Group duplicate array elements into new array - arrays

I have an Order object as follows:
struct Order {
let id: Int
let item: String
let price: Int
}
These are grouped in an, however sometimes there are duplicate IDs and I need these to be grouped into their own array of Order duplicate objects. So essentially at the moment I have [Order] and I need to convert this into [[Order]] where all duplicates will be grouped together, and where there are no duplicates they will simply be on their own in an array.
So for example, imagine I have the following array of orders:
[Order(id: 123, item: "Test item1", price: 1)
Order(id: 345, item: "Test item2", price: 1)
Order(id: 678, item: "Test item3", price: 1)
Order(id: 123, item: "Test item1", price: 1)]
This needs to be converted to:
[[Order(id: 123, item: "Test item1", price: 1), Order(id: 123, item: "Test item1", price: 1)],
[Order(id: 345, item: "Test item2", price: 1)],
[Order(id: 678, item: "Test item3", price: 1)]]
I have been playing around with a dictionary and have so far come up with the following:
let dictionary = Dictionary(grouping: orders, by: { (element: Order) in
return element.id
})
This returns the following type:
[Int : [Order]]
Which is close, but I don't really want them in a dictionary like this. I just need to be able to get an [[Order]] array that I can loop through for use in my UI.

All you have to do is to use .values on the dictionary you have created and you have your array,
let values = Dictionary(grouping: orders, by: \.id).values

This somewhat should do the trick, not the best solution but should be enough to get it working.
var orders = [[Order]]()
for order in orders {
if let index = orders.firstIndex(where: { $0.id == order.id }) {
// We have this order id already let's append another duplicate
orders[index].append(order)
} else {
// We don't have this order id, create a new array
orders.append([order])
}
}

Related

Filter array by matching id properties

I feel like this has to be answered some where, but I have been searching for a few days with no luck. I have an example below. I have an array of users and I need to filter them down to the ones that have a matching ID property, I know the code below doesn't compile.. would be very grateful for any help with this.
struct User {
var id: Int
var name: String
}
let userArray = [
User(id: 1, name: "A"),
User(id: 2, name: "B"),
User(id: 1, name: "C"),
User(id: 3, name: "D"),
]
let newArray = userArray.filter({ $0.id == $1.id })
// This is what i want to achieve
// newArray = [User(id: 1, name: "A"), User(id: 1, name: "C")]
In the actual project, the id is dynamically returned. So I just need to be able to check for what is matching, without knowing what the id will actually be.
Your approach won't work as filter only takes one dynamic parameter and only processes one item at a time. Therefore it can't match two separate array entries.
You example also doesn't specify how you want to handle the situation where you have multiples of different User.id. This answer assumes you want to be able to separate them into separate arrays.
Dictionary has a handy initialiser that will do the bulk of the work for you and group on a defined property. Grouping on id will give you a dictionary where the key is the id and the values an array of matching User records. You can then filter the dictionary to get a dictionary where there are multiple users for any id.
let multiples = Dictionary(grouping: userArray, by: \.id).filter{$0.value.count > 1}
Using your data you will end up with a dictionary of:
[1: [User(id: 1, name: "A"), User(id: 1, name: "C")] ]
Your condition in filter does not compare to a given id value. Below is one added called, which I call matchingId:
struct User {
var id: Int
var name: String
}
let userArray = [
User(id: 1, name: "A"),
User(id: 2, name: "B"),
User(id: 1, name: "C"),
User(id: 3, name: "D"),
]
let matchingId = 1 // or: let matchingId = someFunctionCallReturningAnId()
let result = userArray.filter { $0.id == matchingId }
print(result)

How to find an item in an object of array

I have a list of IDs. Also I have an object that has arrays of datas like the below structure.
[
foods(
foodId: 345,
category: 10,
tools: [10],
name: "food name 1"
),
foods(
foodId: 191,
category: 4,
tools: [2],
name: "food name 2"
),
]
In my list I have list [345, 191]
I want to have a mechanism to access the information of the object when I provide a foodId.
I made it work with one inner and one outer loop. But I was wondering if there is an easier way to do it:
ForEach(foodDetails, id: \.self){ item in
ForEach(self.foods.datas){ ex in
if(ex.foodId == item){
Text(ex.name)
}
}
Any idea how to make it work?
Thanks in advance
you can do simply by getting first element where id match
let result = foodDetails.first(where: {$0.foodId == id})
if let food = result {
print(food.name ?? "") //if name is optional
print(food.foodId)
print(food.category)
}
result you got is that foods? optional struct which have this id

Fetch data from an array with elements of second array

I have one array "users" with all user data and and second array "userIds" having user's id. I have to fetch User from "users" array using "userIds" array
struct User {
let name: String
let id: Int
}
let users: [User] = [User(name: "a", id: 1),
User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 4),
User(name: "d", id: 5)]
let userIds = [2,3,2,5]
result array that I want is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "b", id: 2),
User(name: "d", id: 5)]
so it can have duplicate data according to the data in "userIds".
Now I tried using Higher order function filter:
let result = users.filter { (user) -> Bool in
return userIds.contains(user.id)
}
but this removes the duplicate data and the output is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 5)]
One approach that I tried is using for loop :
var result = [User]()
for i in userIds {
result.append(users.filter({ $0.id == i }).first!)
}
which gives the desired output but if there is a better approach please suggest.
You can solve this using first(where:) to search through users:
let result = userIds.compactMap { desiredDataValue in
users.first(where: { $0.id == desiredDataValue })
}
But if you're doing this a lot, it would probably speed things up if you built a datastructure that allows for fast lookup by the "id" value. You should compare the performance for yourself, and see if you do this enough/frequently enough for it to be worthwhile:
let dictsByData = Dictionary(uniqueKeysWithValues:
users
.lazy
.map { dict in
(key: dict.id, value: dict)
}
)
let result = userIds.compactMap { desiredDataValue in dictsByData[desiredDataValue]! }
result.forEach { print($0) }
Well after digging few more and with the help of this blog:
https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
I tried doing like this:
let results = userIds.compactMap { (int) -> User? in
var matchedUser: User?
if users.contains(where: { (user) -> Bool in
if user.id == int {
matchedUser = user
}
return user.id == int
}) {
return matchedUser
}
return nil
}
and in playground I checked the count the code was executed :
and it seems like the count is less comparing to "for" loop.

Creating a body for Alamofire POST request Swift 4

I need help with creating a custom body when sending POST request with Alamofire.
I'm sending to API products. There are two types of products. First type has just quantity, second one - different quantities(size_id) and quantities that match each size_id.
Final body should look like:
"factory_id": "1"
"order_products[0][product_id]": "1"
"order_products[0][size_id]": "2"
"order_products[0][quantity]": "10"
"order_products[1][product_id]": "1"
"order_products[1][size_id]": "3"
"order_products[1][quantity]": "10"
"order_products[1][product_id]": "2"
"order_products[1][size_id]": "2"
"order_products[1][quantity]": "10"
"order_products[2][product_id]": "3"
"order_products[2][quantity]": "10"
Here's what I achieved so far:
var createOrderBody = [String: Any]()
let productIds = ["1", "2", "3"]
var body = [String: Any]()
var quantity = ["1" : "10", "2": "10"]
var noIdQuantity = ["10"]
var finalBody = [String: Any]()
func formBody(products: String, factoryId: String, productId: String, size_id: String, quantity: String) -> [String: Any] {
createOrderBody["factory_id"] = factoryId
createOrderBody["order_products[\(products)][product_id]"] = productId
createOrderBody["order_products[\(products)][size_id]"] = size_id
createOrderBody["order_products[\(products)][quantity]"] = quantity
return createOrderBody
}
for (index, value) in productIds.enumerated() {
for (id, size) in quantity {
print(id)
print(size)
body = formBody(products: String(index), factoryId: "1", productId: String(value), size_id: id, quantity: size)
print("Body quantity - ", body)
}
}
And the result I have is:
"factory_id": "1",
"order_products[0][product_id]": "1"
"order_products[0][size_id]": "2",
"order_products[0][quantity]": "10",
"order_products[1][product_id]": "2",
"order_products[1][size_id]": "2",
"order_products[1][quantity]": "10",
"order_products[2][product_id]": "3",
"order_products[2][size_id]": "2",
"order_products[2][quantity]": "10",
As you can see, I have almost achieved desired result, but the problem is that it is adding only last element of quantity dictionary and omits other values. Also, I don't know how to add quantity to the product, that doesn't have size_id
Also, I know that it is not a good practice to place for in loop inside other for in loop but I'm new to development and this is the best idea that I have came up with.
Would be grateful for any help with this issue, as I've been battling with it almost for a week right now.
Many thanks and have a nice weekends!
After studying and searching on the Internet, here's a solution to the issue we have here. For reference, original post here - Original Post
Assuming that every product has to hold it's own quantity:
We can define a struct like this:
struct Product {
let id: String
let quantities: [(sizeId: String, quantity: Int)]?
let noIdQuantity: Int?
init(id: String, quantities: [(sizeId: String, quantity: Int)]) {
self.id = id
self.quantities = quantities
self.noIdQuantity = nil
}
init(id: String, quantity: Int) {
self.id = id
self.quantities = nil
self.noIdQuantity = quantity
}
}
With the struct above, we just need only one input variable and one output variable:
// Define input `product with id` as an Array of `Product`
let products = [
Product(id: "1", quantities: [("1", 10), ("2", 10)]),
Product(id: "2", quantities: [("1", 10), ("2", 10)]),
Product(id: "3", quantities: [("1", 15), ("2", 20)]),
Product(id: "4", quantity: 10),
]
// Output dictionary
var body = [String: Any]()
To make entries for a single Product into a Dictionary:
extension Product {
func formBody(_ index: inout Int, into body: inout [String: Any]) {
if let quantities = self.quantities {
for (sizeId, quantity) in quantities {
body["order_products[\(index)][product_id]"] = self.id
body["order_products[\(index)][size_id]"] = sizeId
body["order_products[\(index)][quantity]"] = quantity
index += 1
}
}
if let quantity = self.noIdQuantity {
body["order_products[\(index)][product_id]"] = self.id
body["order_products[\(index)][quantity]"] = quantity
index += 1
}
}
}
And use them as follows:
var index = 0
body["factory_id"] = "1"
for product in products {
product.formBody(&index, into: &body)
}
print("Body quantity - ", body)
body.sorted {$0.key < $1.key}.forEach{print("\($0.key)=\($0.value)")} //For showing readable result, not for Alammofire body
So, the final result would be:
factory_id=1
order_products[0][product_id]=1
order_products[0][quantity]=10
order_products[0][size_id]=1
order_products[1][product_id]=1
order_products[1][quantity]=10
order_products[1][size_id]=2
order_products[2][product_id]=2
order_products[2][quantity]=10
order_products[2][size_id]=1
order_products[3][product_id]=2
order_products[3][quantity]=10
order_products[3][size_id]=2
order_products[4][product_id]=3
order_products[4][quantity]=15
order_products[4][size_id]=1
order_products[5][product_id]=3
order_products[5][quantity]=20
order_products[5][size_id]=2
order_products[6][product_id]=4
order_products[6][quantity]=10
Hope this helps someone, who has same complex structure or similar.

Is there a better way to create an index of an array?

I made up a method to add a variable rankto an array of structs.
The array friutsArrayis created like in the function makeFriuts(). After that, the data gets sorted and according to this, every item gets a rank, respectively index.
In the end I need the FriutsWithRankstruct like it is.
But I´m wondering if there is a better, more effective way to to that. Maybe by even skipping the whole Friuts struct:
struct Friuts {
var name: String
var price: Double
}
struct FriutsWithRank {
var name: String
var price: Double
var rank: Int
}
var friutsArray = [Friuts]()
func makeFriuts() {
friutsArray.append(Friuts(name: "mango", price: 1.2))
friutsArray.append(Friuts(name: "banana", price: 0.79))
friutsArray.append(Friuts(name: "orange", price: 2.2))
}
func makeFriutsWithRank(data: [Friuts]) -> [FriutsWithRank] {
let dataSorted = data.sorted { $1.price < $0.price }
var datatoappend = [FriutsWithRank]()
var i = 0
dataSorted.forEach { fruit in
i += 1
let name = fruit.name
let price = fruit.price
let rank = i
let result = FriutsWithRank(name: name, price: price, rank: rank)
datatoappend.append(result)
}
return datatoappend
}
let friutsArrayWithRank = makeFriutsWithRank(data: friutsArray)
With more effective i mean not necessarily less code. I think the two arrays are now created with two iterations. Is it possible to skip the whole Fruits struct and work just with one struct and one iteration?.
I have applied some modification on your code, please read the inline comments. Not much more optimised, but more readable for sure.
// Typo fixed + it is a single Fruit, not Fruits
struct Fruit {
var name: String
var price: Double
}
struct FruitsWithRank {
// You already have a variable holding name and price, Friut
// Lets just reuse Fruit object
var fruit: Fruits
var rank: Int
}
var fruits = [Fruit]()
func makeFruits() {
fruits.append(Fruit(name: "mango", price: 1.2))
fruits.append(Fruit(name: "banana", price: 0.79))
fruits.append(Fruit(name: "orange", price: 2.2))
}
func makeFruitsWithRank(data: [Fruits]) -> [FruitsWithRank] {
let dataSorted = data.sorted { $1.price < $0.price }
var datatoappend = [FruitsWithRank]()
// Use `enumerated` to get index and the object straight away
for (index, fruit) in dataSorted.enumerated() {
// Just init your `FruitsWithRank` with the fruit and the index
let rankedFruit = FruitsWithRank(fruit: fruit, rank: index)
// Add to your array
datatoappend.append(rankedFruit)
}
return datatoappend
}
let fruitsWithRank = makeFruitsWithRank(data: fruitsArray)
EDIT:
Following the edits of your question, i have applied some changes. If you need FruitsWithRank having name and price, you can just create tuples with name and price, and create an array straight away without any loops or appends. You can omit the makeFruitsWithRank function, and sort, enumerate and create your types straight on the tuple array.
struct FruitsWithRank {
var rank: Int
var name: String
var price: Double
}
let rankedFruits: [FruitsWithRank] = [
(name: "mango", price: 1.2),
(name: "banana", price: 0.79),
(name: "orange", price: 2.2)]
.sorted { $0.price < $1.price }
.enumerated()
.map({ return FruitsWithRank(rank: $0 + 1, name: $1.name, price: $1.price) })
In the end this isn't much more efficient than your code, but it is more compact:
func makeFriutsWithRank(data: [Friuts]) -> [FriutsWithRank] {
let dataMapped = data.sorted { $1.price < $0.price }
.enumerated()
.map { FriutsWithRank(name: $1.name, price: $1.price, rank: $0 + 1) }
return dataMapped
}
Is it really necassary to have Struct? Because you already have sorted, the index can serve as rank.

Resources