1

I have a function that is quite long and at the moment I need a duplicate of the function where the only difference is it asks greater than rather than less than. So the only difference is > or <.

Is there any (non messy) way to make this kind of function just one function instead of the two here?

function(value, modifier) {
 let result;
 if (value > modifier) result = 10;
 return result;
}

function(value, modifier) {
 let result;
 if (value < modifier) result = 10;
 return result;
}

So basically I need a conditional greater than/less than sign.

EDIT: To be clear, in an ideal world I'd like to do this:

myFunction(10, 5, >)

EDIT: Adding one part of my actual function to make things clearer. I was hoping there might be real simple way to do it but it seems like perhaps not so maybe part of the actual function might help things:

function getEdges(colour) {
    for (let x = 0; x < width; x++) {
        for (let y = height - 1; y >= 0; y--) {
            const data = getData(x,y);
            if (data[0] < colour) {
                edge.push(y);
                break;
            }
        }
    }
    return edge;
    }

And there is another almost identical function like this where the only difference is the line if (data[0] > colour) { is greater than rather than less than.

Hasen
  • 11,710
  • 23
  • 77
  • 135
  • 2
    What about adding a third parameter determining what is the desired operation? – briosheje Jun 18 '19 at 12:40
  • um, pass in another argument?? – epascarello Jun 18 '19 at 12:41
  • Your result is undefined if the condition is not met? – Avin Kavish Jun 18 '19 at 12:46
  • What argument? you mean `myFunction(10, 5, >)` ?? I Didn't realise it was possible to pass a greater than or less than sign as an argument. – Hasen Jun 18 '19 at 12:46
  • as string form, so myFunction(10,5, "<") – Wimanicesir Jun 18 '19 at 12:47
  • @Wimanicesir Really? Can you do that? – Hasen Jun 18 '19 at 12:48
  • @hasen you can pass that as a string (or numeric) and interprete it in the callback. – briosheje Jun 18 '19 at 12:48
  • So I can do either `myFunction(10, 5, ">")` or `myFunction(10, 5, "<")` ? But how is it interpreted? – Hasen Jun 18 '19 at 12:49
  • You can pass any string as argument, so "<" or ">" as well. I also posted an answer, could you look at that? – Wimanicesir Jun 18 '19 at 12:49
  • @Wimanicesir It's looking like there's no non messy way to do it unfortunately. Maybe I'll just have to keep to two separate functions. But I'll see if any other good answers come up. – Hasen Jun 18 '19 at 12:53
  • Can't you write `value > modifier ? 10 : undefined` directly in the code, this doesn't look significant enough to wrap in a function? – Avin Kavish Jun 18 '19 at 12:55
  • I added part of my actual function to make things a bit clearer into my original question at the end. – Hasen Jun 18 '19 at 12:58
  • Addressing your edit, there is nothing there that warrants wrapping in a function, use the correct operator in the correct place. Those are what if statements are for. If you post the entire snippet, I can tell you the shortest way to express it. – Avin Kavish Jun 18 '19 at 12:58
  • @Avin Kavish You've lost me.. – Hasen Jun 18 '19 at 13:00
  • If you need to write two if statements, write two if statements, there is no point in putting one if statement inside a function and calling it twice. Functions are to wrap shared logic and re-use them, not to share one if statement between two operators. – Avin Kavish Jun 18 '19 at 13:03
  • @Avin Kavish I only have one if statement. What exactly do you want to say? Please be clear, you've been very vague so far. – Hasen Jun 18 '19 at 13:05
  • Are you trying to re-use the entire `getEdges` method with both less than and greater than signs? – Avin Kavish Jun 18 '19 at 13:08
  • @Avin Kavish I get the data at the x,y coordinate and then see if it is greater than...or in the duplicate function... _less than_ the colour passed to the function. – Hasen Jun 18 '19 at 13:09
  • Check my updated answer @Hasen – DedaDev Jun 18 '19 at 13:15

10 Answers10

2

How about passing the condition function as a third parameter?

function getEdges(colour, condition) {
    for (let x = 0; x < width; x++) {
        for (let y = height - 1; y >= 0; y--) {
            const data = getData(x,y);
            if (condition(data[0], colour)) {
                edge.push(y);
                break;
            }
        }
    }
    return edge;
}

Call the function and pass required condition.

  • for lesser than: getEdges(color, (data, color) => color < data);

  • for greater than: getEdges(color, (data, color) => color > data);

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
Aditya Bhave
  • 998
  • 1
  • 5
  • 10
  • Interesting idea. Can you check my actual function that I added in to my question at the end? That's what I'm actually trying to do. – Hasen Jun 18 '19 at 12:57
  • @Hasen Look at the updated answer. I think using function is cleaner way of doing things. – Aditya Bhave Jun 18 '19 at 13:23
  • 2
    This answer shouldn't have been downvoted. It's the cleanest possible solution. – Aadit M Shah Jun 18 '19 at 13:23
  • Strictly speaking it doesn't match the OP's 'ideal world' syntax. But I prefer this one as well! – springbo Jun 18 '19 at 13:28
  • I get the error 'condition is not a function' with this. – Hasen Jun 18 '19 at 13:44
  • This function places the onus on the method caller to define the condition every time the method needs to be called, which is an unnecessary delegation of responsibility. – Avin Kavish Jun 18 '19 at 13:50
  • @springbo well, nothing in our real world would match it, really. The closest you can get is passing a string, e.g., `"<"` but then you have to check the exact string and make your function less ideal. With this way you can extend it without needing to touch the code, e.g., you can call it with `(a, b) => a === b` condition or even `(a, b) => a * 2 > (b - 1) * 3` if you want, without touching the code of `getEdges` itself. – VLAZ Jun 18 '19 at 13:52
  • @Hasen I created a fiddle for you, check if there is anything wrong in your syntax. https://jsfiddle.net/wu48zvny/1/ – Aditya Bhave Jun 18 '19 at 14:00
2

If the only difference between the two functions is the comparison, then you can just extract that and make it a parameter

function getEdges(colour, comparator) {
  for (let x = 0; x < width; x++) {
    for (let y = height - 1; y >= 0; y--) {
      const data = getData();
      if (comparator(data[0], colour)) {
        edge.push(y);
        break;
      }
    }
  }
  return edge;
}

//call with less than
getEdges(someColour, (a, b) => a < b)
//call with greater than
getEdges(someColour, (a, b) => a > b)

You can also keep your logic in one function and derive two more from it. This way you don't need to maintain multiple code blocks and you still get two explicit calls:

Using partial application with .bind:

function getEdges(comparator, colour) {
//                    ^        ^      the two parameters are swapped
  for (let x = 0; x < width; x++) {
    for (let y = height - 1; y >= 0; y--) {
      const data = getData();
      if (comparator(data[0], colour)) {
        edge.push(y);
        break;
      }
    }
  }
  return edge;
}

//partial application
const getEdgesLessThan = getEdges.bind(null, (a, b) => a < b);
const getEdgesGreaterThan = getEdges.bind(null, (a, b) => a > b);

getEdgesLessThan(someColour)
getEdgesGreaterThan(someColour)

Using a curried function:

function getEdges(comparator) {
//                    ^---------
  return function(colour) {//  | taking two parameters
//                  ^ ----------
    for (let x = 0; x < width; x++) {
      for (let y = height - 1; y >= 0; y--) {
        const data = getData();
        if (comparator(data[0], colour)) {
          edge.push(y);
          break;
        }
      }
    }
    return edge;
  }
}

//currying
const getEdgesLessThan = getEdges((a, b) => a < b);
const getEdgesGreaterThan = getEdges((a, b) => a > b);

getEdgesLessThan(someColour)
getEdgesGreaterThan(someColour)
VLAZ
  • 26,331
  • 9
  • 49
  • 67
  • I just get 'comparator is not a function' with the first block of code. – Hasen Jun 18 '19 at 13:38
  • @AaditMShah oops, well spotted. It wasn't meant to be a code snippet - I used one to format the code by hitting the Tidy button. Also, it's less annoying to type code there than in the normal answer box. I forgot to remove the snippet in the end, though :/ – VLAZ Jun 18 '19 at 13:47
  • @Hasen are you calling the function with a comparator function supplied as a parameter? Check how that should be done below the code. – VLAZ Jun 18 '19 at 13:48
  • @VLAZ Yes I'm using your code to call it `getEdges(someColor, (a, b) => a < b)`. Not sure if this works since above there is another answer using the same type of comparator method and that also returns the same error. It does look like it should work but for some reason doesn't. – Hasen Jun 18 '19 at 13:51
  • @Hasen [here is a demo](https://jsbin.com/rocolum/edit?js,console) with made up numbers. It works in both cases for me. Try to tweak the variables. I don't know why you're getting that result - can you give a reproducible example? – VLAZ Jun 18 '19 at 14:01
1

EDIT: To be clear, in an ideal world I'd like to do this:

myFunction(10, 5, >)

You can get something very similar:

const lt = (x, y) => x < y;
const gt = (x, y) => x > y;

function foo(value, modifier, compare) {
    let result;
    if (compare(value, modifier)) result = 10;
    return result;
}

console.log(foo(2, 3, lt)); // 10
console.log(foo(3, 2, gt)); // 10

Using your second example:

const lt = (x, y) => x < y;
const gt = (x, y) => x > y;

const width  = 3;
const height = 3;

const getData = (x, y) => [height * x + y];

console.log(getEdges(3, lt)); // [2]
console.log(getEdges(3, gt)); // [2,2]

function getEdges(colour, compare) {
    const edge = [];

    for (let x = 0; x < width; x++) {
        for (let y = height - 1; y >= 0; y--) {
            const data = getData(x, y);

            if (compare(data[0], colour)) {
                edge.push(y);
                break;
            }
        }
    }

    return edge;
}

Hope that helps.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Interesting. Would this work my actual function that I added at the end in my updated question? – Hasen Jun 18 '19 at 13:07
  • Yes, yes it would. – Aadit M Shah Jun 18 '19 at 13:07
  • Ok are you sure because I'm getting the data at x,y coordinates within the function so not sure how to compare them in this way. Maybe you can modify the code in your answer to reflect this? – Hasen Jun 18 '19 at 13:10
1

Based on the conversation in the comments, the purpose is to re-use the getEdges function for when a greater than or lower than comparison is needed. I've added a second parameter to indicate this, with it set to false as the default case. The if statement is dual-purpose in the sense that it makes a greater than comparison when the isGreater flag is set to true and a less than comparison when the isGreater flag is set to false. The rest of the logic is re-used verbatim with no duplication.

function getEdgesInner(colour, isGreater = false) {
    for (let x = 0; x < width; x++) {
        for (let y = height - 1; y >= 0; y--) {
            const data = getData(x,y);
            if ((isGreater && data[0] > colour) || (!isGreater && data[0] < colour))
                edge.push(y);
                break;
            }
        }
    }
    return edge;
}

/* Public Api */
function getGreaterEdges(colour) { return getEdges(colour) }
function getLesserEdges(colour) { return getEdges(colour, true) }
Avin Kavish
  • 8,317
  • 1
  • 21
  • 36
  • 1
    Interesting Sir, you may have the neatest answer yet. :) – Hasen Jun 18 '19 at 13:14
  • 1
    @Hasen It's a bad practice to pass boolean flags around: https://softwareengineering.stackexchange.com/q/147977/75147 – Aadit M Shah Jun 18 '19 at 13:22
  • Your link describes a scenario where a boolean flag is passed around till it is eventually used. That is not the case here, this is one flag that has specific meaing and it doesn't get passed around. it is consumed in the method. – Avin Kavish Jun 18 '19 at 13:24
  • 1
    Regardless, passing a boolean flag around is considered a code smell and should be avoided: https://martinfowler.com/bliki/FlagArgument.html – Aadit M Shah Jun 18 '19 at 13:31
  • If you read the source code of many library methods you will see that internal implementations are based on a few methods that change their behaviour based on flags.. The only thing I can agree to is exposing a better public api like I've edited in. – Avin Kavish Jun 18 '19 at 13:44
