iterate over a map and change the position of element - javascript

I have an array of object
const test = [{'type':'Material'}, {'type':''}, {'type':'ABC'}]
Here I am using map over here to itertae
export const mapToNA = values => map(test, value => type || 'NA')
mapToNA(test)
This returns the [{'type':'Material'}, {'type':'NA'}, {'type':'ABC'}]
Now I want value which is NA then it should be at the end
so Output would be like:
[{'type':'Material'},{'type':'ABC'},{'type':'NA'}, ]
How can I get this ?

Since you're already using lodash, lets use _.sortBy with a custom function:
// Data
let test = [{'type':'Material'}, {'type':''}, {'type':'ABC'}];
// Map '' to 'NA'
const mapToNA = values => _.map(test, value => { return { type: value.type || 'NA' }; } )
test = mapToNA(test)
// Sort
test = _.sortBy(test, element => (element.type === 'NA'));
// Log
console.log(test)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
[
{
"type": "Material"
},
{
"type": "ABC"
},
{
"type": "NA"
}
]

You can partition the array to items with type, and items out without, and then map the relevant items' type to NA, and use spread to combine the arrays:
const data = [{'type':'Material'}, {'type':''}, {'type':'ABC'}];
// Pertition items to have type and NA
const [itemsWithType, itemsWithEmptyType] = _.partition(data, o => o.type)
// map the itemsWithEmptyType to NA and combine
const result = [...itemsWithType, ...itemsWithEmptyType.map(o => ({ ...o, type: 'NA' }))]
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

You can easily achieve this result using map and a custom sorting algorithm.
const test = [
{ type: "ABC" },
{ type: "Material" },
{ type: "" },
{ type: "ABC" },
{ type: "" },
];
const result = test
.map((s) => (s.type === "" ? { type: "NA" } : s))
.sort((a, b) => {
if (a.type === "NA" && b.type === "NA") return 0;
if (a.type === "NA") return 1;
if (b.type === "NA") return -1;
else return 0;
});
console.log(result);

You could use 2 separate arrays to keep track of objects with and without type. Merge them after the loop. This is readable and faster.
const withType = [],
withoutType = []
for (const o of test) {
if (o.type)
withType.push(o)
else
withoutType.push({ type: 'NA' })
}
console.log( withType.concat(withoutType) )
You could also reduce with 2 separate arrays and flat them:
const group = test.reduce((acc, o) => {
if (o.type)
acc[0].push(o)
else
acc[1].push({ 'type': 'NA' })
return acc
}, [[], []])
console.log( group.flat() )

You can use default sort function from javascript to solve this.
const arr =[{'type':'Material'}, {'type':'NA'}, {'type':'ABC'}];
arr.sort((a, b) => a["type"] === 'NA' ? 1 : -1);
console.log(arr)

Do you want to switch position between last element of array with your target element using Array.prototype.map ?
If so,
const test = [{"type": "Material"}, {"type": "NA"}, {"type":"ABC"}];
const someFunction = values => {
// NAIndex remember which index of values have "NA"
let NAIndex = 0;
// copy original array values.
const copiedValues = values.slice();
return values.map((value,index) => {
if(value["type"] === "NA"){
NAIndex = index;
return copiedValues[values.length-1];
}
else if(index === values.length-1){
return copiedValues[NAIndex];
}
return value;
})
}
const array = someFunction(test);
console.log(array)
/*
(3) [Object, Object, Object]
0: Object
type: "Material"
1: Object
type: "ABC"
2: Object
type: "NA"
*/
only use map method is a bit give you constraints yourself. try use splice or something. using only map has low efficiency

You can map the array and then sort your array based on 'NA'
const test = [{'type':'Material'}, {'type':''}, {'type':'ABC'}]
let result = test
.map(val => ({type: val.type || 'NA'}))
.sort((a,b) => a.type === 'NA' ? 1 : b.type === 'NA' ? -1 : 0);
console.log(result)

We can use the sort function.
test.sort((elem1, elem2) => {
if (elem2.type !== 'NA')
if (elem1.type === 'NA')
return 1
else
return 0
return -1
})
Or you can use the shorter one
test.sort((elem1, elem2) =>
elem2.type !== 'NA' ? elem1.type === 'NA' ? 1 : 0 : -1)
Learn more about the sort method here.

