I have two arrays and I need to combine them by an id. This is how it looks like:
Every single Station has its own sensors. One, two, three or even none. We can find them thanks to ids. Station has an id property, and Sensors are owners of stationId properties. Unfortunately I can't to download them together in one array, so I have to use two arrays. I want to display them in the list view, so I have to create array of objects where the data of both lists form one list. I have created combined array, but there every Stations has only one Sensor and this my problem.
I can't make array where Stations are owners more than one Sensor. In the code below you can see, that there is a problem with accessing elements. It always takes the first index, but I don't know how to get another ones with same id. I'm stuck
Station:
[{
"id": 14,
"stationName": "Działoszyn",
"gegrLat": "50.972167",
"gegrLon": "14.941319",
"city": {
"id": 192,
"name": "Działoszyn",
"commune": {
"communeName": "Bogatynia",
"districtName": "zgorzelecki",
"provinceName": "DOLNOŚLĄSKIE"
}
},
"addressStreet": null
}]
Sensors:
[{
"id": 92,
"stationId": 14,
"param": {
"paramName": "pył zawieszony PM10",
"paramFormula": "PM10",
"paramCode": "PM10",
"idParam": 3
}
},
{
"id": 88,
"stationId": 14,
"param": {
"paramName": "dwutlenek azotu",
"paramFormula": "NO2",
"paramCode": "NO2",
"idParam": 6
}
}]
Edited code:
func setAddItemList(stations: [Station], sensors: [Sensor]) {
var stationItems = [StationItem]()
var sensorItems = [SensorItem]()
sensors.forEach { sensor in
guard let index = stations.firstIndex(where: { $0.id == sensor.stationId}) else {
print("Failed to find a station for sensor \(sensor.id)")
return
}
let sensorItem = SensorItem(
id: sensor.id,
stationId: sensor.stationId,
param: ParamItem(
paramName: sensor.param?.paramName ?? "",
paramFormula: sensor.param?.paramFormula ?? "",
paramCode: sensor.param?.paramCode ?? "",
idParam: sensor.param?.idParam ?? 0))
sensorItems.append(sensorItem)
if sensorItems.count == sensors.count {
let stationItem = stations.map { station in
StationItem(
id: station.id,
stationId: sensor.stationId,
cityName: station.city?.name ?? "",
addressStreet: station.addressStreet!,
sensor: stationItems[index].sensors?.append(sensorItem) ?? []
)
}
stationItems.append(contentsOf: stationItems)
}
}
I also tested Dictionaries, but I had the same problem.
First map Station to StationItem
var stationItems = stations.map { station in
StationItem(
id: station.id,
stationId: sensor.stationId,
cityName: station.city?.name ?? "",
addressStreet: station.addressStreet!,
sensor: [])
}
Then apply my solution from your previous question but instead of appending a Sensor object you first map it to a SensorItem object as you are doing in your code above
Related
JSON response like this
{
"result": {
"cities": [
{
"id": 1,
"city": "North and Middle Andaman",
"state_id": 32
},
{
"id": 2,
"city": "South Andaman",
"state_id": 32
},
{
"id": 3,
"city": "Nicobar",
"state_id": 32
},
{
"id": 330,
"city": "Mumbai suburban",
"state_id": 12
},
I am showing cities in textfield with TextFieldEditingChanged and dropdown
code: with this code i can show city list in dropdown and got selected city in textfield but i need selected city id as well.. which i am unable to get
if i print print(item, self?.cityId). here Mumbai suburban id is 330 but got dropdown index 2... how do i get selected city id from dropdown, please guide me
o/p
Mumbai suburban Optional(2)
#IBAction func cityTextFieldEditingChanged(_ sender: UITextField) {
var tempArray = [String]()
if sender.text?.count != 0 {
for eachData in cityResultDict?.cities ?? []{
if let wordToSearch = sender.text{
let range = eachData.city?.lowercased().range(of: wordToSearch, options: .caseInsensitive, range: nil, locale: nil)
if range != nil {
tempArray.append(eachData.city ?? "")
}
}
}
}
showDropDown(with: tempArray, from: sender) { [weak self] (item, index) in
self?.locationTextField.text = item
self?.cityId = cityResultDict?.cities?[index].id ?? 0
print(item, self?.cityId)
}
}
if i print print(item, self?.cityId). here Mumbai suburban id is 330 but got dropdown index 2..
Because you are making a new array which is tempArray matching with your search condition when you showDropDown.
I don't know how you implement your showDropDown. But this is a key for your code
Code will be like this
var tempArray = [String]()
var tempCityId = [String]() // I not recommend slipt like this which one is for name city and one is for your id
if sender.text?.count != 0 {
for eachData in cityResultDict?.cities ?? []{
if let wordToSearch = sender.text{
let range = eachData.city?.lowercased().range(of: wordToSearch, options: .caseInsensitive, range: nil, locale: nil)
if range != nil {
tempArray.append(eachData.city ?? "")
// make new array to store your cityId too
tempId.append(eachData.cityId ?? "")
}
}
}
}
showDropDown(with: tempArray, from: sender) { [weak self] (item, index) in
self?.locationTextField.text = item
self?.cityId = tempCityId[index].id ?? 0 // get the id form your tempCityId too
print(item, self?.cityId)
}
Notice: This way I don't recommend. You can make a new struct which store all your reaching City or a struct only store your cityName and cityId which easier for you.
I build this way just for easier for you to realize and because I don't know how you build your showDropDown
I have an State varaible with array of objects like this.
type State = {
Dp: ArrayDataProvider<string, Message>;
};
Inside Dp i will have data which will hold the data in the form of array like this.
[{
"id": 1,
"name": "January",
"abc": abc,
"xyz": xyz
}, {
"id": 2,
"name": "February",
"abc": abc,
"xyz": xyz
}]
I want to replace the object which is having id 2 with the different object and i want to have my object like this .
[{
"id": 1,
"name": "January",
"abc": abc,
"xyz": xyz
}, {
"id": 2,
"name": "New month",
"abc": 1234abc,
"xyz": someVlaue
}]
how to do it in efficient way with typescript in react.
I have done something like this but not working
const data = this.state.Dp?.data.slice();
const index = data.findIndex(currentItem => {
return currentItem.id === updateData[0].id;
});
data[index] = updateData;
this.state.Dp.data = data;
this.setState({ Dp: this.state.Dp });
I use map to do this:
const data = this.state.Dp?.data.map(currentItem => {
return currentItem.id === updatedItem.id ? updatedItem : currentItem;
})
map creates a new array with the items from the previous array, but it gives you an opportunity to make adjustments to the items in the new array as it iterates through them. In my example, I'm checking the id of the item to see if it's the one you want to update, and swapping in your updatedItem if the ids match.
I'm not sure about the TypeScript part, to be honest. I haven't worked with it yet.
Note - I'm not sure what form your updateData is in, so you might have to adjust that. It seems like you want it to be an object, but in one of your lines you're treating it like an array.
Use findIndex to find index of the object where id is equal 2, then replace new object to that place.
let tempArray = [...array];
const index = tempArray.findIndex((element) => element.id === 2);
tempArray[index] = {
id: 2,
name: "New month",
abc: "1234abc",
xyz: "someVlaue"
};
setArray(tempArray);
When trying to parse some data from a REST API endpoint in SwiftUI, I'm struggling to render out to a list correctly.
In the below example, my List requires the exact array position to render results, so it won't show anything without [0] etc...
I'm used to solving this type of issue in JavaScript using a for loop and iterating over it to assign each array position to [i] however in SwiftUI I'm struggling to do the same.
I think it's likely an issue with how I'm handling the nested JSON objects but here we go. I'm also confident it's probably an easy solution that I'm just blanking on here.
I've created a test endpoint that you can fetch from if you need to run it locally (this is in the below examples already).
To clarify what I'm looking to do here. I want to iterate over the array to reference (for example) Text(item.objects.title) without having to specify which item in the array I'm looking for. Using a ForEach doesn't work sadly because the List(viewModel.items, id: \.self) { item in } is essentially replicating the ForEach looping and doesn't work either.
Model
struct ObjectResponse: Hashable, Decodable {
let objects: [Object]
}
struct Object: Hashable, Decodable {
let slug: String
let title: String
let metadata: Metadata
}
struct Metadata: Hashable, Decodable {
let published: String
let url: String
let snippet: String
let read: Bool
}
ViewModel
class ContentViewModel: ObservableObject {
#Published var items: [ObjectResponse] = []
func fetchData() {
let api = "https://api.cosmicjs.com/v2/buckets/a5e294b0-55ee-11ec-942e-ef0a04148eb7/objects?pretty=true&query=%7B%22type%22%3A%22bookmarks%22%7D&read_key=fufvo5ceSy1W88afkchDIRjYrUIzErPw9YzcW2vQV1SxKqjNHo&limit=20&props=slug,title,content,metadata,"
guard let url = URL(string: api) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let data = data {
let result = try JSONDecoder().decode(ObjectResponse.self, from: data)
DispatchQueue.main.async {
self.items = [result]
/* Prints out everything nicely
result.objects.forEach {
print($0.title)
}
*/
}
} else {
print("No data")
}
} catch (let error) {
print(error)
}
}.resume()
}
}
Content View
struct ContentView: View {
#ObservedObject var viewModel = ContentViewModel()
var body: some View {
NavigationView {
List(viewModel.items, id: \.self) { item in
VStack(alignment: .leading) {
Text(item.objects[0].title) // Requires me to provide array position
}
}
.navigationTitle("Bookmarks")
}
.onAppear(perform: {
viewModel.fetchData()
})
}
}
Example JSON response
{
"objects": [
{
"slug": "the-navigation-bar-isnt-hidden-as-expected-in-swiftui",
"title": "The Navigation Bar Isn’t Hidden as Expected in SwiftUI",
"content": null,
"metadata": {
"published": "28 Nov 2021 at 16:30",
"url": "https://betterprogramming.pub/swiftui-navigationbar-is-not-really-hidden-as-you-expect-785ff0425c86",
"snippet": "",
"read": true
}
},
{
"slug": "hey-facebook-i-made-a-metaverse-27-years-ago",
"title": "Hey, Facebook, I Made a Metaverse 27 Years Ago",
"content": null,
"metadata": {
"published": "28 Nov 2021 at 21:39",
"url": "https://www.theatlantic.com/technology/archive/2021/10/facebook-metaverse-was-always-terrible/620546/",
"snippet": "Michel Baret / Gamma-Rapho / Getty In a booth at Ted’s Fish Fry, in Troy, New York, my friend Daniel Beck and I sketched out our plans for the metaverse. ",
"read": true
}
},
{
"slug": "when-big-tech-buys-small-tech-benedict-evans",
"title": "When big tech buys small tech — Benedict Evans",
"content": null,
"metadata": {
"published": "28 Nov 2021 at 21:39",
"url": "https://www.ben-evans.com/benedictevans/2021/11/12/when-big-tech-buys-small-tech",
"snippet": "Acquisitions are a big part of the discussion around competition in tech today, and a big set of questions. It’s easy to say that we wouldn’t let Amazon buy Zappos or Google buy DoubleClick again, but everyone’s favourite puzzle is Instagram. ",
"read": false
}
}
],
"total": 171,
"limit": 3
}
Alright, solved, thanks to a little help from jnpdx. The array needed to be further drilled into via a ForEach loop, like so:
List(viewModel.items, id: \.self) { item in
ForEach(item.objects, id: \.self) { bookmark in
VStack(alignment: .leading) {
Text(bookmark.title)
}
}
}
I have a Country object and a City object
struct Country: {
let name: String
let countryCode: String
let cities: [City]
let population: Int
init(name: String, countryCode: String, cities: [City], population: Int) {
self.name = name
self.countryCode = countryCode
self.cities = cities
self.population = population
}
}
struct City {
let id: Int
let name: String
let latitude: Double
let longitude: Double
let countryCode: String
let population: Int
}
Incoming JSON data looks like this which decodes into [City] array
{
"cities":[
{
"id":1,
"name":"Paris",
"latitude":0,
"logitude":0,
"country_code":"FR",
"population":0
},
{
"id":2,
"name":"Nice",
"latitude":0,
"logitude":0,
"country_code":"FR",
"population":0
},
{
"id":3,
"name":"Berlin",
"latitude":0,
"logitude":0,
"country_code":"DE",
"population":0
},
{
"id":4,
"name":"Munich",
"latitude":0,
"logitude":0,
"country_code":"DE",
"population":0
},
{
"id":5,
"name":"Amsterdam",
"latitude":0,
"logitude":0,
"country_code":"NL",
"population":0
},
{
"id":6,
"name":"Leiden",
"latitude":0,
"logitude":0,
"country_code":"NL",
"population":0
}
]
}
How would I create [Country] array from [City] array efficiently? I've tried to use reduce:into: but not sure that's what I have to use.
I know I could go with an empty array and add/create Countries one by one then search if there is one already and add City to it. That creates awful looking code as for me. I feel like there is an elegant solution to this problem using map or reduce functions.
reduce:into: code I've tried so far
func transformArrayOf(_ cities: [City]) -> [Country] {
let empty: [Country] = []
return cities.reduce(into: empty) { countries, city in
let existing = countries.filter { $0.countryCode == city.countryCode }.first
countries[existing].cities.append(city)
}
}
EDIT:
The function only gets [City] array. So countries must be created only from that.
Dictionary(grouping:by:) with map(_:) works perfectly! Two lines instead on nested for loops and if statements :)
And Country name can be parsed from a country code
Use Dictionary(grouping:by:) and map(_:) combined to get the expected result.
let countries = Dictionary(grouping: cities, by: { $0.countryCode }).map { (countryCode, cities) -> Country in
return Country(name: "", countryCode: countryCode, countryName: "", cities: cities, population: cities.reduce(0) { $0 + $1.population })
}
Since the values for name and countryName are unknown, I've used empty String ("") for both.
This is what Dictionary(grouping:by:) is for:
let citiesByCountryCode = Dictionary(grouping: cities, by: \.countryCode)
But you'll need separate logic to create the countries, because they contain data that isn't derived from the cities like name, countryName (how are those different?), etc.
Given the following object:
Object {November 2014: Object}
November 2014: Object
Cars: Object
324: Object
duration: 1417132808000
total: "00:00:08"
trips: 1
369: Object
duration: 5668531247000
total: "00:00:47"
trips: 4
391: Object
duration: 9919930257000
total: "00:10:57"
trips: 7
396: Object
duration: 9919929791000
total: "00:03:11"
trips: 7
KE-22: Object
duration: 5668531269000
total: "00:01:09"
trips: 4
I need to be able to create arrays from data extracted from it.
Something like this:
Labels: (Key from Objects inside Cars)
[324, 369, 391, 396, KE-22]
Series: (Properties from each object inside Cars)
[Trips, Total]
Data: (Values from properties for each object inside Cars)
[
[1,4,7,7,4], // Array of trips for each car, in order.
["00:00:08", "00:00:47", "00:10:57", "00:03:11", "00:01:09"] // Array of total duration for each car, in order.
]
With this arrays I intend to populate a chart for each month.
The collection of objects has been created using the following code, probably is relevant to the question:
var dataByMonth = _.groupBy($scope.recordlist, function(record) {
return moment(record.date, 'DD-MM-YYYY').format('MMMM YYYY');
});
dataByMonth = _.mapValues(dataByMonth, function(month) {
var obj = {};
obj.Cars = _.groupBy(month, 'car');
obj.Drivers = _.groupBy(month, 'driver');
_.each(obj, function(groupsValue, groupKey) {
obj[groupKey] = _.mapValues(groupsValue, function(groupValue) {
return _.reduce(groupValue, function(sum, trip) {
sum['trips']++;
sum['duration']+= moment.utc(trip.duration, 'HH:mm:ss');
sum['total'] = moment.utc(sum.duration). format('HH:mm:ss')
return sum;
}, {trips: 0, duration: 0, total:0})
});
})
return obj;
});
$scope.statistics = dataByMonth;
console.log($scope.statistics);
Any tips on how to proceed?
_.chain(cars).pairs().map(function(p) {
var id = p[0];
return [id].concat(_.values(p[1])); // produces an array of [{id, duration, trips, total}]
}).reduce(function(acc, as) {
// we assume properties are sorted
return _.map(as, function(a, i) {
return acc[i] = (acc[i] || []).concat([a]);
});
}, []).value();
A problem with objects is that they cannot guarantee the order of their properties.
This means that before converting to arrays, you need to somehow decide on a predifined sequence algorithm. In the code below this is the alphanumeric order of the property names.
var objPart = obj["November 2014"]["Cars"];
var ret = _.chain(objPart).transform(function(total, n , key){
//In this first iteration we collect all possible series that may exist,
total.series =_.union(total.series, _.keys(n) );
//and all labels
total.labels.push(key);
}, {labels:[], series:[], data:[]} ).mapValues(function(val){
//sorting labels, series. Data do not yet exist
return _.sortBy(val);
}).value();
//based on the above orders generate a multi dimensional array for data.
ret = _.transform(ret.labels, function(total, lbl){
var arr = _.transform(ret.series, function(partialTotal, ser){
partialTotal.push(objPart[lbl][ser]);
}, []);
total.data.push(arr);
}, ret);
console.log("Labels:",ret.labels);
console.log("Series:",ret.series);
console.log("Data:",JSON.stringify(ret.data));
The above solution will work even for the data below:
var obj = {
"November 2014": {
"Cars": {
"339": {
"trips": 1,
"total": "00:00:08",
"duration": 1417132809000,
},
"324": {
"trips": 1,
"extraProp1":"x",
"total": "00:00:08",
"duration": 1417132808000,
},
"369": {
"duration": 5668531247000,
"trips": 4,
"total": "00:00:47",
"extraProp2":"y"
},
"391": {
"duration": 9919930257000,
"trips": 7,
"total": "00:10:57"
},
"396": {
"duration": 9919929791000,
"total": "00:03:11",
"trips": 7
},
"KE-22": {
"duration": 5668531269000,
"total": "00:01:09",
"trips": 4
}
}
}
}
Output:
Labels: ["324", "339", "369", "391", "396", "KE-22"]
Series: ["duration", "extraProp1", "extraProp2", "total", "trips"]
Data: [[1417132808000,"x",null,"00:00:08",1],
[1417132808000,null,null,"00:00:08",1],
[5668531247000,null,"y","00:00:47",4],
[9919930257000,null,null,"00:10:57",7],
[9919929791000,null,null,"00:03:11",7],
[5668531269000,null,null,"00:01:09",4]]