Swift loop through array of dictionaries and create a new one - arrays

I'm struggling on getting a new array of dictionaries with conditioned 10 elements out of 80.
here is sample of students json:
{
"id": 111,
"name": "John",
"gender": "male",
"grade": 80,
"extraCredit": 20
},
{
"id": 112,
"name": "Jenny",
"gender": "female",
"grade": 85,
"extraCredit": 5
}
and my struct:
struct StudentData: Decodable {
var id: Int
var name: String
var grade: Int
var extraCredit: Int
}
I need to calculate each student's grade to get final grade and display top 10
here is my code to loop through:
var newStudentData = [Dictionary<String, Any>]()
for student in studentData {
let finalGrade = student.grade + (student.extraCredit * 0.5)
if finalGrade > 88 {
newStudentData.append(student) // error on cannot convert value type of ’StudentData’ to expected argument type ‘[String: Any]
}
}
what did I do wrong? or is there a better way?

newStudentData is a dictionary (why?), but you're appending instances of StudentData to it. This won't work.
That said, I would probably rewrite this like so:
struct StudentData: Decodable {
let id: Int
let name: String
let grade: Int
let extraCredit: Int
var finalGrade: Int {
return self.grade + self.extraCredit / 2
}
}
var students = [StudentData]()
// fill students array here
let beat88 = students.filter { $0.finalGrade > 88 }
let top10 = beat88.sorted { $0.finalGrade > $1.finalGrade }.prefix(10)

Related

Struct inside struct Equatable count duplicate item Swift

