map duplicates key value pair if not present - dictionary

Following is my structure definition:
type test struct{
Title string
State string
Counts int
}
I want to map struct object members in following way
map[Title:map[State:Counts]]
This is the code which successfully do so
func main() {
r := make(map[string]map[string]int)
r1 := make(map[string]int)
var ts []test
ts = append(ts, test{Title: "Push 1",
State: "Active",
Counts: 20})
ts = append(ts, test{Title: "Push 1",
State: "InActive",
Counts: 20})
ts = append(ts, test{Title: "Push 1",
State: "Checked",
Counts: 20})
ts = append(ts, test{Title: "Push 1",
State: "Active",
Counts: 23})
ts = append(ts, test{Title: "Push 2",
State: "Active",
Counts: 20})
ts = append(ts, test{Title: "Push 2",
State: "InActive",
Counts: 23})
for _, t := range ts {
r1[t.State] = t.Counts
r[t.Title] = r1
}
fmt.Println("struct: ", ts)
fmt.Println("map: ", r)
}
Problem I am facing is Title "Push 2" which does not have a State: Checked is been appended with Count value of previous object.
Following output is as follows
struct: [{Push 1 Active 20} {Push 1 InActive 20} {Push 1 Checked 20} {Push 1 Active 23} {Push 2 Active 20} {Push 2 InActive 23}]
map: map[Push 1:map[Active:20 Checked:20 InActive:23] Push 2:map[Active:20 Checked:20 InActive:23]]
Code I compiled is in go playground.

r := make(map[string]map[string]int) only creates a single map, it has no entries.
r1 := make(map[string]int) also only creates a single map to count states, but you don't need only one, you need a separate one for each distinct title.
So instead of creating that single r1, create inner maps on demand. Range over your structs, and when there is no inner map for its title, then create one and store it in the outer r map.
Like this:
for _, t := range ts {
counts := r[t.Title]
if counts == nil {
counts = make(map[string]int)
r[t.Title] = counts
}
counts[t.State]++
}
Note that the counting operation may simply be counts[t.State]++.
With this the output will be (try it on the Go Playground):
map: map[Push 1:map[Active:2 Checked:1 InActive:1] Push 2:map[Active:1 InActive:1]]

Related

Update expression for a list of maps in DynamoDB

I have the following DynamoDB table:
{
record_id: Decimal(10),
...,
options: [ # This is a List of maps
{option_id: 1, counter: Decimal(0), ...},
{option_id: 2, counter: Decimal(0), ...},
],
...
}
Which consists of some items, with unique record_id and the target options list. That list contains maps. In those maps, there is an option_id attribute, and I would like to access the item in the options list whose option_id equals to some target my_option_id and increment its counter.
For example, for the above example, given my_record_id=10 and my_option_id=2, I would like to update the second option item, with option_id=2, and increment its counter by 1, so this {option_id: 2, counter: Decimal(0), ...} becomes {option_id: 2, counter: Decimal(1), ...}.
I am using Python and boto3, but I imagine the syntax here is specific to DynamoDB. Here is what I have so far:
response = table.update_item(
Key={
'record_id': my_record_id,
},
UpdateExpression='SET options.#s.counter = options.#s.counter + :val',
ExpressionAttributeNames={
"#s": my_option_id
},
ExpressionAttributeValues={
':val': Decimal(1)
},
ReturnValues="UPDATED_NEW"
)
It seems the easy fix was to make options a map instead of a list, and then make the option_id the key of that map. Then my code works as expected:
response = table.update_item(
Key={
'record_id': my_record_id,
},
UpdateExpression='SET options.#s.counter = options.#s.counter + :val',
ExpressionAttributeNames={
"#s": my_option_id
},
ExpressionAttributeValues={
':val': Decimal(1)
},
ReturnValues="UPDATED_NEW"
)

Conditional if/then/else for JMESPath?

