Unordered function arguments in Javascript - javascript

Below is a regular function with named parameters:
function who(name, age, isMale, weight)
{
alert(name + ' (' + (isMale ? 'male' : 'female') + '), ' + age + ' years old, ' + weight + ' kg.');
}
who('Jack', 30, true, 90); //this is OK.
What I want to achive is; whether you pass the arguments in order or not; the function should produce a similar result (if not the same):
who('Jack', 30, true, 90); //should produce the same result with the regular function
who(30, 90, true, 'Jack'); //should produce the same result
who(true, 30, 'Jack', 90); //should produce the same result
This enables you to pass a list of arguments in any order but still will be mapped to a logical order. My approach up to now is something like this:
function who()
{
var name = getStringInArgs(arguments, 0); //gets first string in arguments
var isMale = getBooleanInArgs(arguments, 0); //gets first boolean in arguments
var age = getNumberInArgs(arguments, 0); //gets first number in arguments
var weight = getNumberInArgs(arguments, 1); //gets second number in arguments
alert(name + ' (' + (isMale ? 'male' : 'female') + '), ' + age + ' years old, ' + weight + ' kg.');
}
There is a little problem here; functions such as getStringInArgs() and getNumberInArgs() go through all the arguments each time to find the arg by type at the specified position. I could iterate through args only once and keep flags for the positions but then I would have to do it inside the who() function.
Do you think this approach is logical and the only way? Is there a better way to do it?
EDIT 1: Code above actually works. I just want to know if there is a better way.
EDIT 2: You may wonder if this is necessary or whether it makes sense. The main reason is: I'm writing a jQuery function which adds a specific style to a DOM element. I want this function to treat its arguments like shorthand CSS values.
Example:
border: 1px solid red;
border: solid 1px red; /*will produce the same*/
So; here is the real and final code upto now:
(function($){
function getArgument(args, type, occurrence, defaultValue)
{
if (args.length == 0) return defaultValue;
var count = 0;
for(var i = 0; i < args.length; i++)
{
if (typeof args[i] === type)
{
if (count == occurrence) { return args[i]; }
else { count++; }
}
}
return defaultValue;
}
$.fn.shadow = function()
{
var blur = getArgument(arguments, 'number', 0, 3);
var hLength = getArgument(arguments, 'number', 1, 0);
var vLength = getArgument(arguments, 'number', 2, 0);
var color = getArgument(arguments, 'string', 0, '#000');
var inset = getArgument(arguments, 'boolean', 0, false);
var strInset = inset ? 'inset ' : '';
var sValue = strInset + hLength + 'px ' + vLength + 'px ' + blur + 'px ' + color;
var style = {
'-moz-box-shadow': sValue,
'-webkit-box-shadow': sValue,
'box-shadow': sValue
};
return this.each(function()
{
$(this).css(style);
});
}
})(jQuery);
Usage:
$('.dropShadow').shadow(true, 3, 3, 5, '#FF0000');
$('.dropShadow').shadow(3, 3, 5, '#FF0000', true);
$('.dropShadow').shadow();

I find using objects to be more straight-forward and less error prone in the future:
var person = {
name: 'Jack',
age: 30,
isMale: true,
weight: 90
};
who(person);
function who(person){
alert(person.name +
' (' + (person.isMale ? 'male' : 'female') + '), ' +
person.age + ' years old, ' +
person.weight + ' kg.');
}
That way when you come back years later you don't have to lookup to see if age was the first, second, or fifth number and is more descriptive of what you are trying to accomplish.

This seems unnecessarily complex, not just from the perspective of the function, which needs to reorder its arguments, but also from the perspective of whoever is calling. You say that the function can accept its paramters in any order, but that's not entirely true. Since your determination of which variable is which is based on type, it relies on each variable being a different type. The name and gender can be anywhere, but the numeric arguments have to be in a specific order. It also prevents someone from passing in "30" or "90", which are numbers but will be regarded as strings - confusing it with the name and not finding an age or weight.

You can cache the arguments of a specific type in the arguments array. This is a big hack, you could follow the same pattern with the other getTypeInArgs
function getNumberInArgs(args, index) {
if (!args.numbers) {
args.numbers = [];
for (var i=0; i < args.length; i++) {
// You have to implement isNumber
if ( isNumber (args[i]) ) {
args.numbers.push(args[i];
}
}
}
return args.numbers[index];
}
I've never heard of accepting arguments in any order, except for the implode function in PHP, and it's marked on its documentation page as a big hack for historical reasons. So I wouldn't do this. If the order is too confusing, I would use the approach of taking a literal object, as suggested by WSkid.

You could try copying the arguments array into something you can destructively update:
untested code: Edit: I think it works now.
function args_getter(their_arguments){
//copy arguments object into an actual array
//so we can use array methods on it:
var arr = Array.prototype.slice.call(their_arguments);
return function(type){
var arg;
for(var i=0; i<arr.length; i++){
arg = arr[i];
if(type == typeof arg){
arr.slice(i, 1);
return arg;
}
}
return "do some error handling here"
}
}
function foo(){
var args = args_getter(arguments);
var b1 = args('boolean');
var b2 = args('boolean');
var n1 = args('number');
console.log(n1, b1, b2);
}
//all of
// foo(1, true, false),
// foo(true, 1, false), and
// foo(true, false, 1)
// should print (1, true, false)
This is still O(N^2) since you go through the array every time. However this shouldn't be an issue unless your functions can receive hundreds of arguments.

I agree with Griffin. This cannot be done unless you limit the choices more than you have. As it is, you have a string, a boolean and two numbers. Without some more rules on what can be in what position, you cannot tell which number is which. If you're willing to make some rule about which number comes first or which number comes after some other argument, then you can sort it out. In general, I think this is a bad idea. It's much better (from the standpoint of good programming) to use an object like WSkid suggested.
Anyway, if you wanted to make a rule like the weight has to come after the age, then it could be done like this:
function findParm(args, type) {
for (var i = 0; i < args.length; i++) {
if (typeof args[i] == type) {
return(i);
}
}
return(-1);
}
function who(name, age, isMale, weight) {
// assumes all variables have been passed with the right type
// age is before weight, but others can be in any order
var _name, _age, _isMale, _weight, i;
var args = Array.prototype.slice.call(arguments);
_name = args[findParm(args, "string")]; // only string parameter
_isMale = args[findParm(args, "boolean")]; // only boolean parameter
i = findParm(args, "number"); // first number parameter
_age = args[i];
args.splice(i, 1); // get rid of first number
_weight = args[findParm(args, "number")]; // second number parameter
// you now have the properly ordered parameters in the four local variables
// _name, _age, _isMale, _weight
}
who("fred", 50, false, 100);
Working here in this fiddle: http://jsfiddle.net/jfriend00/GP9cW/.
What I would suggest is better programming is something like this:
function who(items) {
console.log(items.name);
console.log(items.age);
console.log(items.weight);
console.log(items.isMale);
}
who({name: "Ted", age: 52, weight: 100, isMale: true});

function who({name, age, isMale, weight})
{
alert(name + ' (' + (isMale ? 'male' : 'female') + '), ' + age + ' years old, ' + weight + ' kg.');
}
who({name:'Jack', age:30, isMale:true, weight90});
Taking parameters as an object, allows you to pass arguments in any order.

There is no way you can do this since you have arguments of the same type.
Unless age and weight have non-overlapping ranges, you can't do this. How are you supposed to distinguish between 30 and 60 for weight or age??

This code:
function who(items) { console.log(items.name); console.log(items.age); console.log(items.weight); console.log(items.isMale);}who({name: "Ted", age: 52, weight: 100, isMale: true});
That a previous posted sent seems sensible. But why make things complicated. My experience when people make things complicated things go wrong.
BTW - The solution above (as the previous posted gave) is similar to the Perl solution.

Related

Is it possible to pass object as parameter?

var obj = {
name1: 1,
name2: 2
}
function myF(obj) {
console.log(obj.name1) // by idea it must return 1
};
myF(obj)
Does anybody know how to pass object in function?
Yes objects make great parameters.
var p1 = {
name: "Tom",
age: 23,
isMale: true
};
var p2 = {
name: "Alicia",
age: 21,
isMale: false
};
var p3 = {
name: "Landon",
age: 1,
isMale: true
};
function greeting(person) {
var str = '';
str += 'Hello my name is ';
str += person.name + ' ';
str += 'I'm a ' + person.age + ' year old ';
if (person.isMale) {
str += age > 18 ? 'man' : 'boy';
} else {
str += age > 18 ? 'woman' : 'girl';
}
if (person.age < 3) {
str = 'Bah'
}
console.log(str);
};
greeting(p1); // 'Hello my name is Tom I'm a 23 year old man';
greeting(p2); // 'Hello my name is Alicia I'm a 21 year old woman;
greeting(p3); // 'Bah';
Objects are good for when you have a grouping of values that belong together and you don't want to pass them in individually (If they belong together they rarely should be passed on their own.)
This is very common practice. Many libraries will utilize a config object so one does not have to specify multiple params
Example:
function makeSquare(height, width, color, border)
Could be easier represented with
function makeSquare(config)
This would make it easier for users to leave out some parameters, say you wanted to makeSquare without a border you would not need to include the border param if you are passing and object.
With parameters
makeSquare(10, 20, red, null)
with Obj
config = {
height: 10,
width: 10,
color: 'red'
};
makeSquare(config);
If you had an extensive amount of configuration options you could see where this may save quite a bit of development time and space

JS concatenating object property values (numeric) instead of adding

In an intersect function, that checks if two objects intersect on the canvas, I need to add the obj.x and obj.width property to get the obj.right(side). Somehow the properties are concatenated instead of added. It probably has something to do with reference-type but I don't see how I can capture the values in primitive types.
function intersects(obj1, obj2) { // checks if 2 shapes intersect
var ob2x = obj2.x;
var ob2width = obj2.width;
if (obj1.x > +obj2.x + 70 || obj2.x > +obj1.x + 70) {
console.log('false : obj1.x=' + obj1.x + ' obj2.right=' + parseInt(ob2x) + parseInt(ob2width));
return false;
}
if (obj1.y > +obj2.y + +obj2.height || obj2.y > +obj1.y + +obj1.height) {
console.log('false');
return false;
}
console.log('false');
return true;
}
I have already tried to get the number value of the object property, as you can see. Didn't work
Also tried parseInt(), which didn't work.
I suppose I can put the values seperately as parameters in the fuctions but I was hoping to keep it as short as possible, because kids need to use it.
You need to add a grouping operator:
... + (parseInt(ob2x) + parseInt(ob2width)) + ...
to isolate that part of the expression so that + is seen as addition. Otherwise, the full expression keeps it as concatenation, even though you convert those to values to numbers (because if a string is anywhere in the expression being evaluated, + means concat).
E.g.
var x = 5;
var y = 6;
console.log('Sum: ' + x + y); // 56
console.log('Sum: ' + (x + y)); // 11

What is the faster way to convert an object with property value: "HH:MM:SS" to string "N Minutes"?

I have hundred of objects with structure like
{
movieName: 'xyz',
time: '02:15:50'
timeAsText: null
}
I need to set timeAsText with a text as "136 minutes" based on property 'time'.
Seconds should be rounded up.
Could you point me out what could be the faster approach?
I tried this with two methods (DEMO); the first using map, and the second using a plain for...loop. As you can see from the demo the plain loop is considerably faster:
var out = [];
for (var i = 0, l = arr.length; i < l; i++) {
var obj = arr[i];
var time = obj.time.split(':').map(Number);
if (time[2] > 0) { time[1]++; }
obj.timeAsText = (time[0] * 60) + time[1] + ' minutes';
out.push(obj);
}
the best to do is probably that, and, seeing the numbers of similar answers, probably the only one.
-first, you use split(':') to make your string become a array of parseable string;
-then, parse the value to int. Use parseInt
at this time, you should have a array like that
[number_of_hours, number_of_minutes,number_of_second]
-then you just have to add the different values like
obj.timeAsText = array[0]*60+array[1]+Math.round(array[2]/60)+' minutes';
The full answer :
var arr=obj.time.split(':').forEach(function(entry){
entry=parseInt(entry);
});
obj.timeAsText= arr[0]*60+arr[1]+Math.round(array[2]/60)+" minutes";
Try this
var obj = {
movieName: 'xyz',
time: '02:15:50'
timeAsText: null
}
var a = obj.time.split(':'); // split it at the colons
var minutes = parseInt(+a[0]) * 60 + parseInt(+a[1]) + Math.round(parseInt(+a[2])/60);
obj.timeAsText = minutes + " minutes";
I don't know if it's the fastest, but it's the most easily readable one:
var test = {
movieName: 'xyz',
time: '02:15:50',
timeAsText: null
};
test.time.replace(/^(\d{2}):(\d{2}):(\d{2})$/, function(m, p1, p2, p3) {
// Multiplication and division implicitly converts p1 and p3 to numbers
return p1*60 + parseInt(p2) + Math.ceil(p3/60);
});

StakeOut: Find Optimal Expectation Using Recursion

I was recently presented with a coding assignment by a startup I was interviewing with. They gave me the following problem and asked for a solution:
Congratulations! You are the new elite hacker in a group of villainous
ne'er-do-wells.
Luckily this group is more saavy than your last band of ruffians, and
they are looking to software (and you) to improve their take. The con
man for the team, has gone door-to-door down each street posing as a
termite inspector so he could covertly total the valuable goods in
each house. Normally the gang would just rob all the valuable homes,
but there's a catch! Whenever a house is robbed in this wealthy
neighborhood, the police watch it and the neighboring houses for
months.
So the gang can't simply rob all the homes, and if they choose to rob
one, they can no longer rob the house on either side of it.
The ringleader wants to know what houses he should rob to maximize the
team's profit, and he wants to know now. Write a function that takes
in an array of positive integers (home values) and returns the maximum
expected value of robbing that street.
For example:
[ 20, 10, 50, 5, 1 ] should return $71, as robbing the first, third,
and fifth houses is optimal [ 20, x, 50, x, 1 ]
[ 20, 50, 10, 1, 5 ] should return $55, as robbing the second and
fifth houses is optimal [ x, 50, x, x, 5 ]
Basically, you can only sum the values of alternative houses because if a house is robbed, the houses immediately before and after it cannot be robbed, due to heavy security in the street.
I wrote a solution using recursion and presented it to them.
I presented a solution which accounted for all cases except for the following:
[2, 3, 2], for which the returned answer was 3, instead of 4.
They told me to fix the bug. Since, it was the only case for which the algorithm didn't work, I used the following code to fix the problem (written in JavaScript):
// The recursive alrogithm doesn't account for arrays of length 3.
if(array.length === 3) {
if(array[0] + array[2] > array[1]) {
return 'Optimal expectation of robbing street: ' + String(array[0] + array[2]);
} else{
return 'Optimal expectation of robbing street: ' + String(array[1]);
}
}
So the complete final solution is as follows (with the above snippet of code included):
// Global variable for expectation:
var expectation = 0;
function optimalExpectation(array) {
// 'array' contains the home values
// The recursive alrogithm doesn't account for arrays of length 3.
if(array.length === 3) {
if(array[0] + array[2] > array[1]) {
return 'Optimal expectation of robbing street: ' + String(array[0] + array[2]);
} else{
return 'Optimal expectation of robbing street: ' + String(array[1]);
}
}
// Base case for recursion:
if(array.sum() === 0) {
return 'Optimal expectation of robbing street: ' + String(expectation);
} else{
expectation += array.max();
var maxIndex = array.indexOf(array.max());
// Recursive call:
return optimalExpectation(injectZeros(array, maxIndex));
}
}
//===============================================================================
// Protypal methods for maximum & sum:
// All array objects inherit these methods from the Array prototype:
Array.prototype.max = function(){
if(this.length === 1){
return this[0];
} else if(this.length === 0){
return null;
}
var maximum = 0;
for(var i = 0; i < this.length; i++){
if(maximum < Math.max(this[i], this[i + 1])){
maximum = Math.max(this[i], this[i + 1]);
}
}
return maximum;
};
Array.prototype.sum = function(){
var sum = 0;
this.forEach(function(el){
sum += el;
});
return sum;
};
// Function to replace maximum values, already accounted for, with zeroes:
function injectZeros(array, index){
if(array.length > 0){
if(index < array.length - 1)
array[index + 1] = 0;
if(index > 0)
array[index - 1] = 0;
}
array[index] = 0;
return array;
}
//==================================================================================
console.log(optimalExpectation([2, 3, 2])); // correctly returns 4, instead of 3
I was rejected after I submitted the above solution. They didn't reject me after my initial solution (they definitely could have). They wanted me to explain my thought process and how I fixed the bug.
I would appreciate any input on where I might have gone wrong. Is there a way to improve my algorithm? Is my code well organized? Is this the right approach? If you are a professional developer who has knowledge of how hiring decisions are made in startups, could you elaborate on what might have lead the team to reject me?
You should go a watch Professor Roughgarden's lecture about Weighted Independent Set in Path Graphs, https://class.coursera.org/algo2-2012-001/lecture/83. It uses Dynamic Programming to solve the interview question. The solution is fairly trivial. You can do it using either recursion or iterations.
I am not a hiring manager. But a quick inspection says that you only picked biggest houses as much as you could. The problem is conceptual, and you swept it under the rug instead of fixing your algorithm (the issue would also present itself with [2, 3, 2, 0.5], as you pick 3 then 0.5, instead of the optimal 2 and 2 - there is nothing special about length 3). The correct solution would at least use backtracking to explore if picking different elements would have made more profit, rather than greedily picking the local maximum without regard to context. It is sufficient to inspect the maximum and the neighbours at each step.
var values = [2, 3, 2, 0.5];
function rob(values) {
if (values.every(function(e) { return e === null })) return [];
var len = values.length;
var max = Math.max.apply(null, values);
var subMax = -Infinity;
var subArray = [];
for (var i = 0; i < len; i++) {
if (values[i] !== null && (values[i - 1] === max || values[i] === max || values[i + 1] === max)) {
nextValues = values.slice();
nextValues[i] = null;
if (i > 0) nextValues[i - 1] = null;
if (i < len - 1) nextValues[i + 1] = null;
var subResult = rob(nextValues);
var subSum = subResult.reduce(function(a, b) { return a + b; }, 0) + values[i];
if (subSum > subMax) {
subArray = subResult;
subArray.push(values[i]);
subMax = subSum;
}
}
}
return subArray;
};
alert(rob(values));
function computeRob (arr, index, canRob) {
if(arr.length == index){
return 0;
}
if (canRob == true) {
var withRob = arr[index] + computeRob(arr, index + 1, false);
var withoutRob = computeRob(arr, index + 1, true);
return Math.max(withRob, withoutRob)
} else {
return withoutRob = computeRob(arr, index + 1, true);
}
}
console.log(computeRob([4,3,8,9,2],0,true));

Can't figure out parseInt, even after research in JS and JQ [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have an object with a property containing a large string. This property has a value with random numbers generated earlier in the script in the format x , x , x , x ... (isn't and can't be an array because of other needs for the variable within the program) and so on. I am trying to get the sum of these numbers and my first thought was to use parseInt() to do this by splitting them all up then adding them together, but when I do this it only returns the first number. Is this what I should do but I'm just doing it wrong? Or is there another function that would make this easier?
The program is a blackjack game I'm making to see how well i understand everything I am learning.
Here is the function i am trying to make to see if the user busts when taking a hit (not much so far because i can't figure out the parseInt thing)
'
function checkBust() {
var total = parseInt(user.hand, 10);
}
'
the user object
'
var user = {
hand: dealUser()
};
'
and the functions to set the object property
function randomCard() {
// random number between 0 and 10
var j = Math.random() * 10;
// round that number into a var called card
var card = Math.round(j);
// if card is 0, assign a J Q or K by making a random number again
if (card === 0) {
//another random number
var k = Math.random() * 10;
// checks random number and assign J Q or K
if (k <= 4) {
card = 'J';
} else if (k <= 7) {
card = 'Q';
}
else {
card = 'K';
}
}
// value of the function is a single card
return card;
}
function dealUser() {
// empty array to store cards
var x = [];
// var to start for loop
var i = 0;
// start for loop
for (i; i < 2; i++) {
// add a random card to the i^th index of x
x[i] = randomCard();
}
// value for function is array of two cards x[0] , x[1]
var cards = x[0] + " , " + x[1];
return cards;
}
parseInt will stop parsing when it reaches a non numeric character.
parseInt('1234,5678', 10); // => 1234
// since a comma (,) is not a numeric character, everything after is ignored.
You have to split the string into an array of strings using the comma as the delimiter:
'1234,5678'.split(','); // => ['1234', '5678'];
Then parse each element of the array to convert them to numbers and then you can sum them.
Here's how I'd do it:
var nums = "1,2,3,4,5";
var sum = nums.split(',').reduce(function(memo, num) {
return memo + parseInt(num, 10);
}, 0);
console.log(sum); // => 15
That should work. See jsbin example.
Note the split parameter needs to match the delimiters you use in your string. for this example ',' is appropriate. For your example you might need /\s*,\s*/.
Unrelated
Since you provided an example of code I can see that you're spending a lot of effort attempting to duck punch and transform the values to the types you need instead of exposing the types in an object. Might I suggest:
function Stack(cards) {
this.cards = cards || [];
}
Stack.prototype.toString = function() {
return this.cards.join(' , ');
};
Stack.prototype.sum = function() {
return this.cards.reduce(function(memo, card) {
return memo + parseInt(card, 10);
}, 0);
};
function randomCard() {
return Math.floor(Math.random() * 13) + 1;
}
Stack.dealHand = function() {
var card1 = randomCard(), card2;
do { card2 = randomCard(); } while (card1 === card2);
return new Stack([card1, card2]);
};
// Example
var hand = Stack.dealHand();
console.log(hand + ' = ' + hand.sum()); // => '3 , 11 = 14'

Categories

Resources