Related

How to take data from nested JSON data in bigquery

I have table with column field as follow :
{
"Quantity":{
"id_1":1,
"id_2":4,
},
"Discount" : {
"id_1":2,
"id_2":1,
},
"sales":{
"id_1":{
"price":50,
"quantity":1
},
"id_2":{
"quantity":1,
"price":620
}}
,
"tax":{
"id_1" : 2,
"id_2" : 3
}
}
My Expected Result is a table as follow :
id
tax
sales_quantity
sales_price
Discount
Quantity
id_1
2
1
50
2
1
id_2
3
1
620
1
4
While I'm trying to create the expected result, I think I found UDF function that work in javascript as follow :
CREATE TEMP FUNCTION json2array(json STRING)
RETURNS ARRAY <STRUCT<id STRING, tax STRING, sales_quantity STRING, sales_price STRING, Discount STRING, Quantity STRING>>
LANGUAGE js AS """
var result = {};
var keys = Object.keys(json);
keys.forEach(k => {
keys2 = Object.keys(json[k])
keys2.forEach(k2 => {
if(result[k2]== null)
result[k2] ={}
if(typeof json[k][k2] === 'object' )
{
Object.keys(json[k][k2]).forEach(k3 => {
result[k2][k +"_"+k3] = json[k][k2][k3]
})
} else {
result[k2][k] =json[k][k2]
}
})
})
var final_result = []
for (const [key, value] of Object.entries(result)) {
value["id"] = key; final_result.push(value);
}
final_result.map(obj => Object.keys(obj).map(k => obj[k] = obj[k] === null ? "" : obj[k]))
return final_result
""";
the function is working in javascript compiler, but it seems the result is not expected when using BigQuery UDF.I think the main problem is in return, but I'm not sure what I'm missing here
Consider below javascript UDF and PIVOT query.
CREATE TEMP FUNCTION json2array(json STRING)
RETURNS ARRAY<STRUCT<id STRING, key STRING, value STRING>> LANGUAGE js AS """
result = [];
for (const [key, obj] of Object.entries(JSON.parse(json))) { // top-most
Object.entries(obj).forEach(o => {
const [k0, v0] = o;
if (typeof(v0) === 'object')
for (const [k, v] of Object.entries(v0))
result.push({id:k0, key:key + '_' + k, value:v});
else
result.push({id:k0, key:key, value:v0});
});
}
return result;
""";
WITH sample_table AS (
SELECT '''{
"Quantity":{ "id_1":1, "id_2":4 },
"Discount" : { "id_1":2, "id_2":1 },
"sales":{
"id_1":{ "price":50, "quantity":1 },
"id_2":{ "quantity":1, "price":620 }
},
"tax":{ "id_1" : 2, "id_2" : 3 }
}''' json
)
SELECT * FROM (
SELECT e.* FROM sample_table, UNNEST(json2array(json)) e
) PIVOT (ANY_VALUE(value) FOR key IN ('tax', 'sales_quantity', 'sales_price', 'Discount', 'Quantity'));
Query results

How to filter out array data based on some outer conditions in react.js