1

You could keep the operator and just swap the terms according to your needs:

function getEdges(colour, operator) {
    for (let x = 0; x < width; x++) {
        for (let y = height - 1; y >= 0; y--) {
            const data = getData();
            const [a, b] = operator == '<' ? [data[0], colour] : [colour, data[0]];
            if (a < b) {
                edge.push(y);
                break;
            }
        }
    }
    return edge;
    }

EDIT:

Ok, so if you want keep things simple and in separated functions, with minimal code changes you could implement it like this:

function getEdges(colour, operator = '<') {
    for (let x = 0; x < width; x++) {
        for (let y = height - 1; y >= 0; y--) {
            const data = getData();
            const [a, b] = operator == '<' ? [data[0], colour] : [colour, data[0]];
            if (a < b) {
                edge.push(y);
                break;
            }
        }
    }
    return edge;
    }

The signature getEdges(colour, operator = '<') makes the operator parameter optional. If you don't pass it to the function, it'll assume a default value of '<' so that you won't to have to change anything in your existing code. Then, you could make a second function that will reuse the original one, just with a different parameter:

function getEdgesGreaterThan(colour) {
    return getEdges(colour, '>');
}

And there you have it! Hope it helps!

Pedro Corso
  • 557
  • 8
  • 22
1

You can try these ways. just like you wanted in your original sample! With short, stylish and beauty ideas:

