I am trying to figure a way in JS to sort an array without actually changing the order of the elements but have more like a "grouping" feature.
Sample Data:
[
{group: 'test', title: 'A'},
{group: 'test', title: 'B'},
{group: 'test2', title: 'C'},
{group: 'test', title: 'D'}
]
My idea is to do an [].sort() the way that my final order is:
[
{group: 'test', title: 'A'},
{group: 'test', title: 'B'},
{group: 'test', title: 'D'},
{group: 'test2', title: 'C'}
]
Means: I did not change the order of the first two items but only switched #3 and #4 because of their group. If I'd do with a classical localeCompare, I'd end up with my groups being restored based on their value which is not what I want, their order should be kept.
EDIT: Considering the answers, I want to re-clarify that I do not want to change the order of the groups, means they should not be alphabetically sorted but instead, they should be groupped by the order they appear.
You need to provide a custom comparison function to Array.sort
var a = [
{group: 'test', title: 'A'},
{group: 'test', title: 'B'},
{group: 'test2', title: 'C'},
{group: 'test', title: 'D'}
];
function myComparisonFunction(a1, b1) {
return a1.group < b1.group ? -1 : a1.group === b1.group ? 0 : 1;
}
a.sort(myComparisonFunction);
console.log(a);
Try this:
function compare(b,c) {
if (b.title < c.title)
return -1;
if (b.title > c.title)
return 1;
return 0;
}
objs.sort(compare);
You can try with a compare function similar to this:
function comp(a, b) {
if (a.group == b.group) {
return a.title < b.title ? -1 : a.title == b.title ? 0 : 1;
}
return a.group < b.group ? -1 : 1;
}
youArray.sort(comp)
If I understood your question correctly, you want to sort on groups first, but then sort on title if your groups are the same.
Related
I have an array of objects that represent some funding data. Each object has a key name "type" that is either c or e. I need to return a table that makes sure the order of the rows follows this pattern: c row => e row => c row => e row... etc.
The data comes back by an ID in desc order and they can be deleted so if there is an original dataset
[{type: 'c', id: 1}, {type: 'e', id: 2}, {type: 'c', id: 3}, {type: 'e', id: 4}]
and we remove the first {type: 'e', id: 2} I want the table to display
[{type: 'c', id: 1}, {type: 'e', id: 4}, {type: 'c', id: 3}]
NOT
[{type: 'c', id: 1}, {type: 'c', id: 3}, {type: 'e', id: 4}]
Is there a way to utilize javascript's sort() here or do I need to write a custom sort function and if so, what would that look like?
I don't think you can use sort in any way here.
The easiest solution, I think, to make sure it follows your pattern would be to split your array in 2 smaller arrays, one with all type: 'c' and another with all type: 'e', then re-merging them together every time you make a change to it.
Something like :
let originalArray = [{type: 'c', id: 1}, {type: 'e', id: 2}, {type: 'c', id: 3}, {type: 'e', id: 4}];
originalArray.splice(2, 1)
let arrayOfTypeE = originalArray.filter(item => item.type === 'e');
let arrayOfTypeC = originalArray.filter(item => item.type === 'c');
let finalArray = [];
for (i = 0; i< arrayOfTypeE.length + arrayOfTypeC.length; i++){
if(i %2 === 0){
finalArray[i] = arrayOfTypeE[Math.floor(i / 2)];
} else {
finalArray[i] = arrayOfTypeC[Math.floor(i / 2)];
}
}
finalArray.forEach((e,i)=>console.log(i, '=>' ,JSON.stringify(e)))
.as-console-wrapper { max-height: 100% !important; top: 0; }
But you need to be sure there won't be an array that's 2 elements shorter than the other, otherwise it become impossible to maintain your pattern. And the whole splitting/re-merging could be made into a function
I am receiving the following array from an API response:
[
{group: '1', , value: 'a'}
{group: '1', , value: 'b'}
{group: '2', , value: 'c'}
{group: '2', , value: 'd'}
]
I want to convert it to the following (want order of groups as received, values can be ordered in any way):
[
{group: '1', values: ['b', 'a'] },
{group: '2', values: ['c', 'd'] },
]
Which Javascript function will be the most efficient to convert it?
I am able to do this by:
let result = [];
data.reduce((groupNumber, input) => {
if (!groupNumber[input.group]) {
groupNumber[input.group] = {group: input.group, values: []};
result.push(groupNumber[input.group]);
}
groupNumber[input.group].values.push(input);
return groupNumber;
}, {});
Is reduce the correct function to be used here? Is there a more efficient approach?
Secondly, will reduce preserve the order in the result? If I am actually receiving the data with group 1 entries first, group 2 next, and so on, can I rely on result being ordered similarly?
Note that I only care about the order of the groups, not about the values inside the group.
Actually either by using reduce() or any other Array methods, the order will be always preserved as they will be executed from index 0 till the end of the array.
But reduce() is mainly used to accumulate the array elements into a single element, it's not the best method for doing such thing but it can be used as well.
Note that your actual code using reduce() isn't returning the right output.
In my opinion I think Array.map() method is better in this case, but it should be used in combination with Array.filter() to remove duplicate elements that will be kept with map():
var result = data.map(v => {
v.values = data.filter(e => e.group == v.group).map(x => x.value);
delete v.value;
return v;
}).filter(x => !x.values.some(e => !e));
Demo:
let data = [{
group: '1',
value: 'a'
}, {
group: '1',
value: 'b'
}, {
group: '2',
value: 'c'
}, {
group: '2',
value: 'd'
}];
var result = data.map(v => {
v.values = data.filter(e => e.group == v.group).map(x => x.value);
delete v.value;
return v;
}).filter(x => !x.values.some(e => !e));
console.log(result);
I would save my reply because we have a great explanation why your approach is great, because you have O(N) computational complexity (Thanks for the great comments to #CertainPerformance) and you iterate your array just one time.
Testing whether reduce method preserve order of object keys:
It looks like reduce method is not preserving order of keys. Here we see that keys are sorted in order how then iterated through source array myArray, however in Chrome browser this behavior is not reproducible(keys are sorted):
var myArray = [{letter: 'c'}, {letter:'e'}, {letter: 'e'}, {letter:'b'}, {letter: 'c'}, {letter:'a'}, {letter:'b'}, {letter:'a'}, {letter: 'd'}, {letter: 'd'}, {letter: 'd'}, {letter: 'd'}];
var myOrderedKeys = myArray.reduce((a, {letter}) => {
a[letter] = a[letter] || letter;
a[letter] = letter;
return a;
}, {})
console.log(myOrderedKeys);
Another example where we have more than one letter:
let notSimpleLetters = [{letter: 'ce'}, {letter:'ec'}, {letter: 'ec'}, {letter:'bw'}, {letter: 'ce'}, {letter:'aw'}, {letter:'ba'}, {letter:'aa'},
{letter: 'df'}, {letter: 'cb'}, {letter: 'dc'}, {letter: 'da'}];
let notSimpleKeys = notSimpleLetters.reduce((a, {letter}) => {
a[letter] = a[letter] || letter;
a[letter] = letter;
return a;
}, {})
console.log(notSimpleKeys);
What is the best way to add List to List in Immutable.js?
concat method is working, but another way is not working.
const a = fromJS([
{
comment: 'aaaa',
who: 'a1',
buttonInfo: ['a', 'b', 'c'],
},
{
comment: 'bb',
who: 'a2',
buttonInfo: ['a', 'b', 'c'],
},
]);
const b = fromJS([
{
comment: 'ccc',
who: 'c1',
buttonInfo: ['a', 'b'],
},
{
comment: 'ddd',
who: 'd2',
buttonInfo: ['a''],
},
]);
This is working:
a.concat(b)
But this is not working:
[...a ,...b]
// or
b.map(v => {
a.push(v);
})
you can use concat method as it said in doc:
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const array = [ 7, 8, 9 ];
const list3 = list1.concat(list2, array);
// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
An ImmutableJS list has a method named concat whose behavior is the same as a normal javascript array. However, you cannot use spread syntax for an Immutable array.
Also the syntax for push is different from the normal array, push like concat with Immutable List returns a new list, your map method will look like
b.map(v => {
a = a.push(v);
})
P.S. Using the above method though will mutate your array a. You must create a new List and then push both the array contents into it if you want to use push. However concat is the best way for your case
For add List to List in Immutable.js, you can use merge method.
Example:
const a = fromJS(
[
{
comment: 'aaaa',
who: 'a1',
buttonInfo: ['a', 'b', 'c'],
},
{
comment: 'bb',
who: 'a2',
buttonInfo: ['a', 'b', 'c'],
},
]
);
const b = fromJS(
[
{
comment: 'ccc',
who: 'c1',
buttonInfo: ['a', 'b'],
},
{
comment: 'ddd',
who: 'd2',
buttonInfo: ['a''],
},
]
);
a.merge(b);
I have an array:
var a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
]
And I want to get transform it to:
var b = {
1: 'a',
2: 'b',
3: 'c',
4: 'd',
}
Actually I'm using pure js:
var b = a.reduce(
(ac, pr) => ({
...ac,
[pr.id]: pr.val,
}),
{}
);
But maybe Ramda.js have something special for that purpose?
You are looking for Ramda's .mergeAll() method:
var b = R.mergeAll(a.map(function(o) {
return {
[o.id]: o.val
}
}));
The .map()call will return the custom object from each item, taking only the values, then .mergeAll() will merge the array into one object.
mergeAll Documentation:
Merges a list of objects together into one object.
Demo:
var a = [{
id: 1,
val: 'a'
},
{
id: 2,
val: 'b'
},
{
id: 3,
val: 'c'
},
{
id: 4,
val: 'd'
},
];
var b = R.mergeAll(a.map(function(o) {
return {
[o.id]: o.val
}
}));
console.log(b);
<script src="https://cdn.jsdelivr.net/ramda/0.18.0/ramda.min.js"></script>
If anyone still passes by here, it does indeed:
R.indexBy(R.prop('id'), someArray);
See indexBy in Ramda's documentation
EDIT:
Bennet is correct. If we want val as the only value per key, we can "pluck" it out after:
const createValDict = R.pipe(
R.indexBy(R.prop('id')),
R.pluck('val')
)
const valDict = createValDict(myArr)
Pluck works on objects too
Get the ordered values from each object by mapping with R.props, and use R.fromPairs to create an object:
var a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
];
var result = R.compose(R.fromPairs, R.map(R.props(['id', 'val'])));
console.log(result(a));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
With plain Javascript, you could use a combination with Object.assign, spread syntax ..., Array#map, destructuring assignment and short hand properties.
var a = [{ id: 1, val: 'a' }, { id: 2, val: 'b' }, { id: 3, val: 'c' }, { id: 4, val: 'd' }],
result = Object.assign(...a.map(({ id, val }) => ({ [id]: val })));
console.log(result);
var a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
]
var result = {};
for (var i=0; i<a.length; i++) {
result[a[i].id] = a[i].val;
}
console.log(result);
If you wanted something point-free, you could write:
const combine = compose(mergeAll, map(lift(objOf)(prop('id'), prop('val'))))
const {compose, mergeAll, map, lift, objOf, prop} = R;
const combine = compose(mergeAll, map(lift(objOf)(prop('id'), prop('val'))))
var a = [{id:1, val:'a'}, {id:2, val:'b'}, {id:3, val:'c'}, {id:4, val:'d'}]
console.log(combine(a));
<script src="https://cdn.jsdelivr.net/ramda/0.18.0/ramda.min.js"></script>
Here it works like a charm :
var a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
];
// var b = R.fromPairs( a.map(Object.values) );
// Perhaps this is the more general and order independent way:
var b = R.fromPairs(a.map( ({id,val})=>[id,val] ));
console.log( b );
<script src="//cdn.jsdelivr.net/npm/ramda#latest/dist/ramda.min.js"></script>
This might be the simplest way:
pipe(map(props(['id', 'val'])), fromPairs)(a)
#spflow's answer is simpler but not guaranteed to work on all platforms. Ramda code golf is always fun!
const { fromPairs, map, pipe, props } = R
const a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
]
const result = pipe(map(props(['id', 'val'])), fromPairs)(a)
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>
Yet one approach:
const { indexBy, prop, pipe, pluck } = R
const a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
]
const result = pipe(indexBy(prop('id')), pluck('val'))(a)
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>
Simplest, point-free:
compose(fromPairs, map(values))(a)
const { compose, fromPairs, map, values } = R
const a = [
{id: 1, val: 'a'},
{id: 2, val: 'b'},
{id: 3, val: 'c'},
{id: 4, val: 'd'},
]
const result = compose(fromPairs, map(values))(a)
console.log(result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
How can I sort an array by string value?
If I have an array such as ['you', 'I', 'me', 'me', 'will', 'me'], how can I get all the indexes with the word me at the front of the array?
I have tried using array.sort, but it does not seem to be working.
e.target.value is the an value I am getting from a <select element in the dom.
arr.sort((a, b) => {
if (a < e.target.value) {
return -1;
}
if (a > e.target.value) {
return 1;
}
return 0;
});
UPDATE:
Yury's answer works very well, but what if I need to match two values in an array of objects and have those sorted.
Example:
arr = [
{id: 1, name: "cookie"},
{id: 2, name: 'foo'},
{id: 3, name: 'bar'},
{id: 2, name: 'foo'}
];
How can I place all the elements with the id '2' and with the name 'foo' at the front of the array?
You could use sort
let a = ['you', 'I', 'me', 'me', 'will', 'me'];
a.sort((a, b) => a !== b && b === 'me' ? 1 : 0);
console.log(a)
const arr = [
{id: 1, name: "cookie"},
{id: 2, name: 'foo'},
{id: 3, name: 'bar'},
{id: 2, name: 'foo'}
];
Use the Array.prototype.sort() method on arr using a callback function that switches the order of items only if the first one does not match the given criteria and the second one does.
arr.sort((item1, item2) => {
if((item1.id !== 2 || item1.name !== 'foo') && (item2.id === 2 || item2.name === 'foo')) {
return 1;
}
return 0;
});
console.log(arr);