Extract value from key value pairs and convert to CSV - unix

I have below data set,
data:[{'name': 'cable', 'status': 'none'}, {'name': 'laptop', 'status': 'loaded', 'mode': 'high'}
{'name': 'samsung', 'status': 'none'}], location:[{'place': 'chennai', 'distance': '100km'},
{'place': 'bangalore', 'distance': '200km'}]
Am trying to extract the values and convert it into CSV. Am facing issues while converting as its multidimensional array. Any suggestion would be helpful.
If my data is just {'name': 'cable', 'status': 'none'}, {'name': 'laptop', 'status': 'loaded', 'mode': 'high'}, am able to get it through awk using below,
awk -F " = " -v OFS="," '
BEGIN { print "name","status","mode","place","distance" }
function printline() {
print data["name"], data["status"], data["mode"]
}
{data[$1] = $2}
NF == 0 {printline(); delete data}
END {printline()}
'
But am unable to get it with my original data set,
Original data,
data:[{'name': 'cable', 'status': 'none'}, {'name': 'laptop', 'status': 'loaded', 'mode': 'high'}
{'name': 'samsung', 'status': 'none'}], location:[{'place': 'chennai', 'distance': '100km'},
{'place': 'bangalore', 'distance': '200km'}]
Expected result,
name status mode place distance
cable none null chennai 100km
laptop loaded high bangalore 200km
samsung none null null null

Here's a start with a step-by-step approach using any awk in any shell on all UNIX boxes:
$ cat tst.awk
{ rec = (NR>1 ? rec " " : "") $0 }
END {
# Identify from rec:
# 1) [{'name': 'cable', 'status': 'none'}, {'name': 'laptop', 'status': 'loaded', 'mode': 'high'} {'name': 'samsung', 'status': 'none'}]
# 2) [{'place': 'chennai', 'distance': '100km'}, {'place': 'bangalore', 'distance': '200km'}]
str = rec
while ( match(str,/\[[^]]+/) ) {
val = substr(str,RSTART+1,RLENGTH-1)
level1vals[++numLevel1vals] = val
str = substr(str,RSTART+RLENGTH)
}
for (level1valNr=1; level1valNr<=numLevel1vals; level1valNr++) {
level1val = level1vals[level1valNr]
# Identify from level1vals[1]:
# 1) 'name': 'cable', 'status': 'none'
# 2) 'name': 'laptop', 'status': 'loaded', 'mode': 'high'
# 3) 'name': 'samsung', 'status': 'none'
# and from level1vals[2]:
# 4) 'place': 'chennai', 'distance': '100km'
# 5) 'place': 'bangalore', 'distance': '200km'
level2valNr = 0
str = level1val
while ( match(str,/{[^}]+/) ) {
val = substr(str,RSTART+1,RLENGTH-1)
++level2valNr
level2vals[level2valNr] = level2vals[level2valNr] " " val
numLevel2vals = (level2valNr > numLevel2vals ? level2valNr : numLevel2vals)
str = substr(str,RSTART+RLENGTH)
}
}
# NOTE: delete these print loops when done testing/debugging
for (level1valNr=1; level1valNr<=numLevel1vals; level1valNr++) {
print "level1vals[" level1valNr "] = <" level1vals[level1valNr] ">"
}
print ""
for (level2valNr=1; level2valNr<=numLevel2vals; level2valNr++) {
print "level2vals[" level2valNr "] = <" level2vals[level2valNr] ">"
}
}
.
$ awk -f tst.awk file
level1vals[1] = <{'name': 'cable', 'status': 'none'}, {'name': 'laptop', 'status': 'loaded', 'mode': 'high'} {'name': 'samsung', 'status': 'none'}>
level1vals[2] = <{'place': 'chennai', 'distance': '100km'}, {'place': 'bangalore', 'distance': '200km'}>
level2vals[1] = < 'name': 'cable', 'status': 'none' 'place': 'chennai', 'distance': '100km'>
level2vals[2] = < 'name': 'laptop', 'status': 'loaded', 'mode': 'high' 'place': 'bangalore', 'distance': '200km'>
level2vals[3] = < 'name': 'samsung', 'status': 'none'>
Add another round of looping using match($0,/\047[^\047]+/) to identify each 'foo' string, store in an array and then loop through that final array in the appropriate order to print the CSV.