const arr=['A','B'];
arr.filter(()=>{
if(type==all){return full array}
else{return arr[1]}
)
What I want is whenever the type is all return complete array but for rest of the condition return only the element at index 1.
Filter only when type is other than all, otherwise return the original data:
Demo:
const arr = [{type: "A", value: "A"}, {type: "B", value: "B"}]
let type = ""
const filterArr = () => {
if (type === "all") {
return arr // Don't filter
}
return arr.filter(item => item.type === type) // Filter when type is other than "all"
}
type = "all"
console.log(filterArr())
type = "B"
console.log(filterArr())
You can return array directly by using conditional operators
Try:-
const arr=['A','B'];
return type === all ? arr : arr[1]
Or you can use filter like below:-
const arr=['A','B'];
const filteredArray = arr.filter((value, index) => type === all || index === 1);

How can i get the keys of an object stored in Array

Lets just prefix this for the moderators this question is not about nested Key's. This is about an array in an object and how to see if its a simple array with only values or if it is an array which holds objects and how to get the keys of these objects. I included code snipped which parses the sample and detects the 2 Array's. What i am looking for is to return only name of array if the array is simple array with only a list of values. If the array is array of objects i would like to get the keys of that object in array.
obj = {
DocId: "email_campaign::3ed76589-4063-49f6-a21e-9ca16981d102",
history: {
created_by: "",
created_on: "",
update_on: "",
updated_by: ""
},
librarys :[{id: 1, name : 'Lib 1'},{ id: 2, name: 'Lib 2'}],
status: "Active",
subject: "Test 1 Subject",
summary: "",
tags: ['one', 'two'],
template_id: ""
};
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
// Here needs to go the Array Part
console.log(el + ' is Array')
return [...res, el];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
} else {
return [...res, el];
}
}, []);
const output = keyify(obj);
console.log(output);
Assuming you want the names of arrays prefixed similarly to the existing prefix for objects, you could try this:
const keyify = (obj, prefix = "") =>
Object.keys(obj).reduce((res, el) => {
const elDisplayName = `${prefix}${el}`;
if (Array.isArray(obj[el])) {
const objectsInArray = obj[el].filter(el => typeof el === "object");
if (objectsInArray.length > 0) {
let objectKeys = [];
objectsInArray.map(object => {
objectKeys = objectKeys.concat(keyify(object, prefix + el + "."))
});
return [...res, ...new Set(objectKeys)];
}
return [...res, elDisplayName];
} else if (typeof obj[el] === "object" && obj[el] !== null) {
return [...res, ...keyify(obj[el], prefix + el + ".")];
} else {
return [...res, elDisplayName];
}
}, []);
There are many caveats to this solution, like assuming if an array has a single object, it will contain only objects. But this should give you a start on detecting the presence of objects in arrays.
You can use .every() on an array to check every item against a case.
To check for an array you can use Array.isArray().
To check for an object you can use typeof variable === 'object' however, arrays are also classed as object types so you will need to check it is also not an array with the above method.
Example using your use case
const isObject = (arrayItem) => {
if (typeof arrayItem === 'object' && !Array.isArray(arrayItem) && arrayItem !== null) {
return true;
}
return false;
}
const array1 = [
[1, 2],
[1, 2],
[1, 2]
];
const array2 = [{
key: 'value'
}, {
key2: 'value'
}];
console.log('array1', array1.every(isObject)); // false
console.log('array2', array2.every(isObject)); // true

JavaScript - Filter object based on multiple values

I need to filter some data based on multiple values. Language, title and slug
[
{
de: "4567uy55",
en: "654321",
lang: [
{
id: "654321",
language: "English",
title: "Title1"
},
{
id: "4567uy55",
language: "German",
title: "Title2"
}
],
slug: 'some-slug'
},
...
]
What I have now returns all objects which have one or part of the filters(in case title is This is a title, the word this should match), but I need to return objects which have all of them.
I used an object flattner just to get all properties and values in one object, but I can't get it to filter the way I need it.
multiFilter = (arr, filters) => {
console.log(filters)
console.log(arr)
let newArray = []
for (let c of arr) {
let flatCourse = flatten(c)
for (let k in flatCourse) {
const keyArr = k.split('/')
const filterKeys = Object.keys(filters)
Object.keys(filters).map((key) => {
if (keyArr.includes(key)) {
const flatVal = flatCourse[k].toString().toLowerCase()
const filterVal = filters[key].toString().toLowerCase()
console.log(flatVal)
console.log(filterVal)
if (flatVal.includes(filterVal)) {
arr = []
arr.push(c)
newArray.push(c)
}
}
})
}
}
return newArray
}
Filters look like this:
[
language:["English"],
title: ["Some title"],
slug:["some slug"]
]
Instead of mixing for loops and functional chaining you could just go with one of them:
multiFilter = (arr, filters) =>
arr.map(flatten).filter(el => // filter out elements from arr
Object.entries(filters).every(([fKey, fValues]) => // ensure that every key is included in the object
Object.entries(el).some(([oKey, oValue]) =>
oKey.split("/").includes(fKey) && fValues.includes(oValue)// make sure that at least one of the values equals the elements value
)
)
);
arr.filter(course => {
// Returns an array of booleans corresponding to whether or not each filter condition is satisfied
return Object.keys(filters).map(key => {
return filters[key].map(filter => {
// Special case here because lang is an array
if (key == 'language' && course.lang != undefined) {
return course.lang.some(lang => lang[key].includes(filter))
}
if (course[key] == undefined) {
return false
}
return course[key].includes(filter)
}).every(result => result == true)
}).every(result => result == true)
})