The answer I found is with one level of struct. In my code I want to find out how many same dates is in inside of the struct of the struct. I never use equatable before so I'm not sure how to implement it with struct inside struct.
I'm using Charts pod and the array chartMetricItemSets contains different readings with multiples days. ex: ["name": sugar, [["date": "Oct 10", "value": 120], ["date": "Oct 10", "value": 121], ["date": "Oct 11", "value": 120], ["date": "Oct 13", "value": 120], ["date": "Oct 13", "value": 121]]. The xAxisDates will be ["Oct 10", "Oct 11", "Oct 13"]
the x is the date and the y is for the same date sums
Goal: chartDataEntries should append [x:0(10/10), y:241], [x:1(10/11), y:120], [x:2(10/13), y: 241]
Thank you!
struct ChartData {
let name: String
let value: [ChartValue]
}
struct ChartValue {
let date: Date
let value: Double
}
var chartMetricItemSets = [[ChartData]]()
var xAxisDates = [String]() //date array
func getChartDataSets() {
for (index, chartHealthItemSet) in chartMetricItemSets.enumerated() {
let dataSourceSelected = dataSourcesSelected[index]
var chartDataEntries = [ChartDataEntry]() //array display on graph
var chartYaxis: Double = 0
var datePoint: Int = 0
var countChartYaxis = 0
var duplicateDateCount = 0
for (index, item) in chartHealthItemSet[0].value.enumerated() {
for (dateIndex, date) in xAxisDates.enumerated() {
if item.date.convertDateToString(dateFormat: .monthDay) == date {
chartYaxis += item.value
datePoint = dateIndex
chartYaxis += 1
break
}
//missing counting duplicateDateCount
if duplicateDateCount == countChartYaxis {
chartDataEntries.append(ChartDataEntry(x: Double(datePoint), y: chartYaxis.roundUpNDecimal(n: 2)))
}
}
print("chartYaxis: \(chartYaxis)")
print("chartDataEntries: \(chartDataEntries)")
}
chartDataEntries.sort(by: { $0.x < $1.x })
let set = LineChartDataSet(entries: chartDataEntries,
label: HEALTH_TABLE.allCases[dataSourceSelected].info.name)
set.mode = .linear // cubic line effect (smoother)
set.colors = [HEALTH_TABLE.allCases[dataSourceSelected].info.chartColor]
set.setCircleColor(.black)
set.lineWidth = 1.5
set.circleRadius = 3
set.drawCircleHoleEnabled = false
set.drawCirclesEnabled = true
set.drawHorizontalHighlightIndicatorEnabled = true
lineChartData?.addDataSet(set)
}
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: xAxisDates)
chartView.xAxis.granularity = 1
chartView.xAxis.axisMinLabels = 4
chartView.xAxis.axisMaxLabels = 6
DispatchQueue.main.async {
self.chartView.data = self.lineChartData
}
}
In Swift 4 a useful API was introduced to group arrays to dictionaries by an arbitrary predicate.
You can group chartHealthItemSet[0]
let grouped = Dictionary(grouping: chartHealthItemSet[0], by: {$0.date.convertDateToString(dateFormat: .monthDay)}
The result is ["10/10":[ChartData], "10/11":[ChartData], ...]
Sort the keys and assign them to xAxisDates
xAxisDates = grouped.keys.sorted()
To get the sum of the values get the array from the grouped dictionary for the date key, map it to the values and sum them up
for (index, date) in xAxisDates.enumerated() {
let sum = grouped[date]!.map{$0.value}.reduce{0.0, +}
chartDataEntries.append(ChartDataEntry(x: Double(index), y: sum.roundUpNDecimal(n: 2)))
}

How to Create Optional Struct with Arrays

I would like to create the following struct/array combination; however, I'm getting lost in the array nesting. Is this the proper way to set up a "City", and if so, what would be the best practice technique to modify it, so that I can init new Cities with a different customer value?
Thanks!
struct City {
var name: String
var groceryStores: [GroceryStore]
}
struct GroceryStore {
var name: String
var employeesAtInterval = [employeeIDs]
}
var employeeIDs = [40, 20, 13, 44]
Based on your code, this seems like what you wanted to achieve:
struct City {
var name: String
var groceryStores: [GroceryStore]
}
struct GroceryStore {
var name: String
// As employeeIDs are Ints, you should initialize it using this syntax
var employeesAtInterval = [Int]()
}
let employeeIDs1 = [40, 20, 13, 44]
let employeeIDs2 = [40, 20, 13, 44]
...
let groceryStore1 = GroceryStore(
name: "store 1",
employeesAtInterval: employeeIDs1
)
let groceryStore2 = GroceryStore(
name: "store 2",
employeesAtInterval: employeeIDs2
)
...
let city1 = City(name: "city 1", groceryStores: [groceryStore1, groceryStore2])
let city2 = City(name: "city 2", groceryStores: [groceryStore3, groceryStore4])
let cities = [city1, city2]

How can I count the item of property as an array inside a class?

How can I count the teamMember in team A?
class BasketBallTeam {
var teamName: String
var teamMember: [String]
init(teamName: String, teamMember: [String]) {
self.teamName = teamName
self.teamMember = teamMember
}
}
let highSchoolTeam: [BasketBallTeam] = [
BasketBallTeam(teamName: "A", teamMember: [
"John", "Sam", "Tom", "Ricky", "George"
]),
BasketBallTeam(teamName: "B", teamMember: [
"Jason", "Michael", "Daniel", "Henry", "David", "Luke"
]),
]
Just count it.
As highSchoolTeam is an array you have to get the first item
let numberOfMembers = highSchoolTeam.first?.teamMember.count
Please name teamMember in plural form teamMembers (or even members) to indicate the array
If you're doing this a lot, it would be best to create a dictionary that lets you easily access teams by their names:
struct BasketBallTeam {
var name: String
var members: [String]
init(name: String, members: [String]) {
self.name = name
self.members = members
}
}
let highSchoolTeams: [BasketBallTeam] = [
BasketBallTeam(name: "A", members: [
"John", "Sam", "Tom", "Ricky", "George"
]),
BasketBallTeam(name: "B", members: [
"Jason", "Michael", "Daniel", "Henry", "David", "Luke"
]),
]
let teamsByName = Dictionary(uniqueKeysWithValues:
highSchoolTeams.lazy.map { team in (key: team.name, value: team) })
Which makes looking up any team by name fast and easy:
let teamA = teamsByName["A"]
let teamASize = teamA?.members.count
print(teamASize as Any)
If you only need to do this once, you can use first(where:):
let teamA = highSchoolTeams.first(where: { $0.name == "A"})
You can get the count for each team as follows:
for team in highSchoolTeam {
print(team.teamMember.count)
}
You can simply use,
first(_:) on highSchoolTeam to get the team where teamName == "A".
count on teamMember to get the total count of teamMembers from the BasketBallTeam object obtained in step-1.
It goes like,
let teamAMemberCount = highSchoolTeam.first{ $0.teamName == "A" }?.teamMember.count
print(teamAMemberCount) //Output: 5

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.

How to mutate Values inside a Array

I need to mutate the following array:
struct Person {
var name: String
var age = 0
}
func showPersonArray() -> [Person] {
var dataArray = [Person]()
dataArray.append(Person(name: "Sarah_Yayvo", age: 29))
dataArray.append(Person(name: "Shanda_Lear", age: 45))
dataArray.append(Person(name: "Heidi_Clare", age: 45))
return dataArray
}
How could I split the "name"-key into two keys: "givenName" and "familyName".
Some nice person gave me this code before:
let arraySeparated1 = dataArray.map { $0.substring(to: $0.range(of: "_")!.lowerBound) }
let arraySeparated2 = dataArray.map { $0.substring(from: $0.range(of: "_")!.upperBound) }
Is it possible to make the mutation inside the struct?
The function showPersonArray() is only for demo and test.
Maybe there is a way to work with a target struct, like this:
struct Persontarget {
var familyname: String
var givenName: String
var age = 0
}
struct Person: Array -> [Persontarget] {
var name: String
var age = 0
// Here the split/mutating code
return [PersonWithTwoNames]
}
Or with an String extension. Possibly my question sounds pretty newby-like, but i´m trying the whole day...
Thanks everyone!
I would write an initializer on the new Person type, which initializes it from the old person type (which I called LegacyPerson):
import Foundation
struct LegacyPerson {
let name: String
let age: Int
}
func getPersonArray() -> [LegacyPerson] {
return [
LegacyPerson(name: "Sarah_Yayvo", age: 29),
LegacyPerson(name: "Shanda_Lear", age: 45),
LegacyPerson(name: "Heidi_Clare", age: 45)
]
}
struct Person {
let familyName: String
let givenName: String
let age: Int
}
extension Person {
init(fromLegacyPerson person: LegacyPerson) {
let index = person.name.range(of: "_")!
self.init(
familyName: person.name.substring(from: index.upperBound),
givenName: person.name.substring(to: index.lowerBound),
age: person.age
)
}
}
let people: [Person] = getPersonArray().map(Person.init)
people.forEach{ print($0) }
Create a method or computer variable for your Person class that returns what you want.
func firstName() -> String {
return self.substring(to: $0.range(of: "_")!.lowerBound)
}
You should not force cast though
with help of computed property defined in an extension
struct Person {
let name: String
let age: Int
}
let person = Person(name: "Maria_Terezia", age: 300)
extension Person {
var names:[String] {
get {
return name.characters.split(separator: "_").map(String.init)
}
}
}
for name in person.names {
print(name)
}
prints
Maria
Terezia

Resources