3

So I currently have a function that outputs sound barrier for the input height.

def soundbarier (y):   
    if 0 < y < 1524:
        S = 340.3
    elif 1524 < y < 3048:
        S = 334.4
    elif 3048 < y < 4572:
        S = 328.4
    elif 4572 < y < 6096:
        S = 322.2
    elif 6096 < y < 7620:
        S = 316.0
    elif 7620 < y < 9144:
        S = 309.6
    elif 9144 < y < 10668:
        S = 303.6
    elif 10668 < y < 12192:
        S = 295.4
    else:
        S = 294.5
    return (float(S))

I want to shorten it using a dictionary but I can't get it to work.

def SOUND(y):
    return {
    0 < float(y) < 1524: 340.3,
    1524 < y < 3048: 334.4,
    3048 < y < 4572: 328.4,
    4572 < y < 6096: 322.2,
    6096 < y < 7620: 316.0,
    7620 < y < 9144: 309.6,
    9144 < y < 10668: 303.6,
    10668 < y < 12192: 295.4
    }.get(y, 9)

print(SOUND(1500))

I don't really want any default values. How could I make it work? I basically need the function to output S for a certain Y within a range.

cs95
  • 379,657
  • 97
  • 704
  • 746
user3613025
  • 373
  • 3
  • 10
  • Are you okay with external modules? – cs95 Mar 15 '18 at 00:24
  • Can you elaborate? – user3613025 Mar 15 '18 at 00:26
  • `pandas` has an API called the `IntervalIndex` which builds a tree and indexes in logarithmic time. You can also query the tree with many values at a time. – cs95 Mar 15 '18 at 00:27
  • Then no I'm not familiar with external modules. I guess I'm stuck with that ugly looking function huh? – user3613025 Mar 15 '18 at 00:28
  • 5
    Not a general solution but since your intervals are going up in multiples of 1524, you could just divide through by 1524, rounding down and use that number as your key. – tim-mccurrach Mar 15 '18 at 00:29
  • What's up with the `float(...)` conversions; that seems unnecessary. –  Mar 15 '18 at 00:29
  • You would also need to check that y % 1524 != 0 since these aren't included as your code stands – tim-mccurrach Mar 15 '18 at 00:31
  • I asked whether you are alright with using them in your code, not whether you know them or not. You will have to pip install these modules first, I don't know whether that is an option for you. – cs95 Mar 15 '18 at 00:31
  • Oh lol forgot to remove the `float(...)` – user3613025 Mar 15 '18 at 00:34
  • I'm using Spyder @cᴏʟᴅsᴘᴇᴇᴅ – user3613025 Mar 15 '18 at 00:35
  • @user3613025, spyder has `numpy` (the generalised solution I offer), but if it is indeed the case you will be always be using multiples of x Tim's solution looks great. – jpp Mar 15 '18 at 00:37
  • @Tim I implemented your idea in a [CW post](https://stackoverflow.com/a/49289794/4909087). No rep will come to me for it. Nice idea. – cs95 Mar 15 '18 at 00:38
  • I get that you're thankful, but you can show your appreciation through upvotes better than adding text that isn't relevant to your question. :) – cs95 Mar 15 '18 at 00:44
  • @cᴏʟᴅsᴘᴇᴇᴅ, this is an interesting and common problem. I'm disappointed nobody interesting in upvoting / downvoting the solutions here! I've started voting now.. – jpp Mar 15 '18 at 09:28
  • @jpp Returned! I agree. – cs95 Mar 15 '18 at 09:46

4 Answers4

2

Mathemagic by @Tim.

consts = [340.3, 334.4, 328.4, 322.2, 316.0, 309.6, 303.6, 295.4, 295.5]

def soundbarrier(y):
    return consts[
        -1 if (not (0 < y < 12192) or y % 1524 == 0) else y // 1524
    ]

In [1]: soundbarrier(1500)
Out[1]: 340.3

In [2]: soundbarrier(10000)
Out[2]: 303.6

In [3]: soundbarrier(100)
Out[3]: 340.3

In [4]: soundbarrier(1524)
Out[4]: 295.5
cs95
  • 379,657
  • 97
  • 704
  • 746
2

There is no built-in syntax for this, but you can implement it efficiently with 'bisect', which finds location of a number in a list for you.

def soundbar(y):
  barrier_dict = {
    1524: 340.0,
    3048: 334.4,
    4572: 328.4,
    float('inf'): 0
  }
  height, barrier = zip(*sorted(barrier_dict.items()))
  return barrier[bisect(height, y)]

print 1000, '->', soundbar(1000)
print 2000, '->', soundbar(2000)
print 2500, '->', soundbar(2500)
print 10000, '->', soundbar(10000)

outputs:

1000 -> 340.0
2000 -> 334.4
2500 -> 334.4
10000 -> 0
Evgeny
  • 3,064
  • 2
  • 18
  • 19
1

One general solution is to use numpy.digitize combined with a dictionary.

Note this doesn't deal with edge cases where your input is on a boundary, in which case floating point approximations may need careful attention.

import numpy

def SOUND(y):

    bins = np.array([0, 1524, 3048, 4572, 6096, 7620, 9144, 10668, 12192])
    values = np.array([340.3, 334.4, 328.4, 322.2, 316.0, 309.6, 303.6, 295.4])

    d = dict(enumerate(values, 1))

    return d[int(np.digitize(y, bins))]

SOUND(7200)  # 316.0
jpp
  • 159,742
  • 34
  • 281
  • 339
  • I'm not familiar with `numpy.digitize`. I basically have 2 set of lists that will run through this sound barrier function and if at any point at a certain y, my velocity is bigger than S then it'll output a "sound barrier broken" message. Will that work with your answer above? – user3613025 Mar 15 '18 at 00:41
  • @user3613025 It will, but you don't need numpy. See the other answers. – cs95 Mar 15 '18 at 00:42
  • 1
    @user3613025, all `np.digitize` does is figure out where the value sits in ranges deduced from consecutive members of `bins`. The result is an integer, e.g. 5 in this case. The 5 is then mapped via the dictionary to 316.0 (the 5th value in `values`). – jpp Mar 15 '18 at 00:53
0

The problem with your code is that python will evaluate the keys in your dictionary to True/False.

How about creating a mapping table(ie a dictionary of tuples to values) like this:

sound_barrier = { (0, 1524): 340.3, (1524, 3048): 334.4, (3048, 4572): 328.4, ... }
def get_mapping(table, value):
    for k in table: 
        if k[0] < value < k[1]:
            return table[k]
get_mapping(sound_barrier, 2000)
Lingster
  • 977
  • 10
  • 21