I need to write a function that retrieves the last parameter passed into the function. If it's just arguments, it should return the last element. If it's a string, it should return the last character. Arrays should return the last element of the array. My code works for arrays, strings and arguments as long as they are not all strings.
function last(list){
var arr;
if(list instanceof Array){
return list[list.length-1];
}
if(typeof list === 'string' || list instanceof String){
arr = list.split("");
return arr[arr.length-1];
}
return arguments[arguments.length-1];
}
This works for almost every case, but I have a problem when the input is a bunch of string arguments.
Test.assertEquals(last('a','b','c','z'), 'z');
returns 'a'
Why are disjoint strings evaluating to true when testing if the arguments are arrays or strings and how can I universaly access the last value of arbitrary parameters?
Something like this should do that
function last(){
var a = arguments;
return (a.length > 1 ? [].slice.call(a) :
typeof a[0] === 'string' ? a[0].split('') : a[0]).pop();
}
FIDDLE
Change your function call to Test.assertEquals(last(['a','b','c','z']), 'z');
Whereas before you were passing four different arguments to last(), this passes a single argument, a list containing four characters.
Change your last function like this:
function last(list){
var arr;
if(list instanceof Array){
return list[list.length-1];
}
if((typeof list === 'string' || list instanceof String) && (arguments.length == 1)){
arr = list.split("");
return arr[arr.length-1];
}
return arguments[arguments.length-1];
};
Related
I have a function that iterates through an array of objects and returns a template literal that grabs a property value (name) and also a property value that is a function method (this is .move / how many steps they will take) The .move method uses math.random to pick a random number of steps and return that value. However, in some objects the move property is defined as an integer such as 1 or 2 instead of a random number.
Is there a way to change my fitnessTest function so that it will accept both the .move() and .move?
I tried using an if else statement inside of my while statement saying
while (steps <= 20) {
if (typeof arrayObject == function) {
steps += arrayObject[i].move();
turns++;
} else
steps += arrayObject[i].move;
turns++;
which returns the objects who have .move values defined as integers correctly but then doesnt return random numbers for the objects who have .move().
function fitnessTest(arrayObject){
let turnsArray = [];
for (i = 0; i < arrayObject.length; i++){
let steps = 0;
let turns = 0;
while (steps <= 20){
steps += arrayObject[i].move();
turns++;
} turnsArray.push(`${arrayObject[i].name} took ${turns} turns to take 20 steps.` );
} return turnsArray;
}
Right now, the function will iterate through an array of objects who have .move() as a function that generates a random number and return the proper string, but the objects who have .move set as an integer it will just give me a
type error of arrayObject[i].move is not a function
1.typeof returns a string value, you need to compare against a string of JavaScript types.
2.
You should test if the property move of a single item of arrayObject is a function, not arrayObject itself:
typeof arrayObject[i].move == 'function'
Check typeof array element but not the array variable.
var arr = [ { move: 10}, {move: function () {}} ];
console.log(typeof arr) // object
console.log(typeof arr[0].move) // number
console.log(typeof arr[1].move) // function
Change your code to :
while (steps <= 20) {
if (typeof arrayObject[i].move === "function") {
steps += arrayObject[i].move();
turns++;
} else if (typeof arrayObject[i].move === "number")
steps += arrayObject[i].move;
turns++
typeof gives you a string, so you need to equate it with a string using "". Also compare the move property and not the object itself.
You can use ternary operator for your purpose and can have a more elegant code.
while (steps <= 20) {
steps += typeof arrayObject[i].move === "function" ? arrayObject[i].move() : arrayObject[i].move;
turns++;
}
Write a function called "removeElement".
Given an array of elements, and a "discarder" parameter,
"removeElement" returns an array containing the items in the given
array that do not match the "discarder" parameter.
Notes:
* If all the elements match, it should return an empty array.
* If an empty array is passed in, it should return an empty array.
I thought this would be the solution:
function removeElement(array, discarder) {
var newarr = array.filter(function(cv, i, a){
if (array == []) {
return [];
} else if (cv != discarder ) {
return cv;
}
});
return newarr;
}
But I get this error:
return_an_array_with_all_booleans_not_matching_discarder
return an array with all booleans not matching discarder
I thought this would then work, because initially I didn't add the clause to factor in if all values were equal to discarder.
function removeElement(array, discarder) {
var newarr = array.filter(function(cv, i, a){
if ((array == []) || (cv == discarder)) { // added this expression to evaluate.
return [];
} else if (cv != discarder ) {
return cv;
}
});
return newarr;
}
While Array#filter
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
needs a boolean (or at least a truthy/falsy) value to return, you could just return the check with the given discarder and return the comparison result.
The result is an array without the given value.
function removeElement(array, discarder) {
return array.filter(function(value) {
return value !== discarder;
});
}
So I'm doing some challenges on freecodecamp, I got stuck on one that says:
"Make a function that looks through an array of objects (first argument) and returns an array of all objects that have matching property and value pairs (second argument)." So I looked the for the answer and came accross the next code:
function whatIsInAName(collection, source) {
var arr = [];
var keys = Object.keys(source);
// Filter array and remove the ones that do not have the keys from source.
arr = collection.filter(function(obj) {
return keys.every(function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
});
});
return arr;
}
I understand what it does what I cant seem to get is the returns inside the collection.filter why do we need these two:
return keys.every(function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
Why the code doesn't work with only the second one.
Can someone explain this to me please.
this code:
arr = collection.filter(function(obj) {
return keys.every(function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
});
First, (this is the main script to check wheter Collection's element match or not with the source)
keys.every(function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
It will just return either true/false based on obj.hasOwnProperty(key) && obj[key] === source[key]; condition. it checks every keys from source. If it finds just one key un-matched with the condition it will break the loop and return false otherwise (passed all the test [all keys and values from source the same with collection's element]) return true.
then
arr = collection.filter(function(obj) {
return true // false
if it return true, the element obj from collection will be passed to arr otherwise filtered / skipped
collection.filter is calling Array.prototype.filter. It is used for removing elements from an array that don't meet certain criteria. The function passed as a parameter is used to determine whether an element meets that criteria. A return value of false means that the element should be removed from the array, while true means that the element should stay in the array.
If you don't give the function a return statement, it will return undefined for all of the elements of the array, which is a falsy value.
In ES6, you can use arrow functions which don't require you to write return:
function whatIsInAName(collection, source) {
var keys = Object.keys(source);
return collection.filter(obj =>
keys.every(key =>
obj.hasOwnProperty(key) && obj[key] === source[key];
)
);
}
Is there a way to check if an object is "array-like", like for these types of objects:
Arrays (duh)
Typed Arrays (Uint8Array, etc), these will return false when Array.isArray is used
arguments object
NodeLists*
There are a few other ones that I can't think of off-hand
I suppose you could check for the presence of a .length property, but non-array-like objects can contain the .length property. I guess the thing these all share in common is the array accessor.
As best I've found in my research on this topic, you have only a couple choices:
You can look only at the .length property and accept any object that seems to have an appropriate .length property that isn't any other things you know you should eliminate (like a function).
You can check for specific array-like objects (HTMLCollection, nodeList) and bias in favor of them.
Here are two options for the first method - one that doesn't accept a zero length and one that does (these incorporate suggestions by gilly3 and things we see in jQuery's similar function):
// see if it looks and smells like an iterable object, but don't accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
item.hasOwnProperty("length") &&
typeof item.length === "number" &&
item.length > 0 &&
(item.length - 1) in item
)
);
}
This, of course, reports false for items with .length === 0, If you want to allow .length === 0, then the logic can be made to include that case too.
// see if it looks and smells like an iterable object, and do accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
typeof (item.length) === "number" &&
(item.length === 0 ||
(item.length > 0 &&
(item.length - 1) in item)
)
)
);
}
Some test cases: http://jsfiddle.net/jfriend00/3brjc/
2) After checking to see that it's not an actual array, you can code to check for specific kinds of array-like objects (e.g. nodeList, HTMLCollection).
For example, here's a method I use when I want to make sure I include nodeList and HTMLCollection array-like objects:
// assumes Array.isArray or a polyfill is available
function canAccessAsArray(item) {
if (Array.isArray(item)) {
return true;
}
// modern browser such as IE9 / firefox / chrome etc.
var result = Object.prototype.toString.call(item);
if (result === "[object HTMLCollection]" || result === "[object NodeList]") {
return true;
}
//ie 6/7/8
if (typeof item !== "object" || !item.hasOwnProperty("length") || item.length < 0) {
return false;
}
// a false positive on an empty pseudo-array is OK because there won't be anything
// to iterate so we allow anything with .length === 0 to pass the test
if (item.length === 0) {
return true;
} else if (item[0] && item[0].nodeType) {
return true;
}
return false;
}
You can check if the object is iterable :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function');
}
Careful though, returns true for strings. If that's a problem, exclude them :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function' && typeof(o)!=='string');
}
Then either access the elements using the iterator or if you want to use the regular array[0] way, just add a check for length property.
Complete isArrayLike function :
function isArrayLike(a){
return (
a!=null &&
typeof(a[Symbol.iterator])==='function' &&
typeof(a.length)==='number' &&
typeof(a)!=='string'
);
}
In JavaScript an array-like object is an object containing a length property typically of type number with a non-negative whole value no greater than 2^53-1. Array-like objects with lengths greater than zero also typically contain additional properties beginning at [0] and going up to [n-1] where n = the value of length.
From MDN:
The term array-like object refers to any object that doesn't throw during the length conversion process described [below]. In practice, such object is expected to actually have a length property and to have indexed elements in the range 0 to length - 1. (If it doesn't have all indices, it will be functionally equivalent to a sparse array.)
Normalization of the length property
The length property is converted to an integer and then clamped to the range between 0 and 2^53 - 1.
Array-like objects also differ from other objects in that Array methods such as Array.prototype.push can be called indirectly on them using Function.prototype.call().
Also from MDN:
Array methods cannot be called directly on array-like objects. But you
can call them indirectly using Function.prototype.call().
Calling Array functions on an object without a normalized length property changes the value of length. Therefore I thought it best that my function return false if the length property is not already an integer between 0 and 2^53 - 1. The length property cannot be a BigInt as that would throw a TypeError.
This function also checks for existence of the last element to help rule out objects that have a length property but aren't intended to be array-like.
Tested and works in old browsers (ES3 and up).
function isArrayLike(obj) {
var i;
return /( Arguments|NodeList|Collection|Array)\b/.test(({}).toString.call(obj))// Always array-like
// Confirm obj is an object but not a function or null
|| !!obj && typeof obj=="object"
// Confirm that Array methods can be called indirectly on obj
// without throwing or changing the value of obj.length
// by confirming obj.length (i) is a normalized integer
// Usually this means i is in the range 0 - 9007199254740991
&& [].push.call({length:Number(i=obj.length)})===i
// And when non-zero confirm i>0 and i-1 is a property in obj
&& (!i || i>0 && i-1 in obj);
}
Well, it depends what you mean by array-like. Probably something you could iterate with a for loop like this:
for (var i=0, l=obj.length; i<l; ++i) {
var item = obj[i];
}
So then the test is simple:
function isArrayLike(obj) {
if (!obj) return false;
var l = obj.length;
if (typeof l != 'number' || l < 0) return false;
if (Math.floor(l) != l) return false;
// fast check
if (l>0 && !((l-1) in obj)) return false;
// more complete check (optional)
for (var i=0; i<l; ++i) {
if (!(i in obj)) return false;
}
return true;
}
Of course, this won't catch arrays which are sparsely populated, but then again, are they really being used as arrays? NodeLists and the like won't be sparsely populated.
Enjoy!
There is a way to check if an object is array-like or not, even if there are no elements in it, using this function here:
isArrayLike = function (_) {
_[0] = 0; return [].slice.call(_).length >= Object.values(_).length;
}
This uses a little hack I accidentally discovered that allows you to determine if an object is (1) an array, (2) array-like, or (3) object / object-like.
The only downside is that it does not work correctly for array-likes that have object-like properties added, such as arguments
Technically, (pretty much) every object is "array-like" (because of type coercion of undefined) according to the standard (ECMAScript 2015 Language Specification §7.3.17, CreateListFromArrayLike (obj [, elementTypes] )):
7.3.17 CreateListFromArrayLike (obj [, elementTypes] )
The abstract operation CreateListFromArrayLike is used to create a List value whose elements are provided by the indexed properties of an array-like object, obj. The optional argument elementTypes is a List containing the names of ECMAScript Language Types that are allowed for element values of the List that is created. This abstract operation performs the following steps:
ReturnIfAbrupt(obj).
If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
If Type(obj)
is not Object, throw a TypeError exception.
Let len be ToLength(Get(obj, "length")).
ReturnIfAbrupt(len).
Let list be an empty List.
Let index be 0.
Repeat while index < len
Let indexName be ToString(index).
Let next be Get(obj, indexName).
ReturnIfAbrupt(next).
If Type(next)
is not an element of elementTypes, throw a TypeError exception.
Append next as the last element of list.
Set index to index + 1.
Return list.
Generated via https://www.browserling.com/tools/html-to-markdown
An Array is a value that has following properties:
Its of type object
It has length property which is equal or greater than 0.
All the value based keys are numeric and is greater than or equal to 0.
Exceptions:
length
Value is a function.
function isArrayLike(value) {
if (typeof value === "object" && !!value) {
const isArray = Array.isArray(value);
const allNumericKeys = Object.keys(value).every((k) =>
(!isNaN(k) && +k >= 0) ||
k === "length" ||
typeof value[k] === "function"
)
const hasLength = value.length > 0
return isArray || (allNumericKeys && hasLength)
}
return false
}
console.log('Array: ', isArrayLike([]))
console.log('Array Like: ', isArrayLike({1: 'a', 2: 'b', length: 3}))
console.log('Array Like with function: ', isArrayLike({1: 'a', 2: 'b', length: 3, foo: () => {} }))
console.log('Array Like with negative keys: ', isArrayLike({ "-1": 'a', "-2": 'b', length: 1}))
console.log('Array Like without length:', isArrayLike({1: 'a', 2: 'b' }))
console.log('Node List: ', isArrayLike(document.querySelectorAll('p')))
console.log('null: ', isArrayLike(null))
console.log('String: ', isArrayLike('Test'))
console.log('Number: ', isArrayLike(123))
<div>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
I say nothing beats the simplicity and expressiveness of extending native objects:
Object.prototype.isArrayLike = function(){ return false; };
Array.prototype.isArrayLike = function(){ return true; };
NodeList.prototype.isArrayLike = function(){ return true; };
HTMLCollection.prototype.isArrayLike = function(){ return true; };
This approach could cause conflicts between frameworks, however, I recommend keeping your distance from a framework whose isArrayLike function does not what the name suggests.
I need a function:
function isSame(a, b){
}
In which, if a and b are the same, it returns true.
, I tried return a === b, but I found that [] === [] will return false.
Some results that I expect this function can gave:
isSame(3.14, 3.14); // true
isSame("hello", "hello"); // true
isSame([], []); // true
isSame([1, 2], [1, 2]); // true
isSame({ a : 1, b : 2}, {a : 1, b : 2}); //true
isSame([1, {a:1}], [1, {a:1}]); //true
You could embed Underscore.js and use _.isEqual(obj1, obj2).
The function works for arbitrary objects and uses whatever is the most efficient way to test the given objects for equality.
the best way to do that is to use a JSON serializer. serialize both to string and compare the string.
There are some examples, adapted from scheme, on Crockford's site. Specifically, check out:
function isEqual(s1, s2) {
return isAtom(s1) && isAtom(s2) ? isEqan(s1, s2) :
isAtom(s1) || isAtom(s2) ? false :
isEqlist(s1, s2);
}
It can all be found here:
http://javascript.crockford.com/little.js
Here is a working example:
http://jsfiddle.net/FhGpd/
Update:
Just wrote some test cases based on the OP. Turns out I needed to modify the sub1 function to check <= 0 not === 0 otherwise isEqual(3.14, 3.14) blew the stack. Also, isEqual does not work for object comparison, so you are on your own there. However, if you follow the examples on Crockford's site you will see how easy and fun it is to write recursive methods that could be used to check for object equality.
Here is something that can work:
function isSame(obj1, obj2, prefer){
// Optional parameter prefer allows to only check for object keys and not both keys and values of an object
var obj_prefer = prefer || "both";
function checkArray(arr1, arr2){
for(var i = 0, j = obj1.length; i<j; i++){
if(obj1[i] !== obj2[i]){return false;}
}
return true;
}
function checkValues(obj_1, obj_2){
for(var prop in obj_1){
if(typeof obj_1[prop] === "function"){ // converting functions to string so that they can be matched
obj_1[prop] = String(obj_1[prop]);
obj_2[prop] = String(obj_2[prop]);
}
if(obj_1[prop] !== obj_2[prop]){ return false;}
}
return true;
}
// The built in === will check everything except when typeof object is "object"
if ( typeof obj1 === "object"){
// typeof Array is object so this is an alternative
if((typeof obj1.push === "function") && (!obj1.hasOwnProperty('push'))){
return checkArray(obj1, obj2);
}
else{
if( obj_prefer !== "keys"){ // do not check for values if obj_prefer is "keys"
return checkValues(obj1, obj2);
}
var keys_1 = Object.keys(obj1);
var keys_2 = Object.keys(obj2);
if(!checkArray(keys_1, keys_2)){return false;}
return true;
}
}
// I thought undefined === undefined will give false but it isn't so you can remove it
if( typeof obj1 === "undefined" && typeof obj2 === "undefined" ){return true}
if(typeof obj1 === "function"){
return String(obj1) === String(obj2);
}
return obj1 === obj2;
}
console.log(isSame(2, 2)); //true
console.log(isSame([1], [1])); // true
Since it converts Functions into Strings to compare them, check out for spaces as that can break things:
var func1 = function(){},
func2 = function(){ }; // function with extra space
isSame(func1, func2); // false
You can check out http://jsfiddle.net/webholik/dwaLN/4/ to try it yourself.
If anyone reading this answer is using Angular.js, you can use angular.equals(obj1,obj2);
According to the docs:
Determines if two objects or two values are equivalent. Supports value
types, regular expressions, arrays and objects.
Two objects or values are considered equivalent if at least one of the
following is true:
Both objects or values pass === comparison.
Both objects or values are of the same type and all of their properties are equal by comparing them with angular.equals.
Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal).
Both values represent the same regular expression (In JavaScript, /abc/ == /abc/ => false. But we consider two regular expressions as
equal when their textual representation matches).
During a property comparison, properties of function type and properties with names that begin with $ are ignored.
Scope and DOMWindow objects are being compared only by identify (===).