How to sort keys when using js-yaml dump() - javascript

I'm using the js-yaml package. It has a function named dump() that will write out a JavaScript data structure in YAML format. I want to specify the order of the keys when it dumps out an object. Here is what their docs say:
sortKeys (default: false) - if true, sort keys when dumping YAML. If a function, use the function to sort the keys.
But there's absolutely no mention of how to use a function to sort the keys. What parameters does it accept? What return values does it expect? It's exactly what I need, but there's no documentation on how to use a function with the sortKeys option.
FYI, I don't want to sort the keys alphabetically. I don't want them to appear in a random order. I don't want them to appear in the order the keys were added. I want total control over which order they appear.

It appears that when you supply a function as the sortKeys option, that function behaves the same way as the function that you can use with the Perl sort() function. I.e. the function receives 2 arguments, call them a and b. If you want key a to appear before key b, return -1 from the function. If you want key a to appear after key b, return 1 from the function. If keys a and b should be considered equal, return 0 from the function. Using this method, I developed a function named toTAML that accepts a javascript data structure and an array of key names. When displaying an object, it will display the keys in the same order as in the supplied array. Keys that don't appear in the array will be put at the end, ordered alphabetically. I will post that function in CoffeeScript, then in the equivalent JavaScript.

import yaml from 'js-yaml'
compareFunc = (a, b) =>
if (a < b)
return -1
else if (a > b)
return 1
else
return 0
toTAML = (obj, lKeys) ->
h = {}
for key,i in lKeys
h[key] = i+1
sortKeys = (a, b) =>
if h.hasOwnProperty(a)
if h.hasOwnProperty(b)
return compareFunc(h[a], h[b])
else
return -1
else
if h.hasOwnProperty(b)
return 1
else
# --- compare keys alphabetically
return compareFunc(a, b)
return yaml.dump(obj, {
skipInvalid: true
indent: 3
sortKeys
lineWidth: -1
})
console.log toTAML({e:5,d:4,c:3,b:2,a:1}, ['c','b','a'])
Output:
c: 3
b: 2
a: 1
d: 4
e: 5

// Generated by CoffeeScript 2.7.0
var compareFunc, toTAML;
import yaml from 'js-yaml';
compareFunc = (a, b) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
};
toTAML = function(obj, lKeys) {
var h, i, j, key, len, sortKeys;
h = {};
for (i = j = 0, len = lKeys.length; j < len; i = ++j) {
key = lKeys[i];
h[key] = i + 1;
}
sortKeys = (a, b) => {
if (h.hasOwnProperty(a)) {
if (h.hasOwnProperty(b)) {
return compareFunc(h[a], h[b]);
} else {
return -1;
}
} else {
if (h.hasOwnProperty(b)) {
return 1;
} else {
// --- compare keys alphabetically
return compareFunc(a, b);
}
}
};
return yaml.dump(obj, {
skipInvalid: true,
indent: 3,
sortKeys,
lineWidth: -1
});
};
console.log(toTAML({
e: 5,
d: 4,
c: 3,
b: 2,
a: 1
}, ['c', 'b', 'a']));

Related

This question is about an exercise in the book Eloquent JavaScript

The last part to this exercise is to write a recursive function that takes two parameters, a joined list and an index respectively. The function will find the value in the object within the list at it's respective index. The code i have written works the way i want (i can see it working when i console.log for every occasion the function is called. But on the last occasion it refers undefined as my value. I cannot understand why. Oh and it works for index of 0. code as followed.
and first, list looks like this:
list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
// console.log(value);
// console.log(targetNum);
// console.log(list);
nth(list, targetNum);
}
};
console.log(nth(arrayToList([1,2,3]),2));
below is the code for arrayToList it was the first part of the exercise and if you have any comments that's cool, cause the hints ended up suggesting to build the list from the end.
const arrayToList = (arr) => {
let list = {
value: arr[0],
rest: nestObject()
};
function nestObject() {
let rest = {};
arr.shift();
const length = arr.length;
if (length == 1) {
rest.value = arr[0];
rest.rest = null;
} else {
rest.value = arr[0];
rest.rest = nestObject();
}
return rest;
}
return list;
};
Both solutions are convoluted and unnecessary verbose. Actually, both functions could be one-liners. Here are a few hints:
For the toList thing consider the following:
if the input array is empty, return null (base case)
otherwise, split the input array into the "head" (=the first element) and "tail" (=the rest). For example, [1,2,3,4] => 1 and [2,3,4]
return an object with value equal to "head" and rest equal to toList applied to the "tail" (recursion)
On a more advanced note, the split can be done right in the function signature with destructuring:
const toList = ([head=null, ...tail]) => ...
Similarly for nth(list, N)
if N is zero, return list.value (base case)
otherwise, return an application of nth with arguments list.rest and N-1 (recursion)
Again, the signature can benefit from destructuring:
const nth = ({value, rest}, n) =>
Full code, if you're interested:
const toList = ([value = null, ...rest]) =>
value === null
? null
: {value, rest: toList(rest)}
const nth = ({value, rest}, n) =>
n === 0
? value
: nth(rest, n - 1)
//
let lst = toList(['a', 'b', 'c', 'd', 'e', 'f'])
// or simply toList('abcdef')
console.log(lst)
console.log(nth(lst, 0))
console.log(nth(lst, 4))
You simply need to add a return when recursively calling nth. Otherwise the logic is carried out but no value is returned (unless targetNum is 0)
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
return nth(list, targetNum); // return needed here too
}
};
Or more succinctly:
const nth = (list, n) => n === 0 ? list.value : nth(list.rest, n - 1)
Here's another non-recursive arrayToList that builds the list from the end:
const arrayToList = arr => arr.slice().reverse().reduce((rest, value) => ({value, rest}), null);
(The slice here is just to make a copy of the array so that the original is not reversed in place.)
Georg’s recursive solutions are beautiful!
I’d like to add the hinted “build the list from the end” solution from the book:
const arrayToList => (arr) => {
var list
while (arr.length) {
list = {value: arr.pop(), rest: list}
}
return list
}

Couldn't understand how .reduce works in JavaScript

I was doing JavaScript exercise and got this question:
Write a JavaScript function that accepts a string as a parameter and find the longest word within the string.
Example string : 'Web Development Tutorial' (Development)
I found a solution and it was like this:
function longest(str) {
let arr = str.split(' ');
return arr.reduce((a,b) => a.length < b.length ? b : a, "");
}
console.log(longest('Web Development Tutorial'));
The code above does work, but I couldn't understand
arr.reduce((a,b) => a.length < b.length ? b : a, "")
part.
Does it mean
reduce(function(a, b) {
if (a.length < b.length) {
b
} else {
a,
"";
}
}
which still doesn't make much sense?
Some very good answers have been given. One way to look at what is happening is to put console.log. Just take a look at this code and you would know what is happening:
function longest(str) {
let arr = str.split(' ');
return arr.reduce((a,b) => {
console.log(`a=${a}, b=${b}`);
return a.length < b.length ? b : a;
}, "");
}
console.log(longest('Web Development Tutorial'));
The output should be self explanatory.
However, if you are not aware of arrow functions or template string literals then you need to learn them first to understand the output.
The first parameter to reduce is the accumulator, which is either the value returned from the last callback, or the initial value (which is either the first argument in the array, or the second argument passed to reduce).
It's equivalent to the following:
function longest(str) {
let arr = str.split(' ');
let a = ''; // initial value
arr.forEach((b) => {
a = a.length < b.length ? b : a;
});
return a;
}
Or, removing the conditional operator:
function longest(str) {
let arr = str.split(' ');
let a = ''; // initial value
arr.forEach((b) => {
if (b.length > a.length) {
a = b;
}
});
return a;
}
Remember that when an arrow function lacks a { after =>, the expression that follows the => will be implicitly returned, so
return arr.reduce((a,b) => a.length < b.length ? b : a, "");
is also equivalent to
return arr.reduce(function(a,b) {
return a.length < b.length ? b : a;
}, '');
reduce works kind of like a loop, involving a value (the current item in the array) and an accumulator (the value returned from the previous call to the function, or the second argument passed to reduce).
Since the purpose of reduce is to reduce the array into a single value, you can think of it like this:
let a = "";
arr.forEach(item => {
if (a.length < b.length) {
a = b;
} else {
a = a;
}
});
return accumulator;
Note that the else statement is irrelevant in the above code, but it represents how reduce works. Try looking it up too - this is a pretty good guide.
If you were to rewrite the code using if it would be:
function longest (a, b) {
if (a.length < b.length) {
return b;
}
else {
return a;
}
}
return arr.reduce(longest, "");
Note that the second argument to reduce (the "") initializes the accumulator variable (the a) to that value (empty string) when reduce starts looping. The syntax is a bit confusing with arrow functions because it could be mistaken for the comma operator when in fact it is simply the comma separating arguments to reduce.
reduce converts multiple (or indeterminate) values into a single value.
It starts with an initial value, which we'll call the "accumulator" and a binary function ("binary" meaning function with two formal parameters), which we'll call the "reducer". reduce applies the reducer to the accumulator and the first element of the list. The return value of the reducer function becomes the new accumulator. That's why we call it "accumulator", because it accumulates or gathers the results of the reducer. reduce repeats this process until there are no more elements in the list, and returns the last return value from the reducer.
Some applications:
Summing a List of Numbers
const add = (x, y) => x + y
[1, 2, 3, 4].reduce(add, 0)
// 10
In this case, x is the accumulator and y is the list element.
If we mentally execute this reducer, we'd have:
x: 0, y: 1
x: 1, y: 2
x: 3, y: 3
x: 6, y: 4
x: 10, STOP
Counting Table Columns from a Table's Data Model
const columnReducer = (tally, {layout}) => {
const isFullLayout = layout === 'full'
const isCompleteSemiLayout = tally.semiLayouts === 2
const columns = isFullLayout || isCompleteSemiLayout ? tally.columns + 1 : tally.columns
const semiLayouts =
isCompleteSemiLayout ? 0
: !isFullLayout ? tally.semiLayouts + 1
: tally.semiLayouts
return { columns, semiLayouts };
}
const { columns } = rows.reduce(columnReducer, { columns: 0, semiLayouts: 0});
Tallying Items
const materialsReducer = (items, { id, material }) => ({
...items,
[material]: {
tally: items[material] || 0 + 1,
ids: [...items[material] || [], id]
}
})
[
{ id: 1, material: 'wood' },
{ id: 2, material: 'gold' },
{ id: 3, material: 'wood' }
].reduce(materialsReducer, {})
// { wood: { tally: 2, ids: [1, 3] }, gold: { tally: 1, ids: [3] } }
reduce is 'declarative', meaning that you as the programmer describe the desired outcome. Contrast that with for loops, which are 'imperative', meaning that you as the programmer instruct the computer what to do in a step-by-step manner.

How to get desired sorting output in an array in javascript

I am trying to sort the object inside an array below in ascending order by it value and get the desired output as below:-
var arr = [{"DOA Qty":"0.000665921017598927382910198160","LOS%":"0","FID Valid EC By Part":"0.0041860443283016713761966","Interventions":"0"}]
Desired output - sort in ascending order by value:
var desiredarr =[{"LOS%":"0","Interventions":"0","DOA Qty":"0.000665921017598927382910198160","FID Valid EC By Part":"0.0041860443283016713761966"}]
let sorteddataarr: any = Object.values(arr[0]).sort(function (a, b) { return arr[a] - arr[b]; });
alert(JSON.stringify(sorteddataarr)); // not giving result
a[1]-b[1] == :ASEC
b[1]-a[1] == :DESC
Try this :
var obj =
{
"DOA Qty":"0.000665921017598927382910198160",
"LOS%":"0",
"FID Valid EC By Part":"0.0041860443283016713761966",
"Interventions":"0"
}
var entries = Object.entries(obj)
entries.sort(function(a,b){return a[1]-b[1]});
obj = {};
entries.map(function(item){
obj[item[0]] = item[1];
})
console.log(obj);
Maybe the following will help, the keys of an object are given in no particular order when doing things like Object.keys or for something in ..., since the order is important to you I converted the object(s) to array(s) of key(s) and value(s):
var arr = [{"DOA Qty":"0.000665921017598927382910198160","LOS%":"0","FID Valid EC By Part":"0.0041860443283016713761966","Interventions":"0"}];
console.log(
arr.map(
object=>
Object.keys(object).map(
key=>[Number(object[key]),key]//your values are not numbers, maybe create better JSON
).sort(
([a],[b])=>a-b
)
//optional, if you rather have [key,value] .map(([value,key])=>[key,value]
)
)
Create a simple compare function.
Define the key to use in the function. I used value
Then call Array.prototype.sort() and pass in your compare function
The only difference between the two for DESC or ASC order is that the greater than and less than symbols are switched
function compareDESC(a, b) {
if (a.value < b.value)
return 1;
if (a.value > b.value)
return -1;
return 0;
}
function compareASC(a, b) {
if (a.value > b.value)
return 1;
if (a.value < b.value)
return -1;
return 0;
}
var arr = [
{
value: 2
},
{
value: 6
},
{
value: 3
},
{
value: 8
},
{
value: 9
},
{
value: 4
},
];
arr.sort(compareDESC)
console.log(arr)
arr.sort(compareASC)
console.log(arr)

typescript array sorting with random sort order

I want to sort an array based on an sort order.
public array1: Array<string> = [];
array.push(some string values); //E.g dog,apple,man
I have a sort order like
dog = 1
apple = 3
man = 2
So i want to sort this array like dog,man,apple.
How to do this in typescript.
If you'd have your order in a map(object) you could just do this.
let list = ['dog', 'cat','apple'];
let sortOrder = {
dog: 1,
cat: 3,
apple: 2
}
list.sort((a, b) => sortOrder[a] - sortOrder[b]);
You can see a working playground example here.
I suppose you use TypeScript (as tagged in you question).
let data = [
{value:"dog", key:1},
{value:"apple", key:3},
{value:"man", key:2},
];
data.sort((d1, d2) => {
if (d1.key > d2.key) return 1;
if (d1.key < d2.key) return -1;
return 0;
});
You have enhanced your question then this answer is not relevant anymore (and fixed code as mentioned in comments).
This will also work. Source.
randomizeArray(a, b, c, d) { // array, placeholder, placeholder, placeholder
c = a.length; while (c) b = Math.random() * c-- | 0, d = a[c], a[c] = a[b], a[b] = d
}
And you can call this function this way, where data is an array []
this.randomizeArray(data, null, null, null);

Merge two javascript objects adding values of common properties

I have two or more javascript objects. I want to merge them adding values of common properties and then sort them in descending order of values.
e.g.
var a = {en : 5,fr: 3,in: 9}
var b = {en: 8,fr: 21,br: 8}
var c = merge(a,b)
c should then be like this:
c = {
fr: 24,
en: 13,
in:9,
br:8
}
i.e. both objects are merge, values of common keys are added and then keys are sorted.
Here's what I've tried:
var a = {en : 5,fr: 3,in: 9}
var b = {en: 8,fr: 21,br: 8}
c = {}
// copy common values and all values of a to c
for(var k in a){
if(typeof b[k] != 'undefined'){
c[k] = a[k] + b[k]
}
else{ c[k] = a[k]}
}
// copy remaining values of b (which were not common)
for(var k in b){
if(typeof c[k]== 'undefined'){
c[k] = b[k]
}
}
// Create a object array for sorting
var arr = [];
for(var k in c){
arr.push({lang:k,count:c[k]})
}
// Sort object array
arr.sort(function(a, b) {
return b.count - a.count;
})
but I dont think its good. So many loops :( It would be nice if someone can provide a less messy and good code.
In ES2015+, object properties are ordered (first by ascending numeric keys, then by insertion order for non-numeric keys). This is guaranteed by the specification if you use one of the methods for which iteration order is specified (like Object.getOwnPropertyNames).
In ES2020+, the methods for which enumeration order used to be unspecified are now specified (though environments have been following it for ages anyway).
But you have to be sure that none of the properties are numeric (otherwise, they'll come first, before non-numeric properties, no matter the insertion order).
Use reduce to iterate over each object and create or add to the same property on the accumulator. Then, sort the object's entries, and use Object.fromEntries to transform it into an object with sorted properties. No need for jQuery:
var a = {en : 5,fr: 3,in: 9}
var b = {en: 8,fr: 21,br: 8}
console.log(merge(a, b));
function merge(...objects) {
const merged = objects.reduce((a, obj) => {
Object.entries(obj).forEach(([key, val]) => {
a[key] = (a[key] || 0) + val;
});
return a;
}, {});
return Object.fromEntries(
Object.entries(merged).sort(
(a, b) => b[1] - a[1]
)
);
}
It is not possible to sort the properties of an object, you can however sort an array:
var merged = $.extend({}, a);
for (var prop in b) {
if (merged[prop]) merged[prop] += b[prop];
else merged[prop] = b[prop];
}
// Returning merged at this point will give you a merged object with properties summed, but not ordered.
var properties = [];
for (var prop in merged) {
properties.push({
name: prop,
value: merged[prop]
});
}
return properties.sort(function(nvp1, nvp2) {
return nvp1.value - nvp2.value;
});
EDIT - i modified the script, this merges the properties if they are of the same type: numbers are summed, strings are concatenated and objects are recursively merged. I didn't include sorting because (quoting this answer Sorting JavaScript Object by property value)
JavaScript objects are unordered by definition (see the ECMAScript
Language Specification, section 8.6). The language specification
doesn't even guarantee that, if you iterate over the properties of an
object twice in succession, they'll come out in the same order the
second time.
If you need things to be ordered, use an array and the
Array.prototype.sort method.
function is_object(mixed_var) {
if (Object.prototype.toString.call(mixed_var) === '[object Array]') {
return false;
}
return mixed_var !== null && typeof mixed_var == 'object';
}
function merge(a, b) {
var cache = {};
cache = unpackObject(a, cache);
cache = unpackObject(b, cache);
return cache;
}
function unpackObject(a, cache) {
for (prop in a) {
if (a.hasOwnProperty(prop)) {
if (cache[prop] === undefined) {
cache[prop] = a[prop];
} else {
if (typeof cache[prop] === typeof a[prop]) {
if (is_object(a[prop])) {
cache[prop] = merge(cache[prop], a[prop]);
} else {
cache[prop] += a[prop];
}
}
}
}
}
return cache;
}
var a = {
en: 5,
fr: 3,
in : 9,
lang: "js",
object: {nestedProp: 6}
}
var b = {
en: 8,
fr: 21,
br: 8,
lang: "en",
object: {nestedProp: 1, unique: "myne"}
}
var c = merge(a, b);
fiddle here http://jsfiddle.net/vyFN8/1/
Here is my attempt, which is recursive for nested objects - https://gist.github.com/greenafrican/19bbed3d8baceb0a15fd
// Requires jQuery
// Merge nested objects and if the properties are numbers then add them together, else
// fallback to jQuery.extend() result
function mergeObjectsAdd(firstObject, secondObject) {
var result = $.extend(true, {}, firstObject, secondObject);
for (var k in result) {
if ("object" === typeof result[k]) {
firstObject[k] = firstObject[k] || {};
secondObject[k] = secondObject[k] || {};
result[k] = mergeObjectsAdd(firstObject[k], secondObject[k]);
} else {
firstObject[k] = firstObject[k] || 0;
secondObject[k] = secondObject[k] || 0;
result[k] = ("number" === typeof firstObject[k] && "number" === typeof secondObject[k]) ? (firstObject[k] + secondObject[k]) : result[k];
}
}
return result;
}

Categories

Resources