es6 map/set iterate from certain item (backwards) - dictionary

Beginner here,
I'm a bit lost with es6 set, map and generators.
How can I select an item in a map, then iterate backwards from that point on effectively? Preferably without going through the whole set/map.
let v = myMap.get('key')
so, from 'v' to the beginning of the map (backwards)?
thank you!

You can create a set of iteration helpers and then compound to create the effect you want:
/* iterTo iterates the iterable from the start and up to (inclusive) key is found.
The function "understands" the Map type when comparing keys as well as
any other iterables where the value itself is the key to match. */
function* iterTo(iterable, key) {
for(let i of iterable) {
yield i;
if((iterable instanceof Map && i[0] === key) || i === key)
return;
}
}
// Same as iterTo, but starts at the key and goes all the way to the end
function* iterFrom(iterable, key) {
let found = false;
for(let i of iterable) {
if(found = (found || (iterable instanceof Map && i[0] === key) || i === key))
yield i;
}
}
// reverseIter creates a reverse facade for iterable
function* reverseIter(iterable) {
let all = [...iterable];
for(let i = all.length; i--; )
yield all[i];
}
You can then use and compound like this:
let m = new Map();
m.set(1, 'a');
m.set(2, 'b');
m.set(3, 'c');
m.set(4, 'd');
m.set(5, 'e');
let s = new Set();
s.add(100);
s.add(200);
s.add(300);
s.add(400);
console.log(...iterTo(m, 3), ...iterFrom(m, 3));
console.log(...reverseIter(iterTo(m, 3)), ...reverseIter(iterFrom(m, 3)));
console.log(...reverseIter(iterTo(s, 200)));

What you probably want is a slice of the keys from the first element to the index of the key, reverse that, iterate over that and get the values from the map.
I am assuming your map is an object:
let keys = Object.keys(map);
return keys.slice(0, keys.indexOf('key')).map((k) => map[k]);
You don't really need a generator.

Related

how to loop through sessionScope in SSJS / XPages?

I have a function to go through all sessionScopes:
function clearMap( map:Map ){ // Get iterator for the keys
var iterator = map.keySet().iterator(); // Remove all items
while( iterator.hasNext() ){
//would like to read here the keyValue
}
}
clearMap(sessionScope);
I would like to read the key value for each item in the map. (keys ending with _languagecode I would like to remove) but how can I do this?
With iterator.next()you have access to the key itself so you should be able to do something like this in SSJS:
function clearMap( map:Map ){ // Get iterator for the keys
var iterator = map.keySet().iterator(); // Remove all items
while( iterator.hasNext() ){
var key = iterator.next();
if (key == 'something you want to test for') {
map.remove(key);
}
}
}

Using ES6 Map with Flow type

