I have this (below) as part of my apps script and I am wondering if there is a way to simplify this:
const fieldRange = ["C9","C10","C11","C12","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24","C25","C26","C27","C28","C29","C30","C33","C34","C35","C36","C37","C38","C41","C42","C45","C46","C47","C48","C49","C50"]
maybe something like ['(C9:C12)','(C15:C30)', etc.
In your situation, how about the following sample script?
Sample script:
// This value is from your question.
const fieldRange = ["C9", "C10", "C11", "C12", "C15", "C16", "C17", "C18", "C19", "C20", "C21", "C22", "C23", "C24", "C25", "C26", "C27", "C28", "C29", "C30", "C33", "C34", "C35", "C36", "C37", "C38", "C41", "C42", "C45", "C46", "C47", "C48", "C49", "C50"];
// Ref: https://tanaikech.github.io/2021/10/08/compiling-continuous-numbers-using-google-apps-script/
const compilingNumbers = (ar) => {
const { values } = [...new Set(ar.sort((a, b) => a - b))].reduce((o, e, i, a) => {
if (o.temp.length == 0 || (o.temp.length > 0 && e == o.temp[o.temp.length - 1] + 1)) {
o.temp.push(e);
} else {
if (o.temp.length > 0) {
o.values.push({ start: o.temp[0], end: o.temp[o.temp.length - 1] });
}
o.temp = [e];
}
if (i == a.length - 1) {
o.values.push(o.temp.length > 1 ? { start: o.temp[0], end: o.temp[o.temp.length - 1] } : { start: e, end: e });
}
return o;
}, { temp: [], values: [] });
return values;
};
const r1 = fieldRange.reduce((o, e) => {
const k = e.replace(/[0-9]+/, "");
const v = Number(e.toUpperCase().replace(/[A-Z]+/, ""));
o[k] = o[k] ? [...o[k], v] : [v];
return o;
}, {});
const res = Object.entries(r1).flatMap(([k, v]) => compilingNumbers(v).map(({ start, end }) => `${k}${start}:${k}${end}`));
console.log(res); // ["C9:C12","C15:C30","C33:C38","C41:C42","C45:C50"]
When this script is run, ["C9:C12","C15:C30","C33:C38","C41:C42","C45:C50"] is obtained.
If you want to retrieve the value like maybe something like ['(C9:C12)','(C15:C30)', etc., please modify ${k}${start}:${k}${end} to (${k}${start}:${k}${end}).
Note:
From your showing sample value, this script supposes that all values in your expected array including A1Notation are only one cell. Please be careful about this.
References:
reduce()
map()
Related
Example, I have a path(prefix) like this: A/B/C/
I want to get bellow list:
[{name:"A",path:"A/"},
{name:"B",path:"A/B/",
{name:"C",path:"A/B/C/"
]
I can split the path to a array, then loop the array to build the new list of object.
But in my mind, I just know there should be a simple and smarter way to achieve this by using reducer, but just stuck here.
You're right that you could use a reducer. Something like this:
const str = "A/B/C/"
const arr = str.split("/").filter(Boolean).reduce((acc, name) => {
const path = [...acc.map(o => o.name), name].join("/") + "/"
return [...acc, { name, path }]
}, [])
console.log(arr)
You can solve this with map, but maybe not as cleanly as you were anticipating:
const result = 'A/B/C'
.split('/')
.filter(x => x)
.map((name, i, arr) => {
const prev = arr[i - 1];
return prev
? { name, path: `${prev.name}${name}/` }
: { name, path: `${name}/` };
});
My odd solution, but I think sdgluck's is the cleanest answer.
arr = "A/B/C/"
.split("/")
.filter(e => e)
.map((e, i, a) => {
a2 = a.filter((el, ix) => {
if (ix <= i) return el;
});
return {[e] : a2.join("/")};
});
Beginner at JavaScript here. Below is a JS challenge I've just completed for a course. The challenge:
Clean the room function: given an input of [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20], make a function that organizes these into individual array that is ordered. For example answer(ArrayFromAbove) should return: [[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591]
My solution:
let originalArray = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
const compareFunction = ((a, b) => {
return a-b;
});
let counter = 1;
const groupFunction = (currentValue, index, arr) => {
nextNumber = arr[index + 1];
if (currentValue === nextNumber){
counter++;
} else {
if (counter > 1){
let filledArray = new Array(counter).fill(currentValue);
counter = 1;
return filledArray;
} else {
return currentValue;
}
}
};
const filterFunction = (currentValue) => {
return currentValue !== undefined;
}
const finalFunction = (arr) => {
arr.sort(compareFunction);
let groupedArray = arr.map(groupFunction);
let finalArray = groupedArray.filter(filterFunction);
return finalArray;
}
finalFunction (originalArray);
Everything returns correctly, however I am under the impression that it is bad practice to declare global variables. With my "counter" variable, if I assign it within the groupFunction, the counter resets every loop through the array, making it useless. Where would be the appropriate place to put this variable? Is there another method / approach that would be better suited for the problem all together? Thank you!
In my opinion, your code is very hard to read, and it would probably be better if you'd rewrite it somehow, but I will leave that up to you. One thing you can do, to remove this global variable, is to use a concept called higher order function.
const higherOrderFunction = () => {
let counter = 1;
const groupFunction = (currentValue, index, arr) => {
nextNumber = arr[index + 1];
if (currentValue === nextNumber){
counter++;
} else {
if (counter > 1){
let filledArray = new Array(counter).fill(currentValue);
counter = 1;
return filledArray;
} else {
return currentValue;
}
}
}
return groupFunction;
};
You then get access to your groupFunction by calling the higher order function,
but the variable does not pollute your global scope:
let groupFunction = higherOrderFunction()
Here's a much shorter approach, that I know as beginner will have you searching to learn what some of the stuff used does, if you did not learn about it already.
Stay Curious.
const finalFunction = (arr) => {
const map = arr.reduce((acc, curr) => {
acc[curr] = (acc[curr] || 0) + 1;
return acc;
}, {});
return Object.keys(map)
.sort((a, b) => a - b)
.reduce((acc, val) => {
const value = Number(val);
acc.push(map[val] > 1 ? Array(map[val]).fill(value) : value);
return acc;
}, []);
};
And another one:
const finalFunction = (arr) => arr.sort((a, b) => a - b)
.reduce((acc, curr) => {
const last = acc.pop();
return curr === last
? [...acc, [curr, curr]]
: last && curr === last[0]
? [...acc, [...last, curr]]
: [...acc, ...(last ? [last] : []), curr];
}, []);
I am trying to solve one algorithm in Javascript where the user requires the input sentence then have to do statistic as the following screenshot
I have done with following code
class TextAnalytics {
getAnalytics(sentence) {
var analyzedResult = {}
var textArray = new Array();
const trimmed = sentence.replace(/\s/g, '').toUpperCase()
for (let i = 0; i < trimmed.length; i++) {
const currentChar = trimmed[i]
if (!analyzedResult[currentChar]) {
analyzedResult[currentChar] = {
count: 1,
prevChar: trimmed[i - 1] ? [trimmed[i - 1]] : [],
nextChar: trimmed[i + 1] ? [trimmed[i + 1]] : [],
index: [i]
}
} else {
analyzedResult[currentChar].count++
trimmed[i - 1] &&
analyzedResult[currentChar].prevChar.push(trimmed[i - 1])
trimmed[i + 1] &&
analyzedResult[currentChar].nextChar.push(trimmed[i + 1])
analyzedResult[currentChar].index.push(i)
}
}
return analyzedResult;
}
getMaxDistance(arr) {
let max = Math.max.apply(null, arr);
let min = Math.min.apply(null, arr);
return max - min;
}
}
var textAnalytics = new TextAnalytics();
console.log(textAnalytics.getAnalytics("its cool and awesome"));
Want to check if there is any other way to solve this problem or any refactoring require
Help will be appreciated.
Thanks
You can write it more elegantly:
class CharStats {
constructor () {
this.prevs = [];
this.nexts = [];
this.indexes = [];
}
add (prev, next, index) {
prev && this.prevs.push(prev);
next && this.nexts.push(next);
this.indexes.push(index);
return this;
}
get count () {
return this.indexes.length;
}
get maxDistance () {
// If the index array is empty, the result will be Infinite.
// But because the algorithm cannot have a situation where
// this class is used without at least one index, this case
// need not be covered.
return Math.max(...this.indexes) - Math.min(...this.indexes);
}
}
const getAnalytics = sentence =>
[...sentence.replace(/\s/g, '').toUpperCase()].reduce((map, cur, i, arr) =>
map.set(cur, (map.get(cur) || new CharStats).add(arr[i - 1], arr[i + 1], i)),
new Map);
console.log(getAnalytics('its cool and awesome'));
1) Convert string to array of chars, remove empty, change to upper case
2) Use reduce, go thru each char and build object 'keys' as Char values to have before, after and index.
3) if Char already exist in object, Append new stats and calculate max-distance.
const getAnalytics = str => {
const caps = Array.from(str.toUpperCase()).filter(x => x.trim());
return caps.reduce((acc, char, i) => {
const prepost = {
before: caps[i-1] || '',
after: caps[i+1] || '',
index: i
};
if (char in acc) {
const chars = [...acc[char].chars, prepost];
const mm = chars.reduce((acc, curr) => ({
max: Math.max(acc.max, curr.index),
min: Math.min(acc.min, curr.index)
}), {max: -Infinity, min: Infinity});
acc[char] = { chars, max_distance: mm.max - mm.min };
} else {
acc[char] = { chars: [prepost], max_distance: 0 };
}
return acc;
}, {});
}
console.log(getAnalytics('its cool and awesome'));
How can I use reduce in order to return the sum only if the accumulator (a) and the currentValue (b) is both an Integer?
My guestGroups Array:
"guestGroups":[
{
"bookingNumber":"21.05.201827.05.20181208",
"arrivalDate":"2018-05-19T22:00:00.000Z",
"departureDate":"2018-05-25T22:00:00.000Z",
"customerId":1,
"fields":[
{
"value":"2",
"display_name":"Personen Anzahl",
"servicio_tags":[
"person-number-info"
]
}
],
"number":"041"
}
]
getPersonNumberAdult() {
const personNumberField = this.fields.find(f => {
return f.servicio_tags && f.servicio_tags.includes('person-number-info');
});
return personNumberField ? Number(personNumberField.value.match(/\d+/)[0]) : '-';
}
isInt(value) {
let x = parseFloat(value);
return !isNaN(value) && (x | 0) === x;
}
guestInfo: t.guestGroups
.map(gg => gg.getPersonNumberAdult())
.reduce((function(a, b) {
if (a && b) {
{
if (this.isInt(a) && this.isInt(b)) {
return a + b
} else {
return '-'
}
}
}
}), 0)
This gives me undefined.
Thanks for your efforts!
Your getPersonNumberAdult should not return either a number or a string. It should return an integer or NaN if no number is available. The '-' should only be used in formatting the output in the end.
Once you got that, you can either filter out invalid values and sum the rest:
const sum = t.guestGroups
.map(gg => gg.getPersonNumberAdult())
.filter(v => isInt(v)) // or just !isNaN
.reduce((a, b) => a + b, 0);
or just sum them and then replace NaN by '-' if any value was invalid:
const sum = t.guestGroups
.map(gg => gg.getPersonNumberAdult())
.reduce((a, b) => a + b, 0);
const output = isInt(sum) ? String(sum) : '-';
depending on which behaviour you want.
I have been reading about map, reduce and filter a lot because of how much they are used in react and FP in general. If we write something like:
let myArr = [1,2,3,4,5,6,7,8,9]
let sumOfDoubleOfOddNumbers = myArr.filter(num => num % 2)
.map(num => num * 2)
.reduce((acc, currVal) => acc + currVal, 0);
3 different loops are run.
I've read about Java 8 streams as well and know that they use what is called a monad, ie, the computations are stored first. They are performed once only in one iteration. For example,
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("A");
})
.forEach(s -> System.out.println("forEach: " + s));
// map: d2
// filter: D2
// map: a2
// filter: A2
// forEach: A2
// map: b1
// filter: B1
// map: b3
// filter: B3
// map: c
// filter: C
PS: Java code is taken from: http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/
There are many other languages that use this same method. Is there a way to do it the same way in JS as well?
This is an exact clone of your Java code. Unlike Bergi's solution, no need to modify global prototypes.
class Stream {
constructor(iter) {
this.iter = iter;
}
* [Symbol.iterator]() {
yield* this.iter;
}
static of(...args) {
return new this(function* () {
yield* args
}());
}
_chain(next) {
return new this.constructor(next.call(this));
}
map(fn) {
return this._chain(function* () {
for (let a of this)
yield fn(a);
});
}
filter(fn) {
return this._chain(function* () {
for (let a of this)
if (fn(a))
yield (a);
});
}
forEach(fn) {
for (let a of this)
fn(a)
}
}
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s => {
console.log("map: " + s);
return s.toUpperCase();
})
.filter(s => {
console.log("filter: " + s);
return s.startsWith("A");
})
.forEach(s => console.log('forEach', s));
Actually, the chaining functionality could be decoupled from specific iterators to provide a generic framework:
// polyfill, remove me later on
Array.prototype.values = Array.prototype.values || function* () { yield* this };
class Iter {
constructor(iter) { this.iter = iter }
* [Symbol.iterator]() { yield* this.iter }
static of(...args) { return this.from(args) }
static from(args) { return new this(args.values()) }
_(gen) { return new this.constructor(gen.call(this)) }
}
Now, you can throw arbitrary generators into this, both predefined and ad-hoc ones, for example:
let map = fn => function* () {
for (let a of this)
yield fn(a);
};
let filter = fn => function* () {
for (let a of this)
if (fn(a))
yield (a);
};
it = Iter.of("d2", "a2", "b1", "b3", "c", "a000")
._(map(s => s.toUpperCase()))
._(filter(s => s.startsWith("A")))
._(function*() {
for (let x of [...this].sort())
yield x;
});
console.log([...it])
You can achieve this using piping, i dunno if this makes it too complicated, but by using piping you can call Array.reduce on the pipe and it performs the same behaviour on each iteration.
const stream = ["d2", "a2", "b1", "b3", "c"];
const _pipe = (a, b) => (arg) => b(a(arg));
const pipe = (...ops) => ops.reduce(_pipe);
const _map = (value) => (console.log(`map: ${value}`), value.toUpperCase());
const _filter = (value) => (console.log(`filter: ${value}`),
value.startsWith("A") ? value : undefined);
const _forEach = (value) => value ? (console.log(`forEach: ${value}`), value) : undefined;
const mapFilterEach = pipe(_map,_filter,_forEach);
const result = stream.reduce((sum, element) => {
const value = mapFilterEach(element);
if(value) sum.push(value);
return sum;
}, []);
I took the pipe function from here
Here is a polyfill of the pipe reduce and an example if you want to use it for more dynamic purposes
Array.prototype.pipeReduce = function(...pipes){
const _pipe = (a, b) => (arg) => b(a(arg));
const pipe = (...ops) => ops.reduce(_pipe);
const reducePipes = pipe(...pipes);
return this.reduce((sum, element) => {
const value = reducePipes(element);
if(value) sum.push(value);
return sum;
}, []);
};
const stream = ["d2", "a2", "b1", "b3", "c"];
const reduced = stream.pipeReduce((mapValue) => {
console.log(`map: ${mapValue}`);
return mapValue.toUpperCase();
}, (filterValue) => {
console.log(`filter: ${filterValue}`);
return filterValue.startsWith("A") ? filterValue : undefined;
}, (forEachValue) => {
if(forEachValue){
console.log(`forEach: ${forEachValue}`);
return forEachValue;
}
return undefined;
});
console.log(reduced); //["A2"]
what is called a monad, ie, the computations are stored first
Um, no, that's not what Monad means.
Is there a way to do it the same way in JS as well?
Yes, you can use iterators. Check this implementation or that one (and for the monad methods, here).
const myArr = [1,2,3,4,5,6,7,8,9];
const sumOfDoubleOfOddNumbers = myArr.values() // get iterator
.filter(num => num % 2)
.map(num => num * 2)
.reduce((acc, currVal) => acc + currVal, 0);
console.log(sumOfDoubleOfOddNumbers);
["d2", "a2", "b1", "b3", "c"].values()
.map(s => {
console.log("map: " + s);
return s.toUpperCase();
})
.filter(s => {
console.log("filter: " + s);
return s.startsWith("A");
})
.forEach(s => console.log("forEach: " + s));
var IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
IteratorPrototype.map = function*(f) {
for (var x of this)
yield f(x);
};
IteratorPrototype.filter = function*(p) {
for (var x of this)
if (p(x))
yield x;
};
IteratorPrototype.reduce = function(f, acc) {
for (var x of this)
acc = f(acc, x);
return acc;
};
IteratorPrototype.forEach = function(f) {
for (var x of this)
f(x);
};
Array.prototype.values = Array.prototype[Symbol.iterator];
const myArr = [1,2,3,4,5,6,7,8,9];
const sumOfDoubleOfOddNumbers = myArr.values() // get iterator
.filter(num => num % 2)
.map(num => num * 2)
.reduce((acc, currVal) => acc + currVal, 0);
console.log({sumOfDoubleOfOddNumbers});
["d2", "a2", "b1", "b3", "c"].values()
.map(s => {
console.log("map: " + s);
return s.toUpperCase();
})
.filter(s => {
console.log("filter: " + s);
return s.startsWith("A");
})
.forEach(s => console.log("forEach: " + s));
In production code, you probably should use static functions instead of putting custom methods on the builtin iterator prototype.
Array.prototype.map and Array.prototype.filter creates new arrays from the previous one.
Array.prototype.reduce applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
Therefore, neither of them allow lazy evaluation.
You can achieve laziness by reducing your multiples loops into one:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const result = array.reduce((acc, x) => x % 2 ? acc += x * 2 : acc, 0);
console.log(result);
Another way could be handling lazy evaluations by yourself in a custom object as follows. Next snippet is an example redefining filter and map:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// convert to a lazy structure...
const results = toLazy(array)
.filter(x => {
console.log('filter', x);
return x % 2 !== 0;
})
.map(x => {
console.log('map', x);
return x * 2;
});
// check logs for `filter` and `map` callbacks
console.log(results.run()); // -> [2, 6, 10, 14, 18]
function toLazy(array) {
const lazy = {};
let callbacks = [];
function addCallback(type, callback) {
callbacks.push({ type, callback });
return lazy;
}
lazy.filter = addCallback.bind(null, 'filter');
lazy.map = addCallback.bind(null, 'map');
lazy.run = function () {
const results = [];
for (var i = 0; i < array.length; i += 1) {
const item = array[i];
for (var { callback, type } of callbacks) {
if (type === 'filter') {
if (!callback(item, i)) {
break;
}
} else if (type === 'map') {
results.push(callback(item, i));
}
}
}
return results;
};
return lazy;
}
However, you can check libraries like lazy.js which provides a lazy engine under the hood using iterators.