How Can I sort Array object for two properties like "surname" - "name"?
I tried with concatenation but It doesn't works, because it sort only for second sorting:
computed:{
sortedArray: function() {
function name(a, b) {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
}
function surname(a, b) {
if (a.surname < b.surname) return -1;
if (a.surname > b.surname) return 1;
return 0;
}
return this.users.sort(surname).sort(name);
}
}
I 'have tried also with:
function surname(a, b) {
return a.surname < b.surname || a.name < b.name
}
But it return an array not sorted by surname / name
You are actually pretty close.
The problem is when you do:
return this.users.sort(surname).sort(name);
You first sort by surname and then by name ignoring surname.
The solution is to use a function that handles both properties at the same time. So join those sort functions into one.
Another thing, your computed is sorting in-place. You probably want to clone the array and return a sorted copy in the computed property instead.
new Vue({
el: '#app',
data: {
users: [
{name: "John", surname: "Nash"},
{name: "Paul", surname: "Pringles"},
{name: "Bob", surname: "Pringles"},
{name: "Bob", surname: "Abbey"},
{name: "Alice", surname: "Abbey"},
]
},
computed:{
sortedArray: function() {
function surnameName(a, b) {
if (a.surname < b.surname) return -1;
if (a.surname > b.surname) return 1;
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
}
// return this.users.sort(surnameName); // sorts in-place
return [...this.users].sort(surnameName); // shallow clone + sort
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>unsorted:<br>{{ users }}</p>
<p>sorted:<br>{{ sortedArray }}</p>
</div>
Related
I have an object array. I want to get for each element in the array the difference with the others.
For example thi is my object array:
array= [{id:1, name= 'test', isAdmin: true, userMail:'test#test.com', userTel: '+12555555'},
{id:1, name= 'test', isAdmin: false, userMail:'test#test.com', userTel: '+12555555'}, {id:1,
name= 'test', isAdmin: false, userMail:'test#test.com', userTel: '+12555785444'}]
the result is :
result = [isAdmin, userMail, userTel]
I tried this solution but not working :
for (let index = 0; index < this.array.length; index++) {
const e = this.array[index];
let j = index + 1;
let t;
while (index !== j && j <= (this.array.length - 1) && t === undefined) {
t = this.difference(array);
j++;
}
let y;
if (t !== null && t !== undefined) {
Object.keys(t).forEach(key => {
if (key !== 'idTrace') {
y = key;
}
});
}
this.result.push(y);
}
difference(object, base) {
return transform(object, (result, value, key) => {
if (!isEqual(value, base[key])) {
result[key] = isObject(value) && isObject(base[key]) ?
this.difference(value, base[key]) : value;
}
});
}
Any help, thanks in advance.
you can do it like this.
Please note that this is not the most performant solution, but it is understandable (readable) solution on which you can make optimisations as needed.
const objects = [{
"id": 1,
"name": "test",
"isAdmin": true,
"userMail": "test#test.com",
"userTel": "+12555555"
},
{
"id": 1,
"name": "test",
"isAdmin": false,
"userMail": "test#test123.com",
"userTel": "+12555555"
},
{
"id": 1,
"name": "test",
"isAdmin": false,
"userMail": "test#test.com",
"userTel": "+12555785444"
}
];
// Finds all the keys that are different in two objects.
const difference = (obj1, obj2) => {
let foundKeys = [];
Object.keys(obj1).forEach(key => {
if (obj1[key] !== obj2[key]) {
foundKeys.push(key);
}
});
return foundKeys;
};
let differentKeys = [];
// Compares every object with all objects and pushes to one array all differences in keys.
for (let i = 0; i < objects.length; i++) {
for (let j = i + 1; j < objects.length; j++) {
differentKeys = differentKeys.concat(difference(objects[i], objects[j]));
}
}
// Removes duplicates.
differentKeys = differentKeys.filter(function(value, index, differentKeys) {
return differentKeys.indexOf(value) === index;
});
console.log(differentKeys);
Let me know if you have any questions.
Does defining variables inside of a computed property have any impact on the perfomance of Vue components?
Background: I built a table component which generates a HTML table generically from the passed data and has different filters per column, filter for the whole table, sort keys, etc., so I'm defining a lot of local variables inside the computed property.
Imagine having an array of objects:
let data = [
{ id: "y", a: 1, b: 2, c: 3 },
{ id: "z", a: 11, b: 22, c: 33 }
]
..which is used by a Vue component to display the data:
<template>
<div>
<input type="text" v-model="filterKey" />
</div>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filteredData" :key="item.id">
<td v-for="(value, key) in item" :key="key">
{{ value }}
</td>
</tr>
</tbody>
</table>
</template>
The data gets filtered via input:
<script>
export default {
props: {
passedData: Array,
},
data() {
return {
filterKey: null,
};
},
computed: {
filteredData() {
// defining local scope variables
let data = this.passedData;
let filterKey = this.filterKey;
data = data.filter((e) => {
// filter by filterKey or this.filterKey
});
return data;
},
},
};
</script>
My question refers to let data = .. and let filterKey = .. as filteredData() gets triggered from any change of the filterKey (defined in data()) so the local variable gets updated too, although they're not "reactive" in a Vue way.
Is there any impact on the performance when defining local variables inside a computed property? Should you use the reactive variables from data() (e. g. this.filterKey) directly inside of the computed property?
The best way to test if something affects performance, is to actually test it.
According to my tests below, it is consistency more than 1000% slower to use this.passedData instead of adding a variable on top of the function. (869ms vs 29ms)
Make sure you run your benchmarks on the target browsers you write your application for the best results.
function time(name, cb) {
var t0 = performance.now();
const res = cb();
if(res !== 20000000) {
throw new Error('wrong result: ' + res);
}
var t1 = performance.now();
document.write("Call to "+name+" took " + (t1 - t0) + " milliseconds.<br>")
}
function withoutLocalVar() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += this.hi + this.hi2;
}
return sum;
},
}
})
return vue.test;
}
function withLocalVar() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
const hi = this.hi;
const hi2 = this.hi2;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += hi + hi2;
}
return sum;
},
}
})
return vue.test;
}
function benchmark() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
const hi = 1;
const hi2 = 1;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += hi + hi2;
}
return sum;
},
}
})
return vue.test;
}
time('withoutLocalVar - init', withoutLocalVar);
time('withLocalVar - init', withLocalVar);
time('benchmark - init', benchmark);
time('withoutLocalVar - run1', withoutLocalVar);
time('withLocalVar - run1', withLocalVar);
time('benchmark - run1', benchmark);
time('withoutLocalVar - run2', withoutLocalVar);
time('withLocalVar - run2', withLocalVar);
time('benchmark - run2', benchmark);
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
I have multiple columns in my table, for example:
id | name | amount | description
And I want to sort each column - on the first click in ascending order, on the second on descending order, on the third go back to default, and all over again.
The default is the id column sorded in asc order.
So, the default state in the reducer is:
sort: {
key: 'id',
desc: false
}
The next steps on clicking name column would be:
sort: {
key: 'name',
desc: false
}
sort: {
key: 'name',
desc: true
}
sort: {
key: 'id',
desc: false
}
The view calls an action using column's name as a parameter:
<td onClick={() => this.props.sort('name')}>Name</td>
<td onClick={() => this.props.sort('amount')}>Amount</td>
An action should dispatch such key and desc values so that it matches my pattern:
export function sort(key) {
return dispatch => {
};
};
How can I do this?
Here you go, brief explanation in code sample.
I setup 2 columns only, cause I am lazy, sorry.
Fiddle: https://jsfiddle.net/u1wru0gb/1/
const data = [
{ id: 1, name: 'Bruce' },
{ id: 3, name: 'Martin' },
{ id: 2, name: 'Andrew' },
];
/**
* Nothing interesting, just render...
*/
function Table({ data, sortByKey }) {
const renderRow = ({ id, name }, idx) => (
<tr key={idx}>
<td>{id}</td>
<td>{name}</td>
</tr>
)
return (
<table>
<tr>
<th onClick={sortByKey('id')}>ID</th>
<th onClick={sortByKey('name')}>Name</th>
</tr>
{ data.map(renderRow) }
</table>
);
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
sort: {
key: undefined,
// 0 - not ordering
// 1 - asc
// 2 - desc
order: 0,
},
};
this.sortByKey = this.sortByKey.bind(this);
}
sortedData() {
const { key, order } = this.state.sort;
// Only sort if key is provided & order != 0.
if (key && order) {
// Comparison function for "asc" sorting.
function compare(a, b) {
if (a[key] < b[key]) return -1;
if (a[key] > b[key]) return 1;
return 0;
}
// Attention! Sort mutates array, clone first.
return [...this.props.data].sort((a, b) => {
// Interesting part. Sort in "asc" order. Flip if want "desc" order!
return compare(a, b) * (order === 1 ? 1 : -1);
});
}
// Return original data (order = 0)
return this.props.data;
}
sortByKey(key) {
return () => {
const sort = (this.state.sort.key === key)
// Key matches, update order
? { key, order: (this.state.sort.order + 1) % 3 }
// Key differs, start with "asc" order
: { key, order: 1 };
this.setState({ sort });
}
}
render() {
return (
<Table data={this.sortedData()} sortByKey={this.sortByKey} />
);
}
}
ReactDOM.render(
<Container data={data} />,
document.getElementById('container')
);
I have data that looks like this:
var records = [
{id: '1', cat: 'A'},
{id: '2', cat: 'A'},
{id: '3', cat: 'B'},
{id: '4', cat: 'B'},
{id: '5', cat: 'B'},
{id: '6', cat: 'C'}
];
I want to create a dc.numberDisplay that displays the count of the number of unique categories, 3 in the example data above (A, B, & C).
This is what I'm currently doing:
var ndx = crossfilter(data); // init crossfilter
// create dimension based on category
var categoryDimension = ndx.dimension(
function (d) {
return d.category;
}
);
// Group by category
var categoryGroup = categoryDimension.group();
var categoryCount = dc.numberDisplay('#category-count'); // An empty span
categoryCount
.group(categoryGroup)
.valueAccessor(
function (d) { return d.value; }
);
The problem is that the numberDisplay displays 2 instead of 3. When debugging, I found that when the valueAccessor is called, d is the count of the number of elements of category A instead of the count of the number of categories.
How can I solve this problem?
UPDATE: Thanks to Nathan's solution, here is a working code snippet (ES2016 style)
const categoryDimension = claims.dimension(
(d) => {
return d.cat;
}
);
const categoryGroup = categoryDimension.groupAll().reduce(
(p, v) => { // add element
const cat = v.cat;
const count = p.categories.get(cat) || 0;
p.categories.set(cat, count + 1);
return p;
},
(p, v) => { // remove element
const cat = v.cat;
const count = p.categories.get(cat);
if (count === 1) {
p.categories.delete(cat);
} else {
p.categories.set(cat, count - 1);
}
return p;
},
() => { // init
return {
categories: new Map()
};
});
categoryCount
.group(categoryGroup)
.valueAccessor(
(d) => {
return d.categories.size;
}
);
You will need to use groupAll() since the number-display only looks at the top group. Then provide custom reduce functions to track unique categories. Finally, when DC.js pulls the value from the top group (there is only one) - just return the number of categories (which is the number of keys in the p object).
var categoryGroup = categoryDimension.groupAll().reduce(
function (p, v) { //add
if(p[v.cat]) {
p[v.cat]++;
} else {
p[v.cat] = 1;
}
return p;
},
function (p, v) { //remove
p[v.cat]--;
if(p[v.cat] === 0) {
delete p[v.cat];
}
return p;
},
function () { //init
//initial p - only one since using groupAll
return {};
}
);
console.debug("groups", categoryGroup.value());
dc.numberDisplay('#category-count')
.group(categoryGroup)
.valueAccessor(
function (d) { return Object.keys(d).length; }
);
I would like a crossfilter group that gives the word frequency and average rating for each word in a series of surveys so that I can make an awesome interactive word-bubble-frequency chart.
My data looks like:
[{feedback: "This is a horrible service", rating:2},
{feedback: "I love everything about everything", rating: 10},
{feedback: "love the user interface, good service", rating:6},
{feedback: "", rating: 7} ]
I would like something like:
[ {key: love, count:2, ave: 8}, {key: horrible, count:1, rating:2 }, {key: service,
count: 2, rating: 4 } ,.... ]
So far I have:
function to break up string into tokens, returns object with frequency for each word
var wordcnt = function(bah ){
var hist = {}, words = bah.split(/[\s*\.*\,\;\+?\#\|:\-\/\\\[\]\(\)\{\}$%&0-9*]/)
for( var i in words)
if(words[i].length >1 )
hist[words[i]] ? hist[words[i]]+=1 : hist[words[i]]=1;
return hist;
};
Loading data into d3 and crossfilter
d3.csv("test.csv", function( error, data) {
data.forEach(function(d) {
d.rating= +d.rating;
d.wordCount= wordcnt(d.feedback.toLowerCase());
});
var ndx = crossfilter(data);
var all = ndx.groupAll();
var frequencyDimension=ndx.dimension(function(d){return d.wordCount; });
And one butchered group reduce function!
var frequencyGroup= frequencyDimension.group().reduce(
function (p, v) {
for (var key in v.wordCount) {
if (v.frequency.hasOwnProperty(key)) {
p.frequency[key]+= v[key];
p.frequency[key].count++;
p.frequency[key].sum+= v.rating ;
}
else{
p.frequency[key]=v[key];
p.frequency[key].count=1;
p.frequency[key].sum = v.rating;
}
}
p.frequency[key].ave = p.frequency[key].sum/p.frequency[key].count ;
return p;
},
function (p, v) {
for (var key in v.wordCount) {
if (v.frequency.hasOwnProperty(key)) {
p.frequency[key]-= v[key];
p.frequency[key].count--;
p.frequency[key].sum-= v.rating ;
}
//don't need an else statement because can't remove key if it doesn't exist
}
p.frequency[key].ave = p.frequency[key].sum/p.frequency[key].count ;
return p;
},
function (p,v) {
return { frequency: {} } ;
}
)