lodash orderby with null and real values not ordering correctly

I have an Angular 2 typescript application that is using lodash for various things.
I have an array of objects that I am ordering using a property in the object...
_.orderBy(this.myArray, ['propertyName'], ['desc']);
This works well however my problem is that sometimes 'propertyName' can have a null value.
These are ordered as the first item in a descending list, the highest real values then follow.
I want to make these null values appear last in the descending ordering.
I understand why the nulls come first.
Does anyone know how to approach this?
The _.orderBy() function's iteratees can use a method instead of a string. Check the value, and if it's null return an empty string.
const myArray = [{ propertyName: 'cats' }, { propertyName: null }, { propertyName: 'dogs' }, { propertyName: 'rats' }, { propertyName: null }];
const result = _.orderBy(myArray, ({ propertyName }) => propertyName || '', ['desc']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
The check can be simple (like the one I've used), which converts all falsy values to an empty string:
propertyName || ''
If you need a stricter check, you can use the ternary operator, and handle just null values:
propertyName === null ? '' : propertyName
Edit: Example with multiple ordering:
const result = _.orderBy(myArray, (item) => [get(item, 'propertyName', 0), get(item, 'propertyName2')], ['desc', 'asc']);
This will order by propertyName then propertyName2.
If propertyName is undefined/null then its default order will be set to 0. (and therefore will be displayed at last because of desc ordering on the propertyName field). In such case, propertyName2 will therefore determine the ordering.
The code I needed looks like this...
_.orderBy(this.myArray, [( o ) => { return o.myProperty || ''}], ['desc']);
Just for future reference to others you can do this to sort ascending with falsey values at the end.
items =>
orderBy(
items,
[
i => !!i.attributeToCheck,
i => {
return i.attributeToCheck ? i.attributeToCheck.toLowerCase() : ''
}
],
['desc', 'asc']
)
mine looks like this. PropName and sort are both variables in my solution
return _.orderBy( myarray, [
( data ) => {
if ( data[propName] === null ) {
data[propName] = "";
}
return data[propName].toLowerCase();
}
], [sort] );
I wanted tolowercase because otherwise the sorting is not correct if different casings
This will put bad values at the bottom, and it differentiates between numbers and strings.
const items = [] // some list
const goodValues = isAscending => ({ value }) => {
if (typeof value !== 'string' && isNaN(value)) {
return isAscending ? Infinity : -Infinity
}
return value || ''
}
const sortedItems = orderBy(
items,
[goodValues(isAscending), 'value'],
[isAscending ? 'asc' : 'desc']
)
This worked for me
orders = [{id : "1", name : "test"}, {id : "1"}];
sortBy = ["id", "name"];
orderby(
orders,
sortBy.map(s => {
return (r: any) => {
return r[s] ? r[s] : "";
};
})),
);
I created a function for this (ts code):
const orderByFix = (array: any[], orderKeys: string[], orderDirs: ('asc' | 'desc')[]) => {
const ordered = orderBy(array, orderKeys, orderDirs);
const withProp = ordered.filter((o) => orderKeys.every(k => o[k]));
const withoutProp = ordered.filter((o) => !orderKeys.every(k => o[k]));
return [...withProp, ...withoutProp];
};
I've extended gwendall's answer to also handle case when "order keys" are functions (_.orderBy allows that)
const orderByFix = (
array: any[],
orderKeys: (string | ((o: any) => any))[],
orderDirs: ('asc' | 'desc')[]
) => {
const ordered = orderBy(array, orderKeys, orderDirs)
const withProp = ordered.filter((o) =>
orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
const withoutProp = ordered.filter(
(o) =>
!orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
return [...withProp, ...withoutProp]
}

Categories

Resources