Am trying to do a simple if/then/else using JMESPath
For example: 'if the input is a string, return the string, else return the "value" property of the input'. An input of "abc" would return "abc". An input of {"value":"def"} would return "def"
With jq this is easy: if .|type == "string" then . else .value end
With JMESPath, I can get the type
type(#)
or the input:
#
or the value property:
value
but I have not found a way to combine them into an if-then-else. Is there any way to do this?
It is possible but not cleanly. The general form is to:
Make the value you are testing an array (wrap in square braces)
Apply the map function to map the filtered array to what value you want if true
At this point you have an array that is populated with one (true) item if the array filter passed, otherwise it is empty
Concat to that array one item (the false value)
Finally, take item at index 0 in this array - which will be the result of the condition
This should allow you to also derive possible transformations for both the false and true conditions
For example, if the test data is as so:
{
"test": 11
}
Depending on the value you can get either produce the results (using test data 11 and 2 as example):
"Yes, the value is 11 which is greater than 10"
OR
"No the value is 2 which is less than or equal to 10"
Like so:
[
map(
&join(' ', ['Yes, the value is', to_string(#), 'which is greater than 10']),
[test][? # > `10`]
),
join(' ', ['No the value is', to_string(test), ' which is less than or equal to 10'])
][] | #[0]
So to abstract a template:
[
map(
&<True Expression Here>,
[<Expression you are testing>][? # <Test Expression>]
),
<False Expression Here>)
][] | #[0]
people[?general.id !=100] || people
{
"people": [
{
"general": {
"id": 100,
"age": 20,
"other": "foo",
"name": "Bob"
},
"history": {
"first_login": "2014-01-01",
"last_login": "2014-01-02"
}
},
{
"general": {
"id": 101,
"age": 30,
"other": "bar",
"name": "Bill"
},
"history": {
"first_login": "2014-05-01",
"last_login": "2014-05-02"
}
}
]
}
if else condition works here

Updating map value for a specific key in Kotlin

I am new at Kotlin and trying to catch up the language.
I have a function that returns DayofWeek as a key and an Int as a value.
The issue that I am facing is that I need to take a list of object that has another list of object inside that has an Int value which I need to save and increment for every time I see the same value.
Here is my function -
class OrdersAnalyzer {
data class Order(val orderId: Int, val creationDate: LocalDateTime, val orderLines: List<OrderLine>)
data class OrderLine(val productId: Int, val name: String, val quantity: Int, val unitPrice: BigDecimal)
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> {
val map: MutableMap<DayOfWeek, Int>? = mutableMapOf(
Pair(DayOfWeek.SUNDAY, 0),
Pair(DayOfWeek.MONDAY, 0),
Pair(DayOfWeek.TUESDAY, 0),
Pair(DayOfWeek.WEDNESDAY, 0),
Pair(DayOfWeek.THURSDAY, 0),
Pair(DayOfWeek.FRIDAY, 0),
Pair(DayOfWeek.SATURDAY, 0)
)
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
var quantity = 0
map?.put(dayOfWeek, quantity)
}
return map!!
}
}
So the issues I am facing right now are 2 -
1) How can I increment the value of each pair when it is the corrent DayOfWeek? I don't want to replace, I want to add it to the last value.
2) When returning the Map, I do not want to return the DayOfWeeks that have the value of 0. How can I do that?
Here is a modified version of the elegant answer provided by Arjan, with some test code.
Updated the answer after the comment from Alon Shlider -- now counting all order item quantities grouped by day of the week:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { sumItemQuantities(it.value) }
fun sumItemQuantities(orders: List<Order>) =
orders.flatMap { it.orderLines.map { line -> line.quantity } }.sum()
fun main() {
val orders = listOf(
Order(
1,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(6, "laptop", 28, 1200.toBigDecimal())
)
),
Order(
2,
LocalDateTime.now().minusDays(1),
listOf(
OrderLine(496, "VR headset", 6, 400.toBigDecimal())
)
)
)
println(totalDailySales(orders))
}
Output:
{FRIDAY=28, SATURDAY=6}
With this approach, Kotlin functions do the grouping and counting for you. The groupBy function creates a map from DayOfWeek to a list of orders (grouping all orders with the same day of week in a list). The mapValues function transforms that map by replacing the lists with the result of the sumItemQuantities function (for each list).
In the for loop in your code, you can retrieve the current quantity for a specific day (or use zero if it isn't set yet), increase it by the right amount and then store it. To return only the map entries with non zero values, you could filter (return totalsPerDay.filter { it.value > 0 }) or start with an empty map. This is your function with some changes:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> {
val totalsPerDay = mutableMapOf<DayOfWeek, Int>()
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
val currentQuantity = totalsPerDay[dayOfWeek] ?: 0
// This is not the best way to increment by the sum of the order
// item quantities...
val orderItemQuantities = sumItemQuantities(listOf(order))
totalsPerDay[dayOfWeek] = currentQuantity + orderItemQuantities
}
return totalsPerDay
}
Output after calling it:
println(OrdersAnalyzer().totalDailySales(orders))
{FRIDAY=28, SATURDAY=6}
Updated answer (also thanks to Freek de Bruijn), with some test code.
I think it would be something like this:
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { it.value.flatMap { it.orderLines } }
.mapValues { it.value.map { it.quantity } }
.mapValues { it.value.sum() }
groupBy creates a Map where the values are of type List<Order>, so you need to call a few steps to convert these values to Int. First we use flatMap to convert List<Order> to List<OrderLine> (map would convert to List<List<OrderLine>>). Then we use map to get the quantities out of List<OrderLine>, and finally sum() to add up all those quantities.
val orders = listOf(
Order(
2,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(5, "monitor", 10, 200.toBigDecimal()),
OrderLine(4, "keyboard", 5, 50.toBigDecimal())
)
)
)
println(totalDailySales(orders))
This results in the output:
{FRIDAY=15}

