6

Problem:

Write a Python function, clip(lo, x, hi) that returns lo if x is less than lo; hi if x is greater than hi; and x otherwise. For this problem, you can assume that lo < hi.

Don't use any conditional statements for this problem. Instead, use the built in Python functions min and max. You may wish to read the documentation on min and the documentation on max, and play around with these functions a bit in your interpreter, before beginning this problem.

This function takes in three numbers and returns a single number.

Code Given:

def clip(lo, x, hi):
    '''
    Takes in three numbers and returns a value based on the value of x.
    Returns:
     - lo, when x < lo
     - hi, when x > hi
     - x, otherwise
    '''

My Code Added:

def clip(lo, x, hi):
    '''
    Takes in three numbers and returns a value based on the value of x.
    Returns:
     - lo, when x < lo
     - hi, when x > hi
     - x, otherwise
    '''
    if min(x, lo, hi) == x:
        return lo
    elif max(x, lo, hi) == x:
        return hi
    else:
        return x

Here's the problem: I can't use ANY conditionals. Help!

viraptor
  • 33,322
  • 10
  • 107
  • 191
user2066771
  • 153
  • 2
  • 4
  • 11

10 Answers10

9

Here is a solution, assuming that lo < hi.

def clip(lo, x, hi):
    return max(lo, min(hi, x))

How it works in each case:

  • lo, when x < lo: if lo < hi, then x < hi, so min(hi, x) returns x and max(lo, x) returns lo.
  • hi, when x > hi: min(hi, x) returns hi and if lo < hi, max(lo, hi) returns hi
  • x, otherwise: x > lo and x < hi, so min(hi, x) returns x and max(lo, x) returns x
Nicolas
  • 5,583
  • 1
  • 25
  • 37
8

So you have a number of options proposed so far. Not yet posted is the nested ternary expression:

def clip(lo, x, hi):
    return lo if x <= lo else hi if x >= hi else x

But since this uses explicit conditional tests, probably not suitable as a solution to the original question. Still, given these options, this is the one that actually has the advantages of short-circuiting if x <= lo (all other methods evaluate all comparisons and/or perform one or two method calls). Let's see how these alternatives actually perform using timeit (tested with Python 3.3, so range does not build a list, but returns an iterator):

python -m timeit -s "lo,hi=10,90" "[max(lo,min(hi,x)) for x in range(100)]"
10000 loops, best of 3: 54.5 usec per loop

(2 function calls per evaluation, kills performance)

python -m timeit -s "lo,hi=10,90" "[(lo,(hi,x)[x<hi])[x>lo] for x in range(100)]"
10000 loops, best of 3: 40.9 usec per loop

(evaluates both tests and builds tuples for every evaluation, but at least no function calls)

python -m timeit -s "lo,hi=10,90" "[sorted((lo,x,hi))[1] for x in range(100)]"
10000 loops, best of 3: 90.5 usec per loop

(builds tuple and sorts - sorry, Gnibbler, this is the slowest)

python -m timeit -s "lo,hi=10,90" "[lo if x <= lo else hi if x >= hi else x for x in range(100)]"
100000 loops, best of 3: 18.9 usec per loop

(fastest, no function calls, only evaluates x >= hi if x > lo)

This short-circuiting can be seen if you move the value of lo to much higher in the test range:

python -m timeit -s "lo,hi=80,90" "[lo if x <= lo else hi if x >= hi else x for x in range(100)]"
100000 loops, best of 3: 15.1 usec per loop

(If you want to reproduce these under Python 2.x, replace range with xrange.)

PaulMcG
  • 62,419
  • 16
  • 94
  • 130
  • Better to just put `L=range(100)` in the setup since you are not modifying it – John La Rooy Feb 18 '13 at 05:09
  • Some might say the ternary expression is a conditional though. – John La Rooy Feb 18 '13 at 05:18
  • I agree, hence my comment "But since this uses explicit conditional tests, probably not suitable as a solution to the original question." AND an interesting thing: I was afraid that putting `L=range(100)` in the setup would be a problem since I am running in Python 3.3, in which `range` returns an iterator, which would be consumed in the first test run, but then be empty for all following test runs. This turned out not to be the case, so apparently the setup code is called before every test run, just not included in the timing statistics (or I could have just setup `L=list(range(100))`.) – PaulMcG Feb 19 '13 at 08:08
3

Without giving out the whole solution - you don't need to "check" anything. A value limited to lo from the bottom is what you get from running max(x, lo).

Also value clipped to one boundary is not going to be affected by clipping to the other boundary, so you can safely run the result of one correction through another one.

viraptor
  • 33,322
  • 10
  • 107
  • 191
2

I looked through all the cases, the answer in every case is the term in the middle when arranged according to magnitude

        def clip(lo,x,hi):
             t = hi + lo + x - max(lo,x,hi)- min(lo,x,hi)
             return t
1

Another solution:

def clip(lo, x, hi):
    result = {x: x}
    result[min(x, lo)] = lo
    result[max(x, hi)] = hi
    return result[x]
Nicolas
  • 5,583
  • 1
  • 25
  • 37
ndpu
  • 22,225
  • 6
  • 54
  • 69
1

solution:

def clip(lo, x, hi):
    x = max(lo, x)
    x = min(x, hi)   
    return x
SagarSave
  • 31
  • 2
0
def clip(lo, x, hi):
    return sorted((lo, x, hi))[1]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
0

min? max? What about:

def clip(lo,x,hi):
    return (lo,(hi,x)[x<hi])[x>lo]
jassinm
  • 7,323
  • 3
  • 33
  • 42
PaulMcG
  • 62,419
  • 16
  • 94
  • 130
0

This will do the trick without conditional operators.

max(lo,min(hi,x))
jcoppens
  • 5,306
  • 6
  • 27
  • 47
-2

here is also a solution: return min(max(x, lo), hi)