Here is a bash/perl script to transform the original data to the "expected result" format. To make it produce result in CSV format, just change $DLMTR="\t" to $DLMTR=",":
% cat data.txt
data:[{'name': 'cable', 'status': 'none'}, {'name': 'laptop', 'status': 'loaded', 'mode': 'high'}
{'name': 'samsung', 'status': 'none'}], location:[{'place': 'chennai', 'distance': '100km'},
{'place': 'bangalore', 'distance': '200km'}]
% cat transform_data.sh
#!/usr/bin/bash
cat $* | tr "," "\n" | perl -lne '
BEGIN {
$i=$j=$data=$location=0;
# Change $DLMTR (delimiter) from "\t" (Tab) to "," for CSV format
$DLMTR="\t"
}
if (/data:/) {$data=1};
if (/location:/) {$location = 1; $data = 0;};
if ($data) { # process elements within data:[]
# \047 = single-quote and change to \042 if double-quote is required
$i++ if /\{/;
/\047name\047:/ && do { $name[$i]=$status[$i]=$mode[$i]=$place[$i]=$distance[$i]="null";
($name[$i])=/:\s*\047(.+?)\047/};
/\047status\047:/ && do {($status[$i])=/:\s*\047(.+?)\047/};
/\047mode\047:/ && do {($mode[$i])=/:\s*\047(.+?)\047/};
}
elsif ($location) { # process elements within location:[]
$j++ if /\{/;
/\047place\047:/ && do {($place[$j])=/:\s*\047(.+?)\047/};
/\047distance\047:/ && do {($distance[$j])=/:\s*\047(.+?)\047/;};
}
END {
print "name${DLMTR}status${DLMTR}mode${DLMTR}place${DLMTR}distance";
foreach $n (1..$i) {
print "$name[$n]${DLMTR}$status[$n]${DLMTR}$mode[$n]${DLMTR}$place[$n]${DLMTR}$distance[$n]";
}}'
% transform_data.sh data.txt
name status mode place distance
cable none null chennai 100km
laptop loaded high bangalore 200km
samsung none null null null

Related

JQ: Selection with conditions in a nested hash

How to get a city name specifying the name of a language with the additional condition of "spoken" or "perhaps"?
{
"Paris": {
"language": {
"fr": "spoken",
"en": "perhaps"
}
},
"London": {
"language": {
"en": "spoken",
"fr": "perhaps",
"ru": "unused"
}
},
"Moscow": {
"language": {
"ru": "spoken",
"en": "perhaps",
"fr": "unused"
}
}
}
E.g:
Input: en;
Output: Paris, London, Moscow
Input: fr;
Output: Paris, London
Input: ru;
Output: Moscow
Convert all entries to an object with key/value fields using to_entries, retain only those objects that do match your conditions (in the .value's object .language the value of a field $lang provided as input variable using --arg equals any one of "spoken" or "perhaps") and output the original entry's .key.
jq -r --arg lang "fr" '
to_entries[]
| select([.value.language[$lang] == ("spoken","perhaps")] | any)
| .key
' input.json
Try it at jqplay.org

How to sort Map<String, Map<String, int>> Dart

I would love to sort value of Map inside the main Map
var data = {
'Data 1' : {'people': 200, 'disctrict': 20},
'Data 2' : {'people': 300, 'disctrict': 12},
'Data 3' : {'people': 520, 'disctrict': 10},
};
Now I want to sort based on people value.
So the expected result should be like this:
var data = {
'Data 3' : {'people': 520, 'disctrict': 10},
'Data 2' : {'people': 300, 'disctrict': 12},
'Data 1' : {'people': 200, 'disctrict': 20},
};
There are probably much better solutions out there, but something like this. You'll have to convert the map to a list to sort it.
void main() {
var data = {
'Data 1' : {'people': 200, 'disctrict': 20},
'Data 2' : {'people': 300, 'disctrict': 12},
'Data 3' : {'people': 520, 'disctrict': 10},
};
print('unsorted: $data');
List<Map<String,dynamic>> listData = [];
data.forEach((key, value) => listData.add({key: value}));
listData.sort((a, b) {
int aValue = a.values.first["people"];
int bValue = b.values.first["people"];
print('a values: $aValue');
print('b values: $bValue');
return aValue < bValue ? 1 : 0;
});
var sortedMap = Map.fromIterable(listData, key: (e) => e.keys.first, value: (e) => e.values.first);
print('sorted: $sortedMap');
}
Map is a LinkedHashMap and stores entries in insertion order. If you want a different order, you will need to use Map.entries, convert it to a List, sort it, and then add them to a new Map (or clear the old one and add to that). Something like:
var mapEntries = data.entries.toList();
mapEntries.sort((a, b) => b.value['people'].compareTo(a.value['people']));
data = Map.fromEntries(mapEntries);

Find path to object in object nested array

I have an object, of which parameters contain and array of object. I receive 1 object id and I need to find its position in that whole mess. With procedural programming I got it working with:
const opportunitiesById = {
1: [
{ id: 1, name: 'offer 1' },
{ id: 2, name: 'offer 1' }
],
2: [
{ id: 3, name: 'offer 1' },
{ id: 4, name: 'offer 1' }
],
3: [
{ id: 5, name: 'offer 1' },
{ id: 6, name: 'offer 1' }
]
};
const findObjectIdByOfferId = (offerId) => {
let opportunityId;
let offerPosition;
const opportunities = Object.keys(opportunitiesById);
opportunities.forEach(opportunity => {
const offers = opportunitiesById[opportunity];
offers.forEach((offer, index) => {
if (offer.id === offerId) {
opportunityId = Number(opportunity);
offerPosition = index;
}
})
});
return { offerPosition, opportunityId };
}
console.log(findObjectIdByOfferId(6)); // returns { offerPosition: 1, opportunityId: 3 }
However this is not pretty and I want to do that in a functional way.
I've looked into Ramda, and I can find an offer when I'm looking into a single array of offers, but I can't find a way to look through the entire object => each array to find the path to my offer.
R.findIndex(R.propEq('id', offerId))(opportunitiesById[1]);
The reason I need to know the path is because I then need tho modify that offer with new data and update it back where it is.
Thanks for any help
You could piece it together using lots of little functions but I want to show you how to encode your intentions in a more straightforward way. This program has an added benefit that it will return immediately. Ie, it will not continue to search thru additional key/value pairs after a match is found.
Here's a way you can do it using mutual recursion. First we write findPath -
const identity = x =>
x
const findPath =
( f = identity
, o = {}
, path = []
) =>
Object (o) === o
? f (o) === true
? path
: findPath1 (f, Object .entries (o), path)
: undefined
If the input is an object, we pass it to the user's search function f. If the user's search function returns true, a match has been found and we return the path. If there is not match, we search each key/value pair of the object using a helper function. Otherwise, if the input is not an object, there is no match and nothing left to search, so return undefined. We write the helper, findPath1 -
const None =
Symbol ()
const findPath1 =
( f = identity
, [ [ k, v ] = [ None, None ], ...more ]
, path = []
) =>
k === None
? undefined
: findPath (f, v, [ ...path, k ])
|| findPath1 (f, more, path)
If the key/value pairs have been exhausted, there is nothing left to search so return undefined. Otherwise we have a key k and a value v; append k to the path and recursively search v for a match. If there is not a match, recursively search the remaining key/values, more, using the same path.
Note the simplicity of each function. There's nothing happening except for the absolute minimum number of steps to assemble a path to the matched object. You can use it like this -
const opportunitiesById =
{ 1:
[ { id: 1, name: 'offer 1' }
, { id: 2, name: 'offer 1' }
]
, 2:
[ { id: 3, name: 'offer 1' }
, { id: 4, name: 'offer 1' }
]
, 3:
[ { id: 5, name: 'offer 1' }
, { id: 6, name: 'offer 1' }
]
}
findPath (offer => offer.id === 6, opportunitiesById)
// [ '3', '1' ]
The path returned leads us to the object we wanted to find -
opportunitiesById['3']['1']
// { id: 6, name: 'offer 1' }
We can specialize findPath to make an intuitive findByOfferId function -
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
findByOfferId (3, opportunitiesById)
// [ '2', '0' ]
opportunitiesById['2']['0']
// { id: 3, name: 'offer 1' }
Like Array.prototype.find, it returns undefined if a match is never found -
findByOfferId (99, opportunitiesById)
// undefined
Expand the snippet below to verify the results in your own browser -
const identity = x =>
x
const None =
Symbol ()
const findPath1 =
( f = identity
, [ [ k, v ] = [ None, None ], ...more ]
, path = []
) =>
k === None
? undefined
: findPath (f, v, [ ...path, k ])
|| findPath1 (f, more, path)
const findPath =
( f = identity
, o = {}
, path = []
) =>
Object (o) === o
? f (o) === true
? path
: findPath1 (f, Object .entries (o), path)
: undefined
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
const opportunitiesById =
{ 1:
[ { id: 1, name: 'offer 1' }
, { id: 2, name: 'offer 1' }
]
, 2:
[ { id: 3, name: 'offer 1' }
, { id: 4, name: 'offer 1' }
]
, 3:
[ { id: 5, name: 'offer 1' }
, { id: 6, name: 'offer 1' }
]
}
console .log (findByOfferId (3, opportunitiesById))
// [ '2', '0' ]
console .log (opportunitiesById['2']['0'])
// { id: 3, name: 'offer 1' }
console .log (findByOfferId (99, opportunitiesById))
// undefined
In this related Q&A, I demonstrate a recursive search function that returns the matched object, rather than the path to the match. There's other useful tidbits to go along with it so I'll recommend you to give it a look.
Scott's answer inspired me to attempt an implementation using generators. We start with findPathGen -
const identity = x =>
x
const findPathGen = function*
( f = identity
, o = {}
, path = []
)
{ if (Object (o) === o)
if (f (o) === true)
yield path
else
yield* findPathGen1 (f, Object .entries (o), path)
}
And using mutual recursion like we did last time, we call on helper findPathGen1 -
const findPathGen1 = function*
( f = identity
, entries = []
, path = []
)
{ for (const [ k, v ] of entries)
yield* findPathGen (f, v, [ ...path, k ])
}
Finally, we can implement findPath and the specialization findByOfferId -
const first = ([ a ] = []) =>
a
const findPath = (f = identity, o = {}) =>
first (findPathGen (f, o))
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
It works the same -
findPath (offer => offer.id === 3, opportunitiesById)
// [ '2', '0' ]
findPath (offer => offer.id === 99, opportunitiesById)
// undefined
findByOfferId (3, opportunitiesById)
// [ '2', '0' ]
findByOfferId (99, opportunitiesById)
// undefined
And as a bonus, we can implement findAllPaths easily using Array.from -
const findAllPaths = (f = identity, o = {}) =>
Array .from (findPathGen (f, o))
findAllPaths (o => o.id === 3 || o.id === 6, opportunitiesById)
// [ [ '2', '0' ], [ '3', '1' ] ]
Verify the results by expanding the snippet below
const identity = x =>
x
const findPathGen = function*
( f = identity
, o = {}
, path = []
)
{ if (Object (o) === o)
if (f (o) === true)
yield path
else
yield* findPathGen1 (f, Object .entries (o), path)
}
const findPathGen1 = function*
( f = identity
, entries = []
, path = []
)
{ for (const [ k, v ] of entries)
yield* findPathGen (f, v, [ ...path, k ])
}
const first = ([ a ] = []) =>
a
const findPath = (f = identity, o = {}) =>
first (findPathGen (f, o))
const findByOfferId = (q = 0, data = {}) =>
findPath (o => o.id === q, data)
const opportunitiesById =
{ 1:
[ { id: 1, name: 'offer 1' }
, { id: 2, name: 'offer 1' }
]
, 2:
[ { id: 3, name: 'offer 1' }
, { id: 4, name: 'offer 1' }
]
, 3:
[ { id: 5, name: 'offer 1' }
, { id: 6, name: 'offer 1' }
]
}
console .log (findByOfferId (3, opportunitiesById))
// [ '2', '0' ]
console .log (findByOfferId (99, opportunitiesById))
// undefined
// --------------------------------------------------
const findAllPaths = (f = identity, o = {}) =>
Array .from (findPathGen (f, o))
console .log (findAllPaths (o => o.id === 3 || o.id === 6, opportunitiesById))
// [ [ '2', '0' ], [ '3', '1' ] ]
I would transform your object into pairs.
So for example transforming this:
{ 1: [{id:10}, {id:20}],
2: [{id:11}, {id:21}] }
into that:
[ [1, [{id:10}, {id:20}]],
[2, [{id:11}, {id:21}]] ]
Then you can iterate over that array and reduce each array of offers to the index of the offer you're looking for. Say you're looking for offer #21, the above array would become:
[ [1, -1],
[2, 1] ]
Then you return the first tuple which second element isn't equal to -1:
[2, 1]
Here's how I'd suggest doing this:
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' },
{ id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' },
{ id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' },
{ id: 22, name: 'offer 6' } ]
};
const findOfferPath = (id, offers) =>
pipe(
toPairs,
transduce(
compose(
map(over(lensIndex(1), findIndex(propEq('id', id)))),
reject(pathEq([1], -1)),
take(1)),
concat,
[]))
(offers);
console.log(findOfferPath(21, opportunitiesById));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {pipe, transduce, compose, map, over, lensIndex, findIndex, propEq, reject, pathEq, take, concat, toPairs} = R;</script>
Then you can take that path to modify your offer as you see fit:
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' },
{ id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' },
{ id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' },
{ id: 22, name: 'offer 6' } ]
};
const updateOffer = (path, update, offers) =>
over(lensPath(path), assoc('name', update), offers);
console.log(updateOffer(["2", 1], '🌯', opportunitiesById));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {over, lensPath, assoc} = R;</script>
Here's another approach:
We start with this generator function:
function * getPaths(o, p = []) {
yield p
if (Object(o) === o)
for (let k of Object .keys (o))
yield * getPaths (o[k], [...p, k])
}
which can be used to find all the paths in an object:
const obj = {a: {x: 1, y: 3}, b: {c: 2, d: {x: 3}, e: {f: {x: 5, g: {x: 3}}}}}
;[...getPaths(obj)]
//~> [[], ["a"], ["a", "x"], ["a", "y"], ["b"], ["b", "c"], ["b", "d"],
// ["b", "d", "x"], ["b", "e"], ["b", "e", "f"], ["b", "e", "f", "x"],
// ["b", "e", "f", "g"], ["b", "e", "f", "g", "x"]]
and then, with this little helper function:
const path = (ps, o) => ps.reduce((o, p) => o[p] || {}, o)
we can write
const findPath = (predicate, o) =>
[...getPaths(o)] .find (p => predicate (path (p, o) ) )
which we can call like
console.log(
findPath (a => a.x == 3, obj)
) //~> ["b","d"]
We can then use these functions to write a simple version of your function:
const findByOfferId = (id, data) =>
findPath (o => o.id === id, data)
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' }, { id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' }, { id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' }, { id: 22, name: 'offer 6' } ]
}
console.log(
findByOfferId (22, opportunitiesById)
) //~> ["3", "1"]
console.log(
findByOfferId (42, opportunitiesById)
) //~> undefined
It is trivial to extend this to get all paths for which the value satisfies the predicate, simply replacing find with filter:
const findAllPaths = (predicate, o) =>
[...getPaths(o)] .filter (p => predicate (path(p, o) ) )
console.log(
findAllPaths (a => a.x == 3, obj)
) //=> [["b","d"],["b","e","f","g"]]
There is a concern with all this, though. Even though findPath only needs to find the first match, and even though getPaths is a generator and hence lazy, we force the full run of it with [...getPaths(o)]. So it might be worth using this uglier, more imperative version:
const findPath = (predicate, o) => {
let it = getPaths(o)
let res = it.next()
while (!res.done) {
if (predicate (path (res.value, o) ) )
return res.value
res = it.next()
}
}
This is what it looks like all together:
function * getPaths(o, p = []) {
yield p
if (Object(o) === o)
for (let k of Object .keys (o))
yield * getPaths (o[k], [...p, k])
}
const path = (ps, o) => ps.reduce ((o, p) => o[p] || {}, o)
// const findPath = (pred, o) =>
// [...getPaths(o)] .find (p => pred (path (p, o) ) )
const findPath = (predicate, o) => {
let it = getPaths(o)
let res = it.next()
while (!res.done) {
if (predicate (path (res.value, o) ) )
return res.value
res = it.next()
}
}
const obj = {a: {x: 1, y: 3}, b: {c: 2, d: {x: 3}, e: {f: {x: 5, g: {x: 3}}}}}
console.log(
findPath (a => a.x == 3, obj)
) //~> ["b","d"]
const findAllPaths = (pred, o) =>
[...getPaths(o)] .filter (p => pred (path(p, o) ) )
console.log(
findAllPaths (a => a.x == 3, obj)
) //~> [["b","d"],["b","e","f","g"]]
const findByOfferId = (id, data) =>
findPath (o => o.id === id, data)
const opportunitiesById = {
1: [ { id: 10, name: 'offer 1' }, { id: 20, name: 'offer 2' } ],
2: [ { id: 11, name: 'offer 3' }, { id: 21, name: 'offer 4' } ],
3: [ { id: 12, name: 'offer 5' }, { id: 22, name: 'offer 6' } ]
}
console.log(
findByOfferId (22, opportunitiesById)
) //~> ["3", "1"]
console.log(
findByOfferId (42, opportunitiesById)
) //~> undefined
Another brief note: the order in which the paths are generated is only one possibility. If you want to change from pre-order to post-order, you can move the yield p line in getPaths from the first line to the last one.
Finally, you asked about doing this with functional techniques, and mentioned Ramda. As the solution from customcommander shows, you can do this with Ramda. And the (excellent as always) answer from user633183 demonstrates, it's possible to do this with mainly functional techniques.
I still find this a somewhat simpler approach. Kudos to customcommander for finding a Ramda version, because Ramda is not particularly well-suited for recursive tasks, but still the obvious approach to something that has to visit the nodes of a recursive structure like a JS object is to use a recursive algorithm. I'm one of the authors of Ramda, and I haven't even tried to understand how that solution works.
Update
user633183 pointed out that this would be simpler, and still lazy:
const findPath = (predicate, o) => {
for (const p of getPaths(o))
if (predicate (path (p, o)) )
return p
}

Ordering the results by the range key in DynamoDB does not work

I have a table definition like the following:
ATTRIBUTE_NAME, ATTRIBUTE_TYPE = 'AttributeName', 'AttributeType'
my_table = dynamodb.create_table(
TableName='my_table',
KeySchema=[
{
ATTRIBUTE_NAME: 'order_id',
'KeyType': 'HASH'
},
{
ATTRIBUTE_NAME: 'time',
'KeyType': 'RANGE'
}
],
AttributeDefinitions=[
{
ATTRIBUTE_NAME: 'order_id',
ATTRIBUTE_TYPE: 'S'
},
{
ATTRIBUTE_NAME: 'time',
ATTRIBUTE_TYPE: 'S'
},
{
ATTRIBUTE_NAME: 'market_product',
ATTRIBUTE_TYPE: 'S'
}
],
GlobalSecondaryIndexes=[
{
'IndexName': 'market_product_index',
'KeySchema': [
{
'AttributeName': 'market_product',
'KeyType': 'HASH'
},
],
'Projection': {
'ProjectionType': 'KEYS_ONLY'
},
'ProvisionedThroughput': {
'ReadCapacityUnits': 5,
'WriteCapacityUnits': 5
}
}
],
ProvisionedThroughput={
'ReadCapacityUnits': 5,
'WriteCapacityUnits': 5
}
)
Here I create dummy data for this table:
from uuid import uuid4 as uuid
my_table_dummy = [
{
'order_id': str(uuid()),
'time': '2019-02-13 15:07:55.575960',
'market_product': 'bitmex:BTC-USD',
'side': 'buy',
'size': '10.2',
'weighted_price': '21.3'
},
{
'order_id': str(uuid()),
'time': '2019-02-13 15:06:55.575960',
'market_product': 'bitmex:BTC-USD',
'side': 'buy',
'size': '10.2',
'weighted_price': '21.3'
},
{
'order_id': str(uuid()),
'time': '2019-02-12 15:06:55.575960',
'market_product': 'bitmex:BTC-USD',
'side': 'buy',
'size': '10.2',
'weighted_price': '21.3'
},
{
'order_id': str(uuid()),
'time': '2019-02-12 15:06:55.575961',
'market_product': 'bitmex:BTC-USD',
'side': 'buy',
'size': '10.2',
'weighted_price': '21.3'
},
{
'order_id': str(uuid()),
'time': '2019-02-11 15:06:55.575960',
'market_product': 'bitmex:BTC-USD',
'side': 'buy',
'size': '10.2',
'weighted_price': '21.3'
}
]
for dummy_sample in my_table_dummy:
my_table.put_item(Item=dummy_sample)
I read that when one queries the above and uses ScanForwardIndex flag, then, the results are sorted by the range key, which in this case is time. However, I am not getting the intended behaviour of obtaining the results of the query in descending/ascending order by time attribute:
response = my_table.query(
IndexName='market_product_index',
KeyConditionExpression=Key('market_product').eq('bitmex:BTC-USD'),
ScanIndexForward=True
)
and response looks like this, i.e. not ordered by time at all:
{'Items': [{'market_product': 'bitmex:BTC-USD',
'order_id': '0d9fd701-5a7e-4348-bb01-631388c2c246',
'time': '2019-02-12 15:06:55.575960'},
{'market_product': 'bitmex:BTC-USD',
'order_id': '8cc1f2a2-0bc5-4169-aca5-cf37abbb5bc4',
'time': '2019-02-11 15:06:55.575960'},
{'market_product': 'bitmex:BTC-USD',
'order_id': 'd23cfa2c-9ae6-403b-ae57-1e1a3796e116',
'time': '2019-02-13 15:06:55.575960'},
{'market_product': 'bitmex:BTC-USD',
'order_id': '29095ee3-588f-4fb8-98a0-ce34adf028ea',
'time': '2019-02-12 15:06:55.575961'},
{'market_product': 'bitmex:BTC-USD',
'order_id': '6cacd8fa-a2d0-4f2d-8041-a30fa5252c3b',
'time': '2019-02-13 15:07:55.575960'}],
'Count': 5,
'ScannedCount': 5,
'ResponseMetadata': {'RequestId': 'bbc8bc0e-218a-4669-ba52-4ac07cc7bb60',
'HTTPStatusCode': 200,
'HTTPHeaders': {'content-type': 'application/x-amz-json-1.0',
'x-amz-crc32': '365619475',
'x-amzn-requestid': 'bbc8bc0e-218a-4669-ba52-4ac07cc7bb60',
'content-length': '738',
'server': 'Jetty(8.1.12.v20130726)'},
'RetryAttempts': 0}}
They are returned in no particular order because your index doesn’t have a sort key. Indexes do not automatically inherit any structure from the base table.
By choosing keys only, you are telling DynamoDB that the primary key of the table should be projected to the GSI, but DynamoDB will not assume that you want your GSI to be sorted by the same attribute.
You can fix this by deleting and recreating your GSI with time stamp as the GSI sort key.

Default values for a method

I'm trying to specify default values to a Map. Is this how it's done?
static def AddOrder( String key, Map order = [
id: '',
campaign_id: '',
email_id: '',
email: '',
total: 0.0d,
order_date: '',
shipping: 0.0d,
tax: 0.0d,
store_id: '',
store_name: '',
items: [
line_num: 0,
product_id: 0,
sku: '',
product_name: '',
category_id: 0,
qty: 0.0d,
cost: 0.0d
]
] ){
contactMC( key, action, order)
}
Why don't you simply test it? A very short program will show you that indeed, that's how default map parameters work:
def testMethod(Map map = [ foo: 'bar' ]) {
return map.foo
}
println testMethod() //outputs bar
println testMethod([foo:'baz']) //outputs baz

Resources