How to convert List to Map in Kotlin?

For example I have a list of strings like:
val list = listOf("a", "b", "c", "d")
and I want to convert it to a map, where the strings are the keys.
I know I should use the .toMap() function, but I don't know how, and I haven't seen any examples of it.
You have two choices:
The first and most performant is to use associateBy function that takes two lambdas for generating the key and value, and inlines the creation of the map:
val map = friends.associateBy({it.facebookId}, {it.points})
The second, less performant, is to use the standard map function to create a list of Pair which can be used by toMap to generate the final map:
val map = friends.map { it.facebookId to it.points }.toMap()
From List to Map with associate function
With Kotlin 1.3, List has a function called associate. associate has the following declaration:
fun <T, K, V> Iterable<T>.associate(transform: (T) -> Pair<K, V>): Map<K, V>
Returns a Map containing key-value pairs provided by transform function applied to elements of the given collection.
Usage:
class Person(val name: String, val id: Int)
fun main() {
val friends = listOf(Person("Sue Helen", 1), Person("JR", 2), Person("Pamela", 3))
val map = friends.associate({ Pair(it.id, it.name) })
//val map = friends.associate({ it.id to it.name }) // also works
println(map) // prints: {1=Sue Helen, 2=JR, 3=Pamela}
}
From List to Map with associateBy function
With Kotlin, List has a function called associateBy. associateBy has the following declaration:
fun <T, K, V> Iterable<T>.associateBy(keySelector: (T) -> K, valueTransform: (T) -> V): Map<K, V>
Returns a Map containing the values provided by valueTransform and indexed by keySelector functions applied to elements of the given collection.
Usage:
class Person(val name: String, val id: Int)
fun main() {
val friends = listOf(Person("Sue Helen", 1), Person("JR", 2), Person("Pamela", 3))
val map = friends.associateBy(keySelector = { person -> person.id }, valueTransform = { person -> person.name })
//val map = friends.associateBy({ it.id }, { it.name }) // also works
println(map) // prints: {1=Sue Helen, 2=JR, 3=Pamela}
}
If you have duplicates in your list that you don't want to lose, you can do this using groupBy.
Otherwise, like everyone else said, use associate/By/With (which in the case of duplicates, I believe, will only return the last value with that key).
An example grouping a list of people by age:
class Person(val name: String, val age: Int)
fun main() {
val people = listOf(Person("Sue Helen", 31), Person("JR", 25), Person("Pamela", 31))
val duplicatesKept = people.groupBy { it.age }
val duplicatesLost = people.associateBy({ it.age }, { it })
println(duplicatesKept)
println(duplicatesLost)
}
Results:
{31=[Person#41629346, Person#4eec7777], 25=[Person#3b07d329]}
{31=Person#4eec7777, 25=Person#3b07d329}
Convert a Iteratable Sequence Elements to a Map in kotlin ,
associate vs associateBy vs associateWith:
*Reference:Kotlin Documentation
1- associate (to set both Keys & Values): Build a map that can set key & value elements :
IterableSequenceElements.associate { newKey to newValue } //Output => Map {newKey : newValue ,...}
If any of two pairs would have the same key the last one gets added to the map.
The returned map preserves the entry iteration order of the original array.
2- associateBy (just set Keys by calculation): Build a map that we can set new Keys, analogous elements will be set for values
IterableSequenceElements.associateBy { newKey } //Result: => Map {newKey : 'Values will be set from analogous IterableSequenceElements' ,...}
3- associateWith (just set Values by calculation): Build a map that we can set new Values, analogous elements will be set for Keys
IterableSequenceElements.associateWith { newValue } //Result => Map { 'Keys will be set from analogous IterableSequenceElements' : newValue , ...}
Example from Kotlin tips :
You can use associate for this task:
val list = listOf("a", "b", "c", "d")
val m: Map<String, Int> = list.associate { it to it.length }
In this example, the strings from list become the keys and their corresponding lengths (as an example) become the values inside the map.
That have changed on the RC version.
I am using val map = list.groupByTo(destinationMap, {it.facebookId}, { it -> it.point })

Groovy Collections: count weirdness with Strings

I am trying to count string values in a member of an object. I have tried three ways, but only one works. I am fine with the one that works, but I can't understand why the others fail. Here's the code:
void testCount() {
TestObj a = new TestObj()
TestObj b = new TestObj()
TestObj c = new TestObj()
a.s = "one"
b.s = "two"
c.s = "two"
def list = [a, b, c]
def count = 0
list.each{
if (it.s.equals("two"))
count++
}
assertTrue("each test failed", count == 2)
assertTrue("collectAll test failed", list.collectAll{ it.s.equals("two")}.size() == 2)
assertTrue("count test failed", list.count{ it.s.equals("two")} == 2)
}
I would expect the Closures passed to collectAll and count to do the same thing I'm doing in my each method. But in the case of collectAll it returns all 3 of the objects and in the case of count it always returns 0.
What am I missing?
collectAll is recursively going through your list, and returning a boolean (as that is what your closure returns for each element in the List)...
So, you get [ false, true, true ], which has 3 elements...
For count,
list.count{ it.s == "two" }
Returns 2 (as expected)
btw: you can do it.s == 'two' in groovy.. no need for all the .equals( "two" )
Edit... Example for count:
class TestObj {
String s
}
list = [ new TestObj( s:'one' ), new TestObj( s:'two' ), new TestObj( s:'two' ) ]
println( list.count { it.s == 'two' } )
Prints 2 for me...
edit 2
Found the cause (from comment below), count didn't accept a closure as a parameter till 1.8 so you'll be calling the object version which will tell you how many times an instance of the closure exists in the list (which is none, as it says)

Resources