mathematical trick:

function func(value, modifier, sign) {
 let result, fc=sign=="<"?1:-1;
 if (value*fc< modifier*fc) result = 10;
 return result; 
}

A useful JS feature:

function func(value, modifier, sign) {//this is slower probably
 let result; 
 if (eval(value+sign+modifier)) result = 10;
 return result; 
}

Usage (both top ways):

console.log(func(1, 2, "<"));

Passing a delegate function (for compare):

function func(value, modifier, compF) {
 let result; 
 if (compF(value, modifier)) result = 10;
 return result; 
}

Usage :

console.log(func(1, 2, function(v, m){return v<m;}/*or equivalent lambda function*/));
Hassan Sadeghi
  • 1,316
  • 2
  • 8
  • 14
0

You should add 3rd argument which takes the condition, for example, string "gt" for Grater Than(>) and "lt" for Less Than(<)

function(value, modifier, cond){
   if(cond === "gt") return value > modifier ? 10 : undefined;
   else if(cond === "lt") return value < modifier ? 10 : undefined; 
}

EDIT: I came up with a better solution, suitable for your updated question.

let check = (value, modifier, cond)=>eval(value + cond + modifier) ? 10 : undefined

here you pass "<" or ">" for cond parameter.

EDIT 2:

function getEdges(colour,cond) {
for (let x = 0; x < width; x++) {
    for (let y = height - 1; y >= 0; y--) {
        const data = getData(x,y);
        let shoudPush = eval(data[0] + cond + colour) ? true : false
        if(shouldPush) {
          edge.push(y); 
          break;
        }
    }
}
return edge;
}
DedaDev
  • 4,441
  • 2
  • 21
  • 28
  • just a side note: the result when "false" should actually be `undefined`, according to his sample. – briosheje Jun 18 '19 at 12:45
  • He does not return false though Edit: What @briosheje says – Wimanicesir Jun 18 '19 at 12:45
  • 1
    you still have to pass a string you can't just pass an operator. It is same at the above with the added risk of an injection attack. If you are adding together 3 strings why use the template syntax `${}` – Avin Kavish Jun 18 '19 at 12:52
  • Well, there is no better solution, and yeah, no need for template syntax, fixed, thanks. – DedaDev Jun 18 '19 at 13:00
0
function(value, modifier, type) {
    let result;
    if(type == ">") {
        if (value > modifier) result = 10;
    } else {
        if (value < modifier) result = 10;
    }
    return result;
}

and pass type as a string "<" or ">"

Daz Chest
  • 66
  • 6
0

EDIT: To be clear, in an ideal world I'd like to do this:

myFunction(10, 5, >)

You can almost do that with using eval. Although I'm a little confused about your result since it potentially returns undefined.

var myFunction = function(value, modifier, operation) {
    var evalResult = eval(value + operation + modifier)
    // I like ternary expression but could also be written as an if/else statement 
    return evalResult ? 10 : undefined
}
myFunction(10, 5, '<') // outputs undefined
myFunction(10, 5, '>') // outputs 10

Just be aware that eval can be dangerous. https://medium.com/@eric_lum/the-dangerous-world-of-javascripts-eval-and-encoded-strings-96fd902af2bd

EDIT Hey I made a codepen in response to your edited answer. https://codepen.io/springborg/pen/ydayYa

I think this is what most closely matches your proposed syntax. Although I would probably advise against using eval unless you are absolutely certain no vulnerability will be exposed.

My recommendation is using Aditya Bhave answer :) https://stackoverflow.com/a/56649540/1303205

springbo
  • 1,096
  • 1
  • 9
  • 15
  • Yeah that example function is not perfect. Check my actual function I added in at the end of my updated question. – Hasen Jun 18 '19 at 13:04
  • Never use `eval`. Not only is it bad coding practice, but also it's unsafe and it significantly reduces performance. – Aadit M Shah Jun 18 '19 at 13:29
0

If reusability is your ultimate goal, here's a curried example of how one might do this (somewhat functional-programming-stylez).

This approach allows greater reuse of the code. You might want to compare things at two points in your code, and return different values according to context.

let functionalComparer = comp => modifier => result => value => {
  return comp(value, modifier) ? result: undefined
}

// curry the crap out of it!
let gt = (x,y) => x > y
let gtComp = functionalComparer(gt)
let gt100 = gtComp(100)
let whenGt100ReturnString = gt100("more than 100!")

console.log(`
  with value 99: ${whenGt100ReturnString(99)},
  with value 101: ${whenGt100ReturnString(101)}
`)

let lt = (x,y) => x < y
let whenLt50ReturnFoo = functionalComparer(lt)(50)("FOO")

console.log(`
  with value 49: ${whenLt50ReturnFoo(49)},
  with value 51: ${whenLt50ReturnFoo(51)}
`)
Roope
  • 291
  • 1
  • 8
  • This is unnecessarily complicated. What's wrong with `const lt = (x, y) => x < y;` and `const gt = (x, y) => x > y;`? It's functional and it's two lines long. – Aadit M Shah Jun 18 '19 at 13:25
  • I'm not really saying that this is the absolute best way to do this specific thing, more just demonstrating the cool factor of currying :) – Roope Jun 18 '19 at 13:31
  • Not the time or place to promote a coding style. StackOverflow answers should be comprehensive, direct, and as minimal as possible. – Aadit M Shah Jun 18 '19 at 13:36