What is the best way to iterate over a Hash in KRL? - hash

Let's say I have a hash where I don't know the contents of the hash (so I can't use pick for this). Here is an example of the hash:
{ "key1" : "value1", "key2" : "value2", "key3" : "value3" }
I want to iterate over this hash and create the following array:
["key1=value1", "key2=value2", "key3=value3"]
My first approach would be to build a recursive function that iterates through the hash and populates the array, but I'm not sure if that can be done. In an array, I can use head() and tail() to help with the recursion, but those operators aren't available for a hash (as far as I know).
I want to initiate this from within a function because I'm doing this in a module. For example:
hash_to_array = function(h) {
// magic code here
}
manipulate_params = function(params) {
params_array = hash_to_array(params);
// more code here...
}

Mike, after impact, I will carve out time to build a keys() operator for hashes.
In the meantime, what I have done to get around this is to keep a separate array of the keys. That way, I can use map, filter and all of the set operations on the index and then use the those values as my keys for the hash operations
key_array = ["key1","key2","key3"];
my_hash = { "key1" : "value1", "key2" : "value2", "key3" : "value3" };
This really only works if you are controlling the values in the hash, but here is some example code:
global {
kvHash = { "key1" : "value1", "key2" : "value2", "key3" : "value3" };
kArray = ["key1","key2","key3"];
}
pre {
pickKey = kArray[1];
value = kvHash.pick("$.#{pickKey}");
// add a new value
newKey = "key4";
newVal = "value4";
newArray = kArray.union(newKey);
newHash = kvHash.put([newKey],newVal);
}
Noticed that I used the set operator union to keep the array full of unique values
The javascript that is generated shows what this does:
var pickKey = 'key2';
var value = 'value2';
var newKey = 'key4';
var newVal = 'value4';
var newArray = ['key1', 'key2', 'key3', 'key4'];
var newHash = {'key2' :'value2','key1' :'value1','key4' :'value4','key3' :'value3'};
Now, you can use the map or filter operators to pass each value individually to a function
c.map(function(x){x+2})
c.filter(function(x){x<5})

I'll suggest this:
foreach my_hash setting(key, value)
pre {
my_array.push("#{key}=#{value}");
}
See http://docs.kynetx.com/docs/Select

I believe I've figured this out, but the answer is a little bit of a hack.
hash_to_sorted_array = function(params, names, new_a) {
n = names.head();
val = params.pick("$.#{n}", true);
appended_array = new_a.append("#{n}=#{val.head()}");
finished_array = (names.length() == 0) => new_a |
hash_to_sorted_array(params, names.tail(), appended_array);
finished_array.sort()
}
This recursive function iterates over the names array, which contains the keys in the hash and removes the key that it processes in each iteration.
To call this function, just call:
sorted_array = hash_to_sorted_array(params, names, []);
For those unfamiliar with the head() and tail() methods of an array: head() gives you the first element in the array while tail() gives you a new array with the first element removed.
I don't really love this solution because you have to pass the names, or the keys, of the hash into the function as one of the arguments. As of this writing, I don't know of any way to extract just the keys from a hash, or I would just use that.

Related

terraform merge two maps with at least one common key

I am looking to merge two maps with at least one common key. The merge function overwrite the value of common key from first map.
I have two maps with one common key .
Code
locals {
map1 = {
"key1" = "value1",
"key2" = "value2"
}
map2 = {
"key1" = "value11"
}
}
output MergedMaps {
value = merge(local.map1, local.map2)
}
Output
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
MergedMaps = {
"key1" = "value11"
"key2" = "value2"
}
Desired output
MergedMaps = {
“key1” = [“value1”, “value11”]
“key2” = “value2”
}
Thanks for help
Its better to have same data structure in MergedMaps, rather then mixing lists and strings. So it would be
MergedMaps = {
“key1” = [“value1”, “value11”]
“key2” = [“value2”]
}
which can be obtained using
output MergedMaps {
value = {for key in distinct(concat(keys(local.map1), keys(local.map2))):
key => flatten([lookup(local.map1, key, []),
lookup(local.map2, key, [])
])
}
}

Add two dictionary values in new array . the array values will remains unchanged from its position

Add two dictionary values in a new array. the array values will remain unchanged from its position. I need to add two dictionary values in a new array. The values added in the array must remain constant at every run.
var dictionary1:[String:Int] = ["Mohan":75, "Raghu":82, "John":79]
var dictionary2:[String:Int] = ["Surya":91, "John":79, "Saranya":92]
dictionary1.merge(dictionary2){(current, _) in current}
var arr : [String] = []
for (key, value) in dictionary1 { arr.append("(key) (value)") }
print(dictionary1)
Simply use append(contentsOf:) to add the contents of dictionary1 and dictionary2 to arr. Use map(_:) to format the key-value pairs while adding to the array.
let dictionary1 = ["Mohan":75, "Raghu":82, "John":79]
let dictionary2 = ["Surya":91, "John":79, "Saranya":92]
var arr = [String]()
arr.append(contentsOf: dictionary1.map({"\($0.key) \($0.value)"}))
arr.append(contentsOf: dictionary2.map({"\($0.key) \($0.value)"}))
print(arr) //["Mohan 75", "John 79", "Raghu 82", "Surya 91", "John 79", "Saranya 92"]
As apple stats, you can use KeyValuePairs to maintain ordered collection of key-value pairs and don’t require the fast key lookup that the Dictionary type provides.
let recordTimes: KeyValuePairs = ["Florence Griffith-Joyner": 10.49,
"Evelyn Ashford": 10.76,
"Evelyn Ashford": 10.79,
"Marlies Gohr": 10.81]
print(recordTimes.first!)
// Prints "("Florence Griffith-Joyner", 10.49)"
Check if it is helpful in your case.

Cannot write to array inside while loop - Subscript is get only

I am having the error -
Cannot assign through subscript: subscript is read only
This occurs when trying to write elements of an array inside a while loop as can be seen in code below. I understand this could be caused by swift mutability however my understanding of this is poor/nil and I'm struggling to find documentation.
Code -
while (counter != ((localArray.count) + 1))
{
data[counter] = /// The error flags on this line for the data array
[
"type" : localArray[counter][0],
"details" : localArray[counter][1],
"cost" : localArray[counter][2]
]
counter = counter + 1
}
EDIT for D4ttatraya -
'data' is declared as
var data: [[String: String]]?
and localArray is a copy of the result of a function -
var result: [[String]] = []
let localArray = result
First instead of declaring type of data array, create empty data array
var data = [[String: String]]()
Second I would suggest you to replace while loop with for-each loop for each inner array inside localArray (then instead of using counter variable you can use elements inside each array).
for array in localArray {
}
Now in loop you can just append new element to data array
data.append(["type": array[0],
"details": array[1],
"cost": array[2]])
also at the beginning of the loop make sure that each array has three elements inside, if hasn't continue to other inner array
guard array.count == 3 else { continue }
So the whole for-each loop should look like this:
for array in localArray {
guard array.count == 3 else { continue }
data.append(["type": array[0],
"details": array[1],
"cost": array[2]])
}
You data is optional so you need to create it before you can add stuff to it, also you are enumerating through localArray in order and then just adding to the end of data so you can do something like this
var data = [[String: String]]();
for theLocal in localArray {
data.append([
"type" : theLocal[0],
"details" : theLocal[1],
"cost" : theLocal[2]
]);
}
data is an optional, but you're trying to access it as a non-optional value. Try data?[counter] or if var data = data { data[counter] = ... }

How to sort dictionary by value?

My dictionary like this:
var items = [Int: [String]]()
var itemsResult = [Int: [String]]()
itmesResult stores the data downloaded from server.
and pass the data to items use of items = itmesResult
the value has 3 elements like ["Apple","/image/apple.png","29"]
and I want to sort the dictionary by first value which is Apple.
for (k,v) in (itemsResult.sorted(by: { $0.value[0] < $1.value[0] })) {
items[k] = v
}
The result of above code is not my expectation.
I would like to sort it alphabetically how can I do this?
Edit:
origin:
1:["Apple","/image/apple.png","29"]
2:["AAA","/image/aaa.png","29"]
3:["Banana","/image/banana.png","29"]
sorted:
2:["AAA","/image/aaa.png","29"]
1:["Apple","/image/apple.png","29"]
3:["Banana","/image/banana.png","29"]
I would like to sort it by first value.
So if I take your example, this does the trick:
var items = [Int: [String]]()
items[0] = ["Apple","/image/apple.png","29"]
items[1] = ["AAA","/image/aaa.png","29"]
items[2] = ["Banana","/image/banana.png","29"]
let itemResult = items.sorted { (first: (key: Int, value: [String]), second: (key: Int, value: [String])) -> Bool in
return first.value.first! < second.value.first!
}
print (itemResult)
The right thing to do is to use objects of course, and note that I'm not null checking the "first" object in each array, but this is not a problem to change.
Let me know if this is what you were looking for, the output is:
[(1, ["AAA", "/image/aaa.png", "29"]), (0, ["Apple", "/image/apple.png", "29"]), (2, ["Banana", "/image/banana.png", "29"])]
EDIT:
Also note, that this case doesn't actually "sort" the dictionary, because a dictionary is by definition not sorted, this creates an array of key-value objects sorted using the array indexes
Instead of saving these variable into an array of arrays, make them an array of dictionaries.
You can do this like so:
var dictionaries:[Dictionary<String, String>] = []
for item in items {
let dictionary = {"name": item[0], "url": item[1], "id" : item[2]}
dictionaries.append(dictionary)
}
You can get your sorted list of dictionaries like this:
dictionaries.sorted(by: { $0["name"] < $1["name"] })

How to extract a subset of a swift 3 Dictionary

I've looked through the methods here but I can't quite find what I'm looking for. I'm new-ish to Swift. I would like to extract a subset from a Dictionary based on a Set of key values, preferably without a loop.
For example, if my key Set is of type Set<String> and I have a Dictionary of type Dictionary<String, CustomObject>, I would like to create a new Dictionary of type Dictionary<String, CustomObject> that contains only the key-value pairs associated with the keys in the Set of Strings.
I can see that I could do this with for loop, by initializing a new Dictionary<String, CustomObj>(), checking if the original Dictionary contains a value at each String in the set, and adding key-value pairs to the new Dictionary. I am wondering if there is a more efficient/elegant way to do this however.
I'd be open to finding the subset with an Array of Strings instead of a Set if there is a better way to do it with an Array of keys.
Many thanks!
Swift 5 - You can do this very simply:
let subsetDict = originalDict.filter({ mySet.contains($0.key)})
The result is a new dictionary with the same type as the original but which only contains the key-value pairs corresponding to the keys in mySet.
Your assumption is correct, there is a more concise/swift-ish way to accomplish what you need.
For example you can do it via reduce, a functional programming concept available in Swift:
let subDict = originalDict.reduce([String: CustomObject]()) {
guard mySet.contains($1.key) else { return $0 }
var d = $0
d[$1.key] = $1.value
return d
}
Or, in two steps, first filtering the valid elements, and then constructing back the dictionary with the filtered elements:
let filteredDict = originalDict.filter { mySet.contains($0.key) }
.reduce([CustomObject]()){ var d = $0; d[$1.key]=$1.value; return d }
forEach can also be used to construct the filtered dictionary:
var filteredDict = [CustomObject]()
mySet.forEach { filteredDict[$0] = originalDict[$0] }
, however the result would be good it it would be immutable:
let filteredDict: [String:CustomObject] = {
var result = [String:CustomObject]()
mySet.forEach { filteredDict2[$0] = originalDict[$0] }
return result
}()
Dummy type:
struct CustomObject {
let foo: Int
init(_ foo: Int) { self.foo = foo }
}
In case you'd like to mutate the original dictionary (instead of creating a new one) in an "intersect" manner, based on a given set of keys:
let keySet = Set(["foo", "baz"])
var dict = ["foo": CustomObject(1), "bar": CustomObject(2),
"baz": CustomObject(3), "bax": CustomObject(4)]
Set(dict.keys).subtracting(keySet).forEach { dict.removeValue(forKey: $0) }
print(dict) // ["foo": CustomObject(foo: 1), "baz": CustomObject(foo: 3)]