What is the Best way to loop over an array in scala - arrays

I'm new to scala and I'm trying to refactor the below code.I want to eliminate "index" used in the below code and loop over the array to fetch data.
subgroupMetricIndividual.instances.foreach { instanceIndividual =>
val MetricContextListBuffer: ListBuffer[Context] = ListBuffer()
var index = 0
contextListBufferForSubGroup.foreach { contextIndividual =>
MetricContextListBuffer += Context(
entity = contextIndividual,
value = instanceIndividual(index).toString
)
index += 1
}
}
For instance, if the values of variables are as below:
contextListBufferForSubGroup = ("context1","context2")
subgroupMetricIndividual.instances = {{"Inst1","Inst2",1},{"Inst3","Inst4",2}}
Then Context should be something like:
{
entity: "context1",
value: "Inst1"
},
{
entity: "context2",
value: "Inst2"
},
{
entity: "context1",
value: "Inst3"
},
{
entity: "context2",
value: "Inst4"
}
Note:
instanceIndividual can have more elements than those in contextListBufferForSubGroup. We must ignore the last extra elements in instanceIndividual in this case

You can zip two lists into a list of tuples and then map over that.
e.g.
subgroupMetricIndividual.instances.foreach { instanceIndividual =>
val MetricContextListBuffer = contextListBufferForSubGroup.zip(instanceIndividual).map {
case (contextIndividual, instanceIndividualIndex) => Context(
entity = contextIndividual,
value = instanceIndividualIndex.toString
)
}
}
If Context can be called like a function i.e. Context(contextIndividual, instanceIndividualIndex.toString) then you can write this even shorter.
subgroupMetricIndividual.instances.foreach { instanceIndividual =>
val MetricContextListBuffer = contextListBufferForSubGroup
.zip(instanceIndividual.map(_.toString)).map(Context.tupled)
}

Without knowing your exact datatypes, I'm mocked up something which is probably close to what you want, and is slightly more functional using maps, and immutable collections
case class Context(entity:String, value:String)
val contextListBufferForSubGroup = List("context1","context2")
val subgroupMetricIndividualInstances = List(List("Inst1","Inst2",1),List("Inst3","Inst4",2))
val result: List[Context] = subgroupMetricIndividualInstances.map { instanceIndividual =>
contextListBufferForSubGroup.zip(instanceIndividual) map { case v: (String, String) =>
Context(
entity = v._1,
value = v._2
)
}
}.flatten

Related

using array for URLSearchParams in vue3

i want to append key and value of array , using for axos request
here is the array
const
schedule = ref({
"userId" : 13,
"sunday" : ["mornig","afternoon","nigh"],
"monday" : ["afternoon","nigh"],
"wednesday" : ["mornig","afternoon"]
})
to append manuallya i can do like this
params.append("userId",data.value.userId)
params.append("sunday[0]",data.value.sunday[0])
params.append("sunday[1]",data.value.sunday[1])
params.append("sunday[2]",data.value.sunday[2])
params.append("monday[0]",data.value.monday[0])
params.append("monday[1]",data.value.monday[1])
params.append("wednesday[0]",data.value.wednesday[0])
params.append("wednesday[1]",data.value.wednesday[1])
but this will be problim if length of the schedule time l (morning,night ...) unknown
i do like this
let i = 0
for(let j in data.value){
console.log(j+$[i++])
}
and also loop using for in and for of , but none of them are success
You "just" need to do a nested iteration. Loop through object, and if any value is an array, loop through the array. Here's an example code to flatten the dataset. Just note that instead of flattenData(schedule) you'd need to pass the ref's value with flattenData(schedule.value).
const schedule = {
userId: 13,
sunday: ["mornig", "afternoon", "nigh"],
monday: ["afternoon", "nigh"],
wednesday: ["morning", "afternoon"],
};
function flattenData(data) {
const o = {};
Object.keys(data).forEach((dataKey) => {
if (Array.isArray(data[dataKey])) {
data[dataKey].forEach(
(_, nestedArrayIndex) =>
(o[`${dataKey}[${nestedArrayIndex}]`] =
data[dataKey][nestedArrayIndex])
);
} else {
o[dataKey] = data[dataKey];
}
});
return o;
}
// show flattened data
console.log(flattenData(schedule));
// prepare params
const params = new URLSearchParams("");
// populate
Object.entries(flattenData(schedule)).forEach(([k,v]) => params.append(k, v))
// result:
console.log(params.toString())

TypeScript not able to iterate associative array

