Create ordered Array from Dictionary - arrays

This is a fundamental thing that I should know but don't know at a deep level and therefore find confusing. Dictionaries seem to be unordered list of keys and values. I want to create an ordered list of keys and values so that I can sort and otherwise keep track of order. I think this means I have to convert the dictionary into a multi-dimensional Array that has the key and value and also an index value ie 0,1,2,3 etc.
If my dictionary looks like the following:
var myScores = [String: Float]()
myScores = ["player1":22,"player2":33]
How do I convert it into an Array where player1:22 is the first element and player2:33 is the second element?
Edit:
Alternative without creating struct is to create an empty array of dictionaries in the form of your dictionary and then append your dictionary to the array.
var myArray = [[String: Float]]()//note double brackets
propArray.append(myScores)

What you want is an array of structs.
struct Score {
let playerName: String
let score: Int
}
let scores = [Score(playerName: "player1", score: 22),
Score(playerName: "player2", score: 33),
]
let sortedScores = scores.sorted(by: { $0.playerName < $1.playerName })
To get key-value pairs out of a dictionary is straightforward, though it forces you to work with tuples, which are not a particularly friendly type. Even so, it's done this way:
let sortedScores = myScores.sorted { $0.key < $1.key }
That will create:
[(key: "player1", value: 22.0), (key: "player2", value: 33.0)]
Or
for (name, score) in myScores.sorted(by: { $0.key < $1.key }) {
print(name, score)
}

You can keep the dictionary as-is. One possible solution is to create an array that contains just the keys in the desired order. Then you can iterate the key array and access the elements of the dictionary.
var myScores: [String:Float] = ["player1":22, "player2":33]
var myPlayers = myScores.keys.sorted()
for player in myPlayers {
let score = myScores[player]
}
This works when you want to show the data in some particular order, such as in a table view.

Related

Looping over and combining Swift array of structs?

I'm trying to create an array of structs (User structs below) where, if the user.name does not yet exist in the array, it appends the user -- but if the user.name is already present ("Mcdonalds" in the below example), then it will simply add the item.amount to the existing struct.
In other words, the below code should create an array of 4 Users, with the User Mcdonalds item.amount totaling 23.44 + 12.33.
I remember doing this sort of thing in JS no sweat, but I can't for the life of me figure out how to do this in Swift. Thanks for any help!
struct User {
var name: String
var amount: Double
}
var user1 = User(name: "Mcdonalds", amount: 23.44)
var user2 = User(name: "Wendys", amount: 15.44)
var user3 = User(name: "Cabanos", amount: 12.22)
var user4 = User(name: "Shell Gas", amount: 23.33)
var user5 = User(name: "Mcdonalds", amount: 12.33)
To loop over the users they'll need to be in an array to start.
Then you can use .reduce(into:) to reduce them into one condensed dictionary (the dictionary will allow you to have a unique key (the name of the user here) so that you don't have duplicate entries). Then you can use .map() to just get the value and not the key of that dictionary so that the final result will be an array of users.
struct User {
var name: String
var amount: Double
}
var users = [
User(name: "Mcdonalds", amount: 23.44),
User(name: "Wendys", amount: 15.44),
User(name: "Cabanos", amount: 12.22),
User(name: "Shell Gas", amount: 23.33),
User(name: "Mcdonalds", amount: 12.33)
]
var reducedUsers = users.reduce(into: [String: User]()) { (result, nextUser) in
if let existing = result[nextUser.name] {
result[nextUser.name] = User(name: nextUser.name, amount: existing.amount + nextUser.amount)
} else {
result[nextUser.name] = nextUser
}
}.map { $0.value }
A clean and swifty way is to write an extension for Array. Swift is highly protocol-oriented, which means you are able to extend any existing system or self-written class with new functions.
This is just a simple implementation, which uses a function to append or update any given user object:
extension Array where Element == User {
/// Appends a not existing user to the array or updates the amount value if user is already present
mutating func appendOrUpdate(_ userObject: Element) {
// Check if user is already in the array
if let index = self.firstIndex(where: { $0.name == userObject.name }) {
// Update the amount
self[index].amount += userObject.amount
}
else {
// Append the user
self.append(userObject)
}
}
}
As the where clause specifies the extension the Element of the array only to be applied when the given object is your User struct, it is only available when you pass in an user object.
Usage:
var userArray: [User] = []
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 23.44))
userArray.appenOrUpdate(User(name: "Wendys", amount: 15.44))
userArray.appenOrUpdate(User(name: "Cabanos", amount: 12.22))
userArray.appenOrUpdate(User(name: "Shell Gas", amount: 23.33))
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 12.33))
This will result in an array with just four entries and the double entry 'Mcdonalds' user's amount cumulated.
Note that the function has the mutating keyword in front, as if not you will not be able to modify the array and its entries. This is necessary due the nature of arrays being Structs themselves.
You can also write a function like the know Array's append(contentsOf:) and pass in an array of user objects and loop through them updating or appending the given objects.
Best way is to put this extension in a separate file called Array+User.swift according to best practise naming conventions.
You can read more about extensions in Swift and their power here: https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
Matthew Gray's answer is very good, and can be used for a wide variety of problems that may be more complex than this one. But for this specific problem, it can be done much more simply.
let reducedUsers = users.reduce(into: [:]) { (result, user) in
result[user.name, default: 0] += user.amount
}
.map(User.init)
The point of this is that it tears apart the struct into key and value in a Dictionary, and then reassembles the values into an Array at the end. Swift is smart enough to figure out the type of the [:], so there's no need to specify that.
Note that there is a time-space tradeoff here. This creates a temporary Dictionary that can be very large. If this kind of operation is common and the dataset is large, you should consider storing this data in a Dictionary ([String: User] or [String: Double]) all the time rather than converting back and forth.

How prevent Object.keys() sort?

The problem with the ECMA standard for sort of Object.keys() is known:
Object.keys() handle all keys with integer (example: 168), including integer as strings (example: "168"), as a integer. The result is, both are the same (168 === "168"), and overwrite itself.
var object = {};
object["168"] = 'x';
object[168] = 'y';
Object.keys(object); // Array [ "168" ]
object[Object.keys(object)]; // "y"
Interestingly, all keys (including pure integer keys) are returned as a string.
The ecma262 wrote about this: All keys will be handle as a integer, expect the key is a String but is not an array index.
https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
That should tell us: 168 === "168". A toString() do not solve the problem.
var object = {};
object[[3].toString()] = 'z';
object[[1].toString()] = 'x';
object[[2].toString()] = 'y';
Object.keys(object);
// Array(3) [ "1", "2", "3" ]
Paradoxically, in this case, only integer apply as "enumerable" (it's ignoring array.sort(), that sort also strings with letters.).
My question about this is simple: How can i prevent the sort function in Object.keys()? I have testet the Object.defineProperties(object, 1, {value: "a", enumerable: true/false}), but that mean not realy enumerable in the case of integer or string or integer-like string. It means only should it be counted with or not. It means "counted" like omit (if it false), not "enumerabled" like ascending or descending.
A answere like that is not a good answer: Please use only letters [a-zA-Z] or leastwise a letter at the first position of keyword.
What I want: That the keys are not sorted, but output in the order in which they were entered, whether integer, string or symbol.
Disclaimer: Please solutions only in JavaScript.
Javascript Objects are unordered by their nature. If you need an ordered object-like variable I would suggest using a map.
To achieve what you're looking for with a map instead of object you'd do something like the below:
var map1 = new Map();
map1.set("123", "c");
map1.set(123, "b");
var iterator1 = map1.keys();
var myarray = [];
for (var i = 0; i < map1.size; i++) {
myarray.push(iterator1.next().value);
}
console.log(myarray);
// Array ["123", 123]
Unfortunately it's not compatible with IE and I'm not sure how else you could achieve what you need without it. A quick Google did return something about jQuery maps, though.
If you don't want to use jQuery and still need to support IE some points are below:
Is there anything stopping you using an array rather than JS object to store the data you need? This will retain the order per your requirements unlike objects. You could have an object entry in each iteration which represents the key then use a traditional foreach to obtain them as an array. I.e.
The array:
var test_array = [
{key: 123, value: 'a value here'},
{key: "123", value: 'another value here'}
];
// console.log(test_array);
Getting the keys:
var test_array_keys = [];
test_array.forEach(function(obj) { test_array_keys.push(obj['key']); } );
// console.log(test_array_keys);
Then if you needed to check whether the key exists before adding a new entry (to prevent duplicates) you could do:
function key_exists(key, array)
{
return array.indexOf(key) !== -1;
}
if(key_exists('12345', test_array_keys))
{
// won't get here, this is just for example
console.log('Key 12345 exists in array');
}
else if(key_exists('123', test_array_keys))
{
console.log('Key 123 exists in array');
}
Would that work? If not then the only other suggestion would be keeping a separate array alongside the object which tracks the keys and is updated when an entry is added or removed to/from the object.
Object Keys sorted and store in array
First Creating student Object. then sort by key in object,last keys to store in array
const student={tamil:100, english:55, sci:85,soc:57}
const sortobj =Object.fromEntries(Object.entries(student).sort())
console.log(Object.keys(sortobj))
use map instead of an object.
let map = new Map()
map.set("a", 5)
map.set("d", 6)
map.set("b", 12)
to sort the keys (for example, to update a chart data)
let newMap = new Map([...map.entries()].sort())
let keys = Array.from(newMap.keys()) // ['a','b','d']
let values = Array.from(newMap.values()) // [5,12,6]

Sorting a dictionary by key and converting it into an array

I have a dictionary of prices and quantities. I am getting updates on the price and values multiple times in a second so I don't want to store them in an array because dictionary are much faster.
let mainPriceValDict = [Double:Double]()
The data is coming in as an array of JSON so I am using codable to parse the JSON and put it into a dictionary. When I use the data, it needs to be sorted in ascending and/or descending order because I am looping through each price in order to get to a certain total quantity. The format that the array is in that I am looping through is as follows:
let loopingArray = [PriceQuantityEntry]()
struct PriceQuantityEntry {
let price : Double
let size : Double
}
I want to sort the prices which are the keys in the dictionary above and convert them into an array of PriceQuantityEntry. What is the best way to do this? In ascending and deciding order. I have tried first getting all the keys sorted and then grabbing associated values and putting them into the array in order but this seems like more processing than this task actually requires.
I think the best way to do this would be to put a custom initializer in the struct to convert the dictionary value to a value of type PriceQuantityEntry but I am not exactly sure how that would work with the sorting.
This is what I am currently doing to get it to work. I just feel like there is a more efficient way for it to be done. If you feel like I should keep the structure as an array instead of converting it to a dict, let me know.
loopingArray = self.mainPriceValDict.sorted { $0.0 < $1.0 }.map { PriceQuantityEntry(price: $0.0, size: $0.1) }
If you are getting a lot of updates to individual entries, both a dictionary and an array may cause memory copies of the whole memory structure every time an entry is changed.
I would suggest using objects (classes) instead of structures. This will allow you to use both an array and a dictionary to reference the object instances. The dictionary will provide direct access for updates and the array will allow sequential processing in forward or backward order.
[EDIT] Example:
class PriceQuantityEntry
{
static var all:[PriceQuantityEntry] = []
static var prices:[Double:PriceQuantityEntry] = [:]
var price : Double
var size : Double
init(price:Double, size:Double)
{
self.price = price
self.size = size
PriceQuantityEntry.all.append(self)
// PriceQuantityEntry.all.resort() // on demand or when new prices added
PriceQuantityEntry.prices[price] = self
}
class func update(price:Double, with size:Double)
{
if let instance = PriceQuantityEntry.prices[price]
{ instance.size = size }
else
{
let _ = PriceQuantityEntry(price:price, size:size)
PriceQuantityEntry.resort()
}
}
class func resort()
{
PriceQuantityEntry.all.sort{$0.price < $1.price}
}
}
// if adding multiple initial entries before updates ...
let _ = PriceQuantityEntry(price:1, size:3)
let _ = PriceQuantityEntry(price:1.25, size:2)
let _ = PriceQuantityEntry(price:0.95, size:1)
PriceQuantityEntry.resort()
// for updates ...
PriceQuantityEntry.update(price:1, with: 2)
// going throug list ...
var count:Double = 0
var total:Double = 0
var quantity:Double = 5
for entry in PriceQuantityEntry.all
{
total += min(entry.size,quantity-count) * entry.price
count = min(quantity,count + entry.size)
if count == quantity {break}
}

Swift: Accessing array value in array of dictionaries

I am currently struggling with obtaining a value from an array inside an array of dictionaries. Basically I want to grab the first "[0]" from an array stored inside an array of dictionaries. This is basically what I have:
var array = [[String:Any]]()
var hobbies:[String] = []
var dict = [String:Any]()
viewDidLoad Code:
dict["Name"] = "Andreas"
hobbies.append("Football", "Programming")
dict["Hobbies"] = hobbies
array.append(dict)
/// - However, I can only display the name, with the following code:
var name = array[0]["Name"] as! String
But I want to be able to display the first value in the array stored with the name, as well. How is this possible?
And yes; I know there's other options for this approach, but these values are coming from Firebase (child paths) - but I just need to find a way to display the array inside the array of dictionaries.
Thanks in advance.
If you know "Hobbies" is a valid key and its dictionary value is an array of String, then you can directly access the first item in that array with:
let hobby = (array[0]["Hobbies"] as! [String])[0]
but this will crash if "Hobbies" isn't a valid key or if the value isn't [String].
A safer way to access the array would be:
if let hobbies = array[0]["Hobbies"] as? [String] {
print(hobbies[0])
}
If you use a model class/struct things get easier
Given this model struct
struct Person {
let name: String
var hobbies: [String]
}
And this dictionary
var persons = [String:Person]()
This is how you put a person into the dictionary
let andreas = Person(name: "Andreas", hobbies: ["Football", "Programming"])
persons[andreas.name] = Andreas
And this is how you do retrieve it
let aPerson = persons["Andreas"]

iOS Swift: How to find unique members of arrays of different types based on specific attributes

Goal: I have two different classes, and two arrays containing members of each class. Using Swift 2.0, I would like to find the unique members of one array compared to the other based on specific attributes of each class.
Example:
class A {
var name: String
init(name: String) {
self.name = name
}
}
class B {
var title: String
init(title: String) {
self.title = title
}
}
let aArray = [A(name:"1"), A(name:"2"), A(name:"3"), A(name:"4")]
let bArray = [B(title:"1"), B(title:"2"), B(title:"5")]
So, I'm looking for some operation between aArray and bArray which returns the 3rd and 4th element of aArray, because they are uniquely in aArray, where the basis of comparison is the attributes A.name and B.title.
Of course, reversing the order of the operation would pick out the 3rd element of bArray, because it is uniquely in bArray.
I know I can accomplish the goal straightforwardly using a simple for loop, but I was hoping for something more elegant and more optimized. But if a for loop is as fast or faster than anything fancier, I'm happy to use it just as well.
I'm not sure fancy or elegant this code is, but, we could do something like this:
let mappedArray = bArray.map { $0.title }
let filteredArray = aArray.filter { !mappedArray.contains($0.name) }
So when we want the unique elements from aArray, we first map the elements from bArray to get an array of the value we want to actually compare:
let mappedArray = bArray.map { $0.title }
mappedArray is just an array of strings based on the title property of the objects in bArray.
Next, we use the filter method to filter objects from aArray. The filter method returns an array with objects that pass the test in our closure. The test we want to apply is objects that are not contained in the mapped array we just built.
let filteredArray = aArray.filter { !mappedArray.contains($0.name) }
If we want to do it the other way, just change a few things:
let mappedArray = aArray.map { $0.name }
let filteredArray = bArray.filter { !mappedArray.contains($0.title) }

Resources