I'm trying to wrap my head around flow and I struggle to make it work with ES6's Map
Consider this simple case (live demo):
// create a new map
const m = new Map();
m.set('value', 5);
console.log(m.get('value') * 5)
flow throws:
console.log(m.get('value') * 5)
^ Cannot perform arithmetic operation because undefined [1] is not a number.
References:
[LIB] static/v0.72.0/flowlib/core.js:532: get(key: K): V | void;
^ [1]
I also tried:
const m:Map<string, number> = new Map();
m.set('value', 5);
console.log(m.get('value') * 5)
But I got the same error
I believe this is because flow thinks that the value can also be something else than a number, so I tried to wrap the map with a strict setter and getter (live demo):
type MyMapType = {
set: (key: string, value: number) => MyMapType,
get: (key: string) => number
};
function MyMap() : MyMapType {
const map = new Map();
return {
set (key: string, value: number) {
map.set(key, value);
return this;
},
get (key: string) {
return map.get(key);
}
}
}
const m = MyMap();
m.set('value', 5);
const n = m.get('value');
console.log(n * 2);
but then I got:
get (key: string) {
^ Cannot return object literal because undefined [1] is incompatible
with number [2] in the return value of property `get`.
References:
[LIB] static/v0.72.0/flowlib/core.js:532: get(key: K): V | void;
^ [1]
get: (key: string) => number ^ [2]
How can I tell flow that I only deal with a Map of numbers?
Edit:
Typescript approach makes more senses to me, it throws on set instead on get.
// TypeScript
const m:Map<string, number> = new Map();
m.set('value', 'no-number'); // << throws on set, not on get
console.log(m.get('value') * 2);
Is there a way to make Flow behave the same way?
What Flow is trying to tell you is that by calling map.get(key), .get(...) may (V) or may not (void) return something out of that map. If the key is not found in the map, then the call to .get(...) will return undefined. To get around this, you need to handle the case where something is returned undefined. Here's a few ways to do it:
(Try)
const m = new Map();
m.set('value', 5);
// Throw if a value is not found
const getOrThrow = (map, key) => {
const val = map.get(key)
if (val == null) {
throw new Error("Uh-oh, key not found")
}
return val
}
// Return a default value if the key is not found
const getOrDefault = (map, key, defaultValue) => {
const val = map.get(key)
return val == null ? defaultValue : val
}
console.log(getOrThrow(m, 'value') * 5)
console.log(getOrDefault(m, 'value', 1) * 5)
The reason that map.get(key) is typed as V | void is the map might not contain a value at that key. If it doesn't have a value at the key, then you'll throw a runtime error. The Flow developers decided they would rather force the developer (you and me) to think about the problem while we're writing the code then find out at runtime.
Random and pretty late, but was searching and came up with this for my own use cases when I didn't see it mentioned:
const specialIdMap = new Map<SpecialId, Set<SpecialId>>();
const set : Set<SpecialId> = specialIdMap.get(uniqueSpecialId) || new Set();
and this saves quite a lot of boilerplate of checking if null and/or whatever. Of course, this only works if you also do not rely on a falsy value. Alternatively, you could use the new ?? operator.

How should a disjoint union wrapper be structured for refinement to work?

On the Flowtype "Try" site
// #flow
type One = {type: "One"};
type Two = {type: "Two"};
type Node = One | Two;
class Foo<N: Node> {
node: N;
constructor(n: N) {
this.node = n;
}
}
const fooNode: Foo<Node> = new Foo({type: "One"});
if (fooNode.node.type === "One") {
const fooOne: Foo<One> = fooNode;
}
the if type check is not enough to refine the type, if I understand right, because the type is not guaranteed to be constant.
Since I want to avoid the possibility of this being an X/Y problem, the usecase I'm playing with at the moment is searching from a given node with a .find method that would return the refined type, e.g. using
parent(): Foo<N> | null {
// ...
return null;
}
find<U: Node>(callback: (foo: Foo<N>) => Foo<U> | null): Foo<U> | null {
let p = this;
do {
const result = callback(p);
if (result) return result;
p = p.parent();
} while (p);
return null;
}
with
const f: Foo<Node> = new Foo({type: "One"});
const result: Foo<Two>|null = f.find((p) => p.node.type === "Two" ? p : null);
which would allow me to return the refined type at the while searching.
The problem is with the type annotation on this line:
const fooNode: Foo<Node> = new Foo({type: "One"});
By explicitly using Foo<Node> you're preventing the refinement from happening. You can use Foo<*> to make the inference work correctly.
Here's an example:
https://flowtype.org/try/#0PTAEAEDMBsHsHcBQiAuBPADgU1AeQHY4C8oA3qOtgFygBEBWtoAvgNyqY4Aq8soJ5Slhq0esJmw7ZQAOVgATYnkKgAPqDHtEAY2gBDAM4HQAMViwAPDJpzFAPjKJQofAuGytz7bHwGUAJwBXbRRYfwAKfBsASkdnZxQACwBLAwA6V0V+F3ZnZkR85G9fFFBIc1t3M0sAKgcSQnhTc3DSIREGWmZo9nDy2EqaaosGOx7EPoq3IfMLMTHWUBBQLH9-MKA
There are two problems. Foo is invariant, so you will never be able to refine it: it doesn't have any known subtypes other than itself.
On the other hand, even if Foo was covariant, it wouldn't work. You simply can't refine a generic class.
The only practical option is to unwrap, refine, and wrap again.

dart, a nice way of replacing keys and values in maps?

I have a Map and I want to go through both the values and keys and replace any occurrences of particular objects which meet some set criteria with some other objects, so if I find a key that meets a specific criteria.
I want to swap that key for a different object that still points at the same value object in the map, similarly if I find a value that I want to replace I want the original key to point at the replacement value.
Here is some code that works for a simplified example but it looks quite ugly, is there a nicer way of achieving this, meaning a method which doesn't require that you extract every key and value you want to replace and then write the replacements back in.
It would be nicer to be able to just iterate of the map once rather than iterating over the keys, and then iterating over all the keys and values to be replaced?
void main(){
//replace all values of instance A with an Object and all keys starting with "t" with the same key minus the "t"
var m = {
"one": new A(),
"two": new A(),
"three": new B(),
"four": new B()
};
mapKeyAndValueSwapper(m,
keyMeetsCondition: (k) => k.startsWith("t"),
valueMeetsCondition: (v) => v is A,
keyReplacer: (k) => k.replaceFirst("t", ""),
valueReplacer: (v) => new Object());
print(m);
}
mapKeyAndValueSwapper(Map m, {bool keyMeetsCondition(key), bool valueMeetsCondition(value), dynamic keyReplacer(key), dynamic valueReplacer(value)}){
var keysToReplace = [];
var keysToReplaceValuesFor = [];
var keys = m.keys;
keys.forEach((k){
if(keyMeetsCondition != null){
if(keyMeetsCondition(k)){
keysToReplace.add(k);
}
}
if(valueMeetsCondition != null){
if(valueMeetsCondition(m[k])){
keysToReplaceValuesFor.add(k);
}
}
});
keysToReplaceValuesFor.forEach((k){
m[k] = valueReplacer(m[k]);
});
keysToReplace.forEach((k){
m[keyReplacer(k)] = m.remove(k);
});
}
class A{}
class B{}
I think this does the same:
Map newMap = {};
m.forEach((k, v) {
var key = k;
var value = v;
if(m[k] is A) {
value = new Object();
}
if(k.startsWith('t')) {
key = k.replaceFirst('t', '');
}
newMap[key]=value;
});
You can create an extension on Map that creates a list from the mapEntries and then manipulate that list to update the key (using the index to maintain order), then clear the map and rebuild it from your updated list.
extension MapX<K, V> on Map<K, V> {
bool updateKey({required K currentKey, required K newKey}) {
if (containsKey(currentKey) && !containsKey(newKey)) {
final value = this[currentKey] as V;
final index = keys.toList().indexWhere((k) => k == currentKey);
final mapEntriesList = entries.toList();
mapEntriesList.removeAt(index);
mapEntriesList.insert(index, MapEntry<K,V>(newKey, value));
clear();
addEntries(mapEntriesList);
return true;
} else {
return false;
}
}
}

Make operation with aggregate function result

I confess that I do not get along very well with the Deferred object. I'm making a query to the database on several "Stores" and as a result I want to do a series of operations. This troubles me because the results are returned asynchronously and I have no way to perform the corresponding operation on the "store" you should. In short, the problem is that this piece of code always executes the same function on the same "Store"
for (var i = 0; i < schema['stores'].length; i++) {
storeName = schema['stores'][i].name;
var objeto = db.executeSql('SELECT MAX(date_upd) FROM ' + '"' + storeName + '"').done(
function(result, a){
//saveDataSynce(db, storeName, result);
console.log(result);
}
);
}
Whenever there is a loop on async operation, be very careful about function scope. In your example code, storeName inside the function will always be the last executed value. Use function scope as follow:
var getMax = function(storeName) {
db.executeSql('SELECT MAX(date_upd) FROM ' + '"' + storeName + '"').done(
function(result){
//saveDataSynce(db, storeName, result);
console.log(storeName, result);
}
);
}
for (var i = 0; i < schema['stores'].length; i++) {
getMax(schema['stores'][i].name);
}
However, preferred coding pattern for YDN-DB is NoSQL style as follow:
var getMax = function(storeName) {
var indexName = 'date_upd';
var key_range = null; // whole store
var limit = 1;
var offset = 0;
var reverse = true;
db.values(storeName, indexName, key_range, limit, offset, reverse).done(
function(results) {
var max_key = results[0]; // may be undefined. OK.
//saveDataSynce(db, storeName, max_key);
console.log(storeName, max_key);
}
);
}
Note that keys (primary or index) are always sorted by ascending order. Max key is the first key in reverse order.

Resources