I have a service which populates my associative array in typescript,
fun populateData(){
let tempArr;
tempArr = [];
this.service.get('Post', 1, 'true').subscribe(
(response) => {
this.loadingIcon = false;
for (let i = 0; i < response.results.length; i++) {
tempList = response.results[i]['tags'];
for ( let iter of tempList){
if ( iter in tempArr) {
tempArr[iter] = tempArr[iter] + 1;
}else {
tempArr[iter] = 1;
}
}
}
},
(error) => {
if (error['status'] === 401) {
localStorage.clear();
this.router.navigateByUrl('/login');
} else {
this.router.navigateByUrl('/error');
}
}
);
console.log(tempArr);
/*
This function is inside a class, once I iterate get access to tempArr I will be assigning the tempArr data to a class variable like
for (items in tempArr){
this.data.push(items, tempArr[items]);
}
*/
}
I'm able to populate my associative array with the service above which gives the following output in console,
I'm not able to iterate through this array, I tried a couple of methods like the following,
for ( const key in tempArr) {
console.log(key + ':' + tempArr[key]);
}
I want both they key and values from the array.
TypeScript generally assumes that the keys to arrays are numbers. What you were doing might work but it's not very idiomatic. I'm not going to rewrite your whole function but here are a few pointers:
When constructing your associative array (map for short from now on) you should try using an object instead of an array:
const tagCounts: { [key: string]: number } = {};
for (const result of response.results) {
for (const tag of result.tags) {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
}
}
Then you can iterate the result with:
for (const tag of Object.keys(tagCounts)) {
const count = tagCounts[tag];
// Other stuff here
}
Or if you have polyfills for Object.entries then with:
for (const [tag, count] of Object.entries(tagCounts)) {
// Other stuff here
}
Looking at your code, this.data.push also seems wrong: it will add a string and a number to your data array which is almost certainly not what you want. You might want to consider converting data to an object as well if you want to store key-value pairs.

array.groupBy in TypeScript

The basic array class has .map, .forEach, .filter, and .reduce, but .groupBy i noticably absent, preventing me from doing something like
const MyComponent = (props:any) => {
return (
<div>
{
props.tags
.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
}
</div>
)
}
I ended up implementing something myself:
class Group<T> {
key:string;
members:T[] = [];
constructor(key:string) {
this.key = key;
}
}
function groupBy<T>(list:T[], func:(x:T)=>string): Group<T>[] {
let res:Group<T>[] = [];
let group:Group<T> = null;
list.forEach((o)=>{
let groupName = func(o);
if (group === null) {
group = new Group<T>(groupName);
}
if (groupName != group.key) {
res.push(group);
group = new Group<T>(groupName);
}
group.members.push(o)
});
if (group != null) {
res.push(group);
}
return res
}
So now I can do
const MyComponent = (props:any) => {
return (
<div>
{
groupBy(props.tags, (t)=>t.category_name)
.map((group)=>{
return (
<ul key={group.key}>
<li>{group.key}</li>
<ul>
{
group.members.map((tag)=>{
return <li key={tag.id}>{tag.name}</li>
})
}
</ul>
</ul>
)
})
}
</div>
)
}
Works pretty well, but it is too bad that I need to wrap the list rather than just being able to chain method calls.
Is there a better solution?
You can use the following code to group stuff using Typescript.
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
// A little bit simplified version
const groupBy = <T, K extends keyof any>(arr: T[], key: (i: T) => K) =>
arr.reduce((groups, item) => {
(groups[key(item)] ||= []).push(item);
return groups;
}, {} as Record<K, T[]>);
So, if you have the following structure and array:
type Person = {
name: string;
age: number;
};
const people: Person[] = [
{
name: "Kevin R",
age: 25,
},
{
name: "Susan S",
age: 18,
},
{
name: "Julia J",
age: 18,
},
{
name: "Sarah C",
age: 25,
},
];
You can invoke it like:
const results = groupBy(people, i => i.name);
Which in this case, will give you an object with string keys, and Person[] values.
There are a few key concepts here:
1- You can use function to get the key, this way you can use TS infer capabilities to avoid having to type the generic every time you use the function.
2- By using the K extends keyof any type constraint, you're telling TS that the key being used needs to be something that can be a key string | number | symbol, that way you can use the getKey function to convert Date objects into strings for example.
3- Finally, you will be getting an object with keys of the type of the key, and values of the of the array type.
you could add the function to the array prototype in your app (note some don't recomend this: Why is extending native objects a bad practice?):
Array.prototype.groupBy = function(/* params here */) {
let array = this;
let result;
/* do more stuff here*/
return result;
};
Then create an interface in typescript like this:
.d.ts version:
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
OR in a normal ts file:
declare global {
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
}
Then you can use:
props.tags.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
A good option might be lodash.
npm install --save lodash
npm install --save-dev #types/lodash
Just import it import * as _ from 'lodash' and use.
Example
_.groupBy(..)
_.map(..)
_.filter(..)
Instead of groupby use reduce. Suppose product is your array
let group = product.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a.organization] = [...r[a.organization] || [], a];
return r;
}, {});
console.log("group", group);
During the TC39 meeting of December 2021, the proposal introducing the new Array.prototype.groupBy and Array.prototype.groupByToMap function has reached stage 3 in the specification process.
https://github.com/tc39/proposal-array-grouping
https://github.com/tc39/proposals/commit/b537605f01df50fd4901be5ce4aa0d02fe6e7193
Here's how both functions are supposed to look like according to the README linked above:
const array = [1, 2, 3, 4, 5];
// groupBy groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
array.groupBy((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// => { odd: [1, 3, 5], even: [2, 4] }
// groupByToMap returns items in a Map, and is useful for grouping using
// an object key.
const odd = { odd: true };
const even = { even: true };
array.groupByToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
While not a 100% guaranty that it will really end up in a future version of JavaScript in the form described above (there's always a chance that the proposal can be adjusted or dropped, notably for compatibility reasons), it's nevertheless a strong commitment to have this groupBy feature offered in the standard lib soon.
By ripple effect, it also means that these functions will be also available in TypeScript.

unable to return an Array from a class

Trying to understand 'apply' method. I want to create a class which returns either a List, Array or a Set depending on the argument passed. The code works for List and Set but not for Array. I am not able to understand the issue
class CollectionFactory [A](s:String){}
object CollectionFactory {
def apply[A](s: String): Traversable[A] = {
s match {
case "list" => {
List[A]()
}
//this doesnt work. It seems using [A] is incorrect. How do I specify the type?
/*
case "array" => {
new Array[A](1)
}
*/
case _ => {
Set[A]() }
}
}
}
val c = CollectionFactory[Int]("list")
c: Traversable[Int] = List()
CollectionFactory[String]("list")
res0: Traversable[String] = List()
CollectionFactory[Boolean]("")
res1: Traversable[Boolean] = Set()
You need a ClassTag[A] to instantiate a new Array[A]. This is easy to fix by adding an implicit ct: ClassTag[A] parameter.
object CollectionFactory {
def apply[A: reflect.ClassTag](s: String): Traversable[A] = {
s match {
case "list" => List[A]()
case "array" => new Array[A](1)
case _ => Set[A]()
}
}
}

ES6: Merge two arrays into an array of objects

I have two arrays that I want to merge together to one array of objects...
The first array is of dates (strings):
let metrodates = [
"2008-01",
"2008-02",
"2008-03",..ect
];
The second array is of numbers:
let figures = [
0,
0.555,
0.293,..ect
]
I want to merge them to make an object like this (so the array items match up by their similar index):
let metrodata = [
{data: 0, date: "2008-01"},
{data: 0.555, date: "2008-02"},
{data: 0.293, date: "2008-03"},..ect
];
So far I do this like so: I create an empty array and then loop through one of the first two arrays to get the index number (the first two arrays are the same length)... But is there an easier way (in ES6)?
let metrodata = [];
for(let index in metrodates){
metrodata.push({data: figures[index], date: metrodates[index]});
}
The easiest way is probably to use map and the index provided to the callback
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = metrodates.map((date,i) => ({date, data: figures[i]}));
console.log(output);
Another option is to make a generic zip function which collates your two input arrays into a single array. This is usually called a "zip" because it interlaces the inputs like teeth on a zipper.
const zip = ([x,...xs], [y,...ys]) => {
if (x === undefined || y === undefined)
return [];
else
return [[x,y], ...zip(xs, ys)];
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = zip(metrodates, figures).map(([date, data]) => ({date, data}));
console.log(output);
Another option is to make a generic map function which accepts more than one source array. The mapping function will receive one value from each source list. See Racket's map procedure for more examples of its use.
This answer might seem the most complicated but it is also the most versatile because it accepts any number of source array inputs.
const isEmpty = xs => xs.length === 0;
const head = ([x,...xs]) => x;
const tail = ([x,...xs]) => xs;
const map = (f, ...xxs) => {
let loop = (acc, xxs) => {
if (xxs.some(isEmpty))
return acc;
else
return loop([...acc, f(...xxs.map(head))], xxs.map(tail));
};
return loop([], xxs);
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = map(
(date, data) => ({date, data}),
metrodates,
figures
);
console.log(output);
If you use lodash, you can use zipWith + ES6 shorthand propery names + ES6 Arrow functions for a one-liner, otherwise see #noami's answer.
const metrodata = _.zipWith(figures, metrodates, (data, date)=> ({ data, date }));

Resources