So I have an array of objects. Really the objects follow the GeoJSON specification, so keep that in mind. Within the "properties" objects, exists a proerty of "name". This name will be A, B, C... blah ... Z, AA, AB, etc. etc. for each different feature. See JSON example (I did strip out some other things which weren't important to this question, such as the geometry and what not...):
{
"features" : [{
"properties" : {
"name" : "A",
"description" : null,
},
"type" : "Feature"
}, {
"properties" : {
"name" : "B",
"description" : null,
},
"type" : "Feature"
}, {
"properties" : {
"name" : "C",
"description" : null,
},
"type" : "Feature"
}
],
"type" : "FeatureCollection"
}
What I'd like to do is find the MAX letter within this array of features to return the next one in the series. In this example, 'C' would be considered the MAX, so I should need to return the value of 'D'. If I had AA, that would be considered the MAX, and it would return 'AB'. If the max value happened to be a 'Z', I'd like to return a value of 'AA'.
Can ignore use of lowercase and only utilize 26, upper-cased english letters. No other characters.
I believe I could solve this with some usage of the javascript CharCodeAt(index) as well as applying Math.max, adding + 1, then converting back to it's ascii character represenation... but I'm having trouble bringing this together in a working function that loops through all this stuff.
Help would be appreciated!
Update:
I got it partially working with the following code. However haven't quite figured out how to get it to work if it wraps around from Z to AA. Or if the MAX is found to be AF, then returning AG. AZ would have to return BA.
String.fromCharCode(Math.max.apply(Math,someObject.features.map(function(o){return o.properties.name.charCodeAt(0);})) + 1)
Other known rules:
Upper limit can be ZZ - highly unlikely I'd need to wrap back to AAA
The max character will not always be last in the array, so can't
simply get the last feature of the array.
The solution using Array.sort, String.fromCharCode and String.charCodeAt functions:
var someObject = {
"features" : [{
"properties" : { "name" : "AB", "description" : null},
"type" : "Feature"
}, {
"properties" : {"name" : "B", "description" : null},
"type" : "Feature"
}, {
"properties" : { "name" : "AF", "description" : null},
"type" : "Feature"
}
],
"type" : "FeatureCollection"
};
function getNext(data) {
data.features.sort(function(a,b){
return a.properties.name.length - b.properties.name.length ||
a.properties.name.localeCompare(b.properties.name);
});
var last = data.features[data.features.length - 1].properties.name;
if (last.length === 1) {
return (last === "Z")? "AA" : String.fromCharCode(last.charCodeAt(0) + 1);
} else {
if (last === "ZZ") return last; // considering 'ZZ' as a limit
if (last[1] !== "Z") {
return last[0] + String.fromCharCode(last[1].charCodeAt(0) + 1);
} else if (last[1] === "Z"){
return String.fromCharCode(last[0].charCodeAt(0) + 1) + "A";
}
}
}
console.log(getNext(someObject)); // 'AG'
You can find the MAX string with:
var maxName = null,
obj = null,
name = null;
for(var idx = 0; idx < features.length; ++idx){
obj = features[idx];
name = obj.properties.name;
if(maxName == null || name.length > maxName.length ||
(name.length == maxName.length && name > maxName)
){
maxName = name;
}
}
I'm still working on getting the next name though.
I think you want to sort, which actually JS has a nice feature for comparing strings. However, it would return that ("AA" > "Z") === true so you want to account for length as well.
I think this works.
function sortName(a, b){
a = a.properties.name;
b = b.properties.name;
if(a.length>b.length){
return 1;
}
if(a > b){
return 1;
}
return -1;
}
console.log(someObject.features.sort(sortName)[0]. properties.name);
You can do it using
var obj = {
"features": [{
"properties": {
"name": "A",
"description": null,
},
"type": "Feature"
}, {
"properties": {
"name": "B",
"description": null,
},
"type": "Feature"
}, {
"properties": {
"name": "C",
"description": null,
},
"type": "Feature"
}],
"type": "FeatureCollection"
};
var largest = Math.max.apply(Math, findProp(obj.features, "name"));
console.log(changeToStr(largest + 1));
Where findProp is getting the property value array, changeToStr is converting number to string and changeToNum is converting number to String.
function changeToNum(val) {
var base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
i, j, result = 0;
for (i = 0, j = val.length - 1; i < val.length; i += 1, j -= 1) {
result += Math.pow(base.length, j) * (base.indexOf(val[i]) + 1);
}
return result;
};
function changeToStr(number) {
var baseChar = ("A").charCodeAt(0),
letters = "";
do {
number -= 1;
letters = String.fromCharCode(baseChar + (number % 26)) + letters;
number = (number / 26) >> 0;
} while (number > 0);
return letters;
}
function findProp(obj, key, out) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(out)) out = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
out.push(changeToNum(obj[i]));
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findProp(obj[i], key, out);
}
}
}
return out;
}
See the working Fiddle here.
Related
I have an object whose keys I don't know but structure is basically the same. Value can be a string or another object of strings/objects. Here is an example:
d = {
"name": "Sam",
"grade": 9,
"classes": {
"a": 1,
"b": 2
},
"age": null
}
What I want now is if value is not another object, get the key name and its value. If value is null, return empty string. From the above the expected output is:
name=Sam, grade=9, a=1, b=2, age=''
Here since classes is object, it has to be looped again to get keys (a,b) and values (1,2).
I tried the following but it gives if any of the values is null, it returns an error:
Cannot convert undefined or null to object
It works well if there is no null value:
function getKeyValues(data) {
var q = '';
f(data);
function f(s) {
Object.keys(s).forEach(function(key) {
if (typeof s[key] === 'object') {
f(s[key]);
} else {
q = q + key + '=' + (s[key] == null) ? "" : s[key] + '&';
}
});
}
return q;
}
d = {
"name": "Sam",
"grade": 9,
"classes": {
"a": 1,
"b": 2
},
"age": null
}
console.log(getKeyValues(d));
Try this one:
function getKeyValues(data) {
var q = [];
var keys = Object.keys(data);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data[key];
if (value == null) {
q.push(key + "=''");
} else if (typeof value == "object") {
q.push(getKeyValues(value));
} else {
q.push(key + "=" + value);
}
}
return q.join(",");
}
Another approach is to use the reduce method. Personally I find it a little bit cleaner.
function getKeyValues(d) {
return Object.keys(d).reduce((memo, key) => {
if (!d[key]) {
memo[key] = '';
} else if (typeof d[key] === 'object') {
Object.keys(d[key]).forEach((subKey) => {
memo[subKey] = d[key][subKey];
})
} else {
memo[key] = d[key];
}
return memo;
}, {})
}
Also, while your question is very clear, I must say that it also makes me a little bit wary. You could find yourself in some difficult debugging situations if property names are ever repeated in nested objects. For example, if
d={"name":"Sam","grade":9,"buddy":{"name":"Jeff","age":12}}
would you expect name be "Sam" or "Jeff"? A function that answers your question could return either, so that is something to be aware of going forward.
For the following json
[
{
"index": "xyz",
...
},
{
"index": "abc1234",
...
},
{
"index": "xyz",
...
},
{
"index": "abc5678",
...
}
...
I want to filter out abc values and xyz values separately.
I tried the following to get values
var x = _.filter(jsonData, function (o) {
return /abc/i.test(o.index);
});
and it worked to give the filtered outputs.
Now i want to get the highest of abc values that is if there values abc123, abc444, abc999 then the code should return abc999.
I can loop over again using lodash but could this be done in a single call - within the same one that filters out?
You can use Array.prototype.reduce(), String.prototype.replace() with RegExp /\D+/ to match and remove characters that are not digits. Check if previous number portion of string is less than current number portion of string
var jsonData = [
{
"index": "xyz",
},
{
"index": "abc1234",
},
{
"index": "xyz",
},
{
"index": "abc5678",
},
{
"index": "abc1",
}];
var x = jsonData.reduce(function (o, prop) {
return /abc/i.test(prop.index)
? !o || +prop.index.replace(/\D+/, "") > +o.replace(/\D+/, "")
? prop.index
: o
: o
}, 0);
console.log(x);
Following is a crude and unsatisfactory implementation:
//filter out matching objects for possible future use
var latest = "";
var matches = _.filter(jsonData, function (o) {
var ret = /abc/i.test(o.index);
if (ret) {
var digits = o.index.replace(/\D/g,'')
if (parseInt(digits) > latest) {
latest = digits;
latestIndex = o.index
console.log(latest+">>>latestIndex")
}
return true;
}
return false;
});
console.log("latestIndex->"+latest);
}
If you want to find the highest abc{SOME_NUMBER} value and filter at the same time, you can just use regular iteration instead of _.filter:
let jsonData = [{"index": "xyz"},{"index": "abc1234"}, {"index": "xyz"},{"index": "abc5678"}];
let max = Number.MIN_SAFE_INTEGER; // value for the max number at the end of "abc"
let item; // item containing the max abc${NUMBER} value
let filtered = []; // filtered array containing abc strings
jsonData.forEach((curr) => {
// filter test
if (/abc/i.test(curr.index)) {
filtered.push(curr);
// max value test
const [digits] = curr.index.match(/\d+/);
const test = parseInt(digits);
if (test > max) {
max = test;
item = curr;
}
}
});
console.log('Item:\n', item, '\n\n----\nFiltered:\n', filtered);
One way to do it using lodash is by replacing filter with maxBy in your code.
Of course, this has the downside that if no valid elements exist in the collection, it'll arbitrarily return an invalid one. So, after getting the result, an extra validity check is needed.
This is why I have extracted the validation/filter code in a separate function:
var jsonData = [{
"index": "xyz",
}, {
"index": "abc1234",
}, {
"index": "xyz",
}, {
"index": "abc5678",
}];
var isValid = function(o) {
return /abc/i.test(o.index);
};
var highest = _.maxBy(jsonData, isValid);
if (isValid(highest)) {
console.log('The max value is: ' + highest.index);
} else {
console.log('No valid value found!');
}
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>
And here's how it works if there are no valid elements:
var jsonDataWithoutValidValues = [{
"index": "xyz",
}, {
"index": "xyz",
}];
var isValid = function(o) {
return /abc/i.test(o.index);
};
var highest = _.maxBy(jsonDataWithoutValidValues , isValid);
if (isValid(highest)) {
console.log('The max value is: ' + highest.index);
} else {
console.log('No valid value found!');
}
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>
This is a probably a bit weird to use in production, but I thought it was interesting enough to share.
Well in this case you can just use Array prototype sort after you have filtered out the 'abc' to sort them the way you want
var x = _.filter(jsonData, function (o) {
return /abc/i.test(o.index);
}).sort(function (a, b) {
if(a.index > b.index) return -1;
if(a.index < b.index) return 1;
return 0;
});
if you do the sorting correct you can get the highest value like
console.log(x[0].index)
You could use a single loop with Array#reduce and check the number, if exists.
var data = [{ index: "xyz" }, { index: "abc1234" }, { index: "xyz" }, { index: "abc5678" }],
getNumber = function (s) { return s.match(/^abc(\d+)/i)[1]; },
result = data.reduce(function (r, a) {
return a.index.match(/^abc\d/i) && (!r || getNumber(r.index) < getNumber(a.index)) ? a : r;
}, undefined);
console.log(result);
Here's a lodash chaining approach:
_(data)
.map('index')
.filter(_.method('match', /abc/))
.maxBy(_.flow(_.bindKey(/\d+/, 'exec'), _.first, _.toNumber));
The map() and filter() calls get you a list of stings with abc in them. The maxBy() call finds the max, but we have to compose a function to tell it that we want to compare it numerically. The flow() function is really handy for this. Here, we're telling it to execute the regular expression, find the first element of the result, and turn that into a number.
I need to add filter option to my grid.I use Fixed Data Table.Here is simple filtering example with that grid.
https://github.com/facebook/fixed-data-table/blob/master/examples/old/FilterExample.js
This example filter the Json array only by first name.But I need to filter by all of the objects in JSON Array.
For example may JSON array is here:
{"id":7,"first_name":"Sarah","last_name":"Hottie",
"country":"Sweden","salary":12000},
{"id":9,"first_name":"Mary","last_name":"Parah",
"country":"Argentina","salary":10000}
When I write "arah" to the general input filter value.I need to show both of the two elements of array.Because "id:7" first name (Sarah) and "id:9" last name (Parah) include my filter value ("arah").
If the country value of the another element of JSON array include "arah" I need to show that too.
So I need to filter the JSON array by all of the values it include.
What do you suggest?
You can utilize the filter prototype of the array. It will be something like this:
var arr = [ {"id":7,"first_name":"Sarah","last_name":"Hottie",
"country":"Sweden","salary":12000}, {"id":9,"first_name":"Mary","last_name":"Parah","country":"Argentina","salary":10000}]
var runFilter = function(arr,searchKey) {
var filterFn = function(obj) {
// Iterate the obj for each key.
for (var k in obj) {
if (typeof obj[k] == "string" && obj[k].indexOf(searchKey) >= 0) {
return true;
}
}
}
return arr.filter(filterFn);
}
var filteredArr = runFilter(arr,'arah')
I suggest to use Array#filter in combination with Array#some and a check of the type.
var data = [{ "id": 7, "first_name": "Sarah", "last_name": "Hottie", "country": "Sweden", "salary": 12000 }, { "id": 9, "first_name": "Mary", "last_name": "Parah", "country": "Argentina", "salary": 10000 }],
search = 'arah',
result = data.filter(function (a) {
return Object.keys(a).some(function (k) {
if (typeof a[k] === 'string' && ~a[k].indexOf(search)) {
return true;
}
if (typeof a[k] === 'number' && ~a[k] === search) {
return true;
}
});
});
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
You can find the filter function in line 45 of the example code. It is
return row['firstName'].toLowerCase().indexOf(filterBy.toLowerCase()) >= 0
If you want to look into every part of an Object, you can use a for...in loop:
for(var key in row){
if((row[key] + "").indexOf(filterBy) > -1){
return true;
}
}
return false;
Replace line 45 with the code above and you should be fine.
Try This :
<script type="text/javascript">
var arr = [ {"id":7,"first_name":"Sarah","last_name":"Hottie","country":"Sweden","salary":12000},
{"id":8,"first_name":"Mary","last_name":"Parah","country":"Argentina","salary":10000},
{"id":9,"first_name":"Gold","last_name":"sonam","country":"India","salary":15000}];
var filterKey = 'arah';
function findJsonString(arr,filterKey){
var result = [];
for (var i = arr.length - 1; i >= 0; i--) {
var part1 = arr[i].first_name.indexOf(filterKey);
var part2 = arr[i].last_name.indexOf(filterKey);
// console.log(arr[i]);
// console.log(' part 1 : ' + part1 + ' part 2 : ' + part2);
if(part1 != -1 || part2 != -1)
{
result[+i] = arr[i];
// OR result.push(arr[i]);
}
}
return result;
}
console.log(findJsonString(arr,filterKey));
</script>
OUTPUT :
[Object { id=7, first_name="Sarah", last_name="Hottie", more...}, Object { id=8, first_name="Mary", last_name="Parah", more...}]
I have following array from facebook graph API.
I want to sort it by comment_count, like_count, time in javascript.
[
{
"status_id": "1",
"message": "message1",
"comment_info": {
"comment_count": "1"
},
"like_info": {
"like_count": "0"
},
"time": "1380046653"
},
{
"status_id": "2",
"message": "message2",
"comment_info": {
"comment_count": "2"
},
"like_info": {
"like_count": "5"
},
"time": "1368109884"
}
]
I wrote function like below,
function sortResults(prop, asc) {
statusString = statusString.sort(function(a, b) {
if (asc) return (a[prop] > b[prop]) ? 1 : ((a[prop] < b[prop]) ? -1 : 0);
else return (b[prop] > a[prop]) ? 1 : ((b[prop] < a[prop]) ? -1 : 0);
});
console.log(statusString);
}
And on button click
sortResults(['comment_info']['comment_count'], true);
But it sorts weiredly.
Your function didnt' take in consideration multi dimensional sort which needs to access deep property of nested arrays.
Here is a working example JSFIDLE link (click here)
var jSon = [{"status_id":"1","message":"message1","comment_info":{"comment_count":"1"},"like_info":{"like_count":"0"},"time":"1380046653"},{"status_id":"2","message":"message2","comment_info":{"comment_count":"2"},"like_info":{"like_count":"5"},"time":"1368109884"}];
// Function that sorts arr Array
// by prop (handling custom Fb cases)
// in dir direction (asc/desc)
function sortJson(arr, prop, dir) {
return arr.sort(function(a,b) {
var propA,propB;
if (prop == "comment_count") {
propA = a['comment_info']['comment_count'];
propB = b['comment_info']['comment_count'];
} else if (prop == "like_count") {
propA = a['like_info']['like_count'];
propB = b['like_info']['like_count'];
} else {
propA = a[prop];
propB = b[prop];
}
if (dir=='asc') {
return propA - propB;
} else {
return propB - propA;
}
});
}
console.log( sortJson(jSon, 'time', 'asc') );
console.log( sortJson(jSon, 'comment_count', 'asc') );
console.log( sortJson(jSon, 'like_count', 'desc').toString() );
You probably need sort function from native [].
[].sort(compareFunction)
example from here :
function compare(a, b) {
if (a is less than b by some ordering criterion)
return -1;
if (a is greater than b by the ordering criterion)
return 1;
// a must be equal to b
return 0;
}
let's say your order in the sort is as you have mentioned them:
var arr = [{"status_id":"1","message":"message1","comment_info":{"comment_count":"1"},"like_info":{"like_count":"0"},"time":"1380046653"},{"status_id":"2","message":"message2","comment_info":{"comment_count":"2"},"like_info":{"like_count":"5"},"time":"1368109884"}];
arr.sort(function (a, b) {
var countA = parseInt(a["comment_info"]["comment_count"]);
var countB = parseInt(b["comment_info"]["comment_count"]);
var likeCountA = parseInt(a["like_info"]["like_count"]);
var likeCountB = parseInt(b["like_info"]["like_count"]);
var timeA = a["time"];
var timeB = b["time"];
return ((countA - countB) || (likeCountA - likeCountB) || (timeA - timeB));
});
I have an array that contains raw values as well as computed values. I would like to be able to sort the array dynamically based on either a raw value or the result of one of the computed values. The actual sorts that will be required will not be known until runtime.
I've put together the below sample (plunker here) that demonstrates the situation and a working solution*. I would like to know how to improve this... specifically, the use of:
Array.prototype.sortBy = function (property) {
return this.sort(mySort(property));
};
is copied from this stackoverflow response - and Ege Özcan specifically states
//Please don't just copy-paste this code.
//See the explanation at the end. A lot could break.
I would like to understand how to implement this sorting algorithm on my object without violating the 'A lot could break' warning (that I don't understand).
*One of the things I love about stackoverflow is that the process of framing the question well frequently leads you to simplify the problem to the point where a (not necessarily the) solution presents itself. I started this problem not being able to sort based on a property or computed value. Now, I'm looking for validation/improvement on the implementation.
Sample:
var rawData = [
{ "Id": 3, "itemCount": 3531, "val1": 905, "val2": 172 },
{ "Id": 2, "itemCount": 3111, "val1": 799, "val2": 147 },
{ "Id": 4, "itemCount": 3411, "val1": 871, "val2": 199 },
{ "Id": 5, "itemCount": 3414, "val1": 892, "val2": 178 },
{ "Id": 1, "itemCount": 3182, "val1": 845, "val2": 155 }
];
function MyItem(item) {
var self = this;
for (var val in item) {
if (item.hasOwnProperty(val)) {
self[val] = item[val];
}
}
}
function extendMyItems() {
MyItem.prototype.computedOne = function () {
var input = this;
return input.itemCount / input.val1;
};
MyItem.prototype.computedTwo = function () {
var input = this;
return input.val1 * input.val2;
};
}
function getItems(input) {
var ret = [];
for (var i = 0; i < input.length; i++) {
var item = new MyItem(input[i]);
ret.push(item);
}
return ret;
}
function doIt() {
Array.prototype.sortBy = function (property) {
return this.sort(mySort(property));
};
extendMyItems();
var sortList = [{ "sortKey": "Id", "sortOrder": "asc" },
{ "sortKey": "val1", "sortOrder": "asc" },
{ "sortKey": "val2", "sortOrder": "desc" },
{ "sortKey": "computedOne", "sortOrder": "desc", "isComputed": true },
{ "sortKey": "Id", "sortOrder": "desc" },
{ "sortKey": "computedTwo", "sortOrder": "asc", "isComputed": true }];
// get the array of MyItem
var myItems = getItems(rawData);
for (var k = 0; k < sortList.length; k++) {
myItems.sortBy(sortList[k]);
// process the sorted items here (ranking/scoring, etc)
for (var p = 0; p < myItems.length; p++) {
console.log('Id: ' + myItems[p].Id + ' val1: ' + myItems[p].val1 + ' val2: ' + myItems[p].val2 + ' c1: ' + myItems[p].computedOne() + ' c2: ' + myItems[p].computedTwo());
}
}
function mySort(srt) {
var so = srt.sortOrder == 'asc' ? 1 : -1;
var key = srt.sortKey;
var result = 0;
console.log(srt.sortKey + ' ' + srt.sortOrder + ':');
return function (a, b) {
if (srt.isComputed) {
// this seems like a hack - is there a better way to switch between property and function value????
result = (a[key]() < b[key]()) ? -1 : (a[key]() > b[key]()) ? 1 : 0;
} else {
result = (a[key] < b[key]) ? -1 : (a[key] > b[key]) ? 1 : 0;
}
return result * so;
};
}
}
It is considered by most a bad practice to extend native objects like Array. That is what the post you mentioned was getting at. The concern is you do not know how this will effect the behavior of other scripts.
Here is an example of a scenario where poorly written caused issues once the array prototype was manipulated. This is a scenario I ran into once on a large code based which was really hard to track down:
function BadUseOfForLoop(){
//You should NEVER use a for in loop to iterate over an array
//although some people do this and it works until you extend Array
var arr = [1,2,3,4];
for (key in arr){
console.log(arr[key]);
}
}
BadUseOfForLoop();
console.log("Extend Array...");
Array.prototype.sortBy = function(){
return "Doesnt matter...";
};
BadUseOfForLoop();
Output:
1
2
3
4
Extend Array...
1
2
3
4
function (){
return "Doesnt matter...";
}
http://jsfiddle.net/vTwRY/
One thing you could do to avoid the caveat is simply not extend the Array object and create a helper to do this for you.
var ArrayHelper = {
sortBy : function(arr, prop){
return function(){
var so = srt.sortOrder == 'asc' ? 1 : -1;
var key = srt.sortKey;
var result = 0;
console.log(srt.sortKey + ' ' + srt.sortOrder + ':');
return function (a, b) {
if (srt.isComputed) {
result = (a[key]() < b[key]()) ? -1 : (a[key]() > b[key]()) ? 1 : 0;
} else {
result = (a[key] < b[key]) ? -1 : (a[key] > b[key]) ? 1 : 0;
}
return result * so;
};
}
}
};
And then in your code...
ArrayHelper.sortBy(myItems,sortList[k]);
instead of
myItems.sortBy(sortList[k]);
Working demo
For further reading on the subject this Perfection Kills post discusses whether or not extending native objects is a good idea and you will see it is not a cut and dry issue. Above, I layed out an issue that is not discussed in this blog post but really can cause conflicts with other code.
The answer states that you shouldn't extend things like Array.prototype. Personally, I don't consider this a mortal sin; most libraries can safely handle these scenarios.
That said, you could wrap the sorting functionality:
function ArraySorter(arr)
{
this.arr = arr;
}
ArraySorter.prototype.sortBy = function(prop) {
return this.arr.sort(mySort(prop));
}
var sortedItems = new ArraySorter(myItems).sortBy('whatever');