Daily Challenge #57 - BMI Calculator

twitter logo ・1 min read

Daily Challenge (114 Part Series)

1) Daily Challenge #1 - String Peeler 2) Daily Challenge #2 - String Diamond 3 ... 112 3) Daily Challenge #3 - Vowel Counter 4) Daily Challenge #4 - Checkbook Balancing 5) Daily Challenge #5 - Ten Minute Walk 6) Daily Challenge #6 - Grandma and her friends 7) Daily Challenge #7 - Factorial Decomposition 8) Daily Challenge #8 - Scrabble Word Calculator 9) Daily Challenge #9 - What's Your Number? 10) Daily Challenge #10 - Calculator 11) Daily Challenge #11 - Cubic Numbers 12) Daily Challenge #12 - Next Larger Number 13) Daily Challenge #13 - Twice Linear 14) Daily Challenge #14 - Square into Squares 15) Daily Challenge #15 - Stop gninnipS My sdroW! 16) Daily Challenge #16 - Number of People on the Bus 17) Daily Challenge #17 - Double Trouble 18) Daily Challenge #18 - Triple Trouble 19) Daily Challenge #19 - Turn numbers into words 20) Daily Challenge Post #20 - Number Check 21) Daily Challenge #21 - Human Readable Time 22) Daily Challenge #22 - Simple Pig Latin 23) Daily Challenge #23 - Morse Code Decoder 24) Daily Challenge #24 - Shortest Step 25) Daily Challenge #25 - Double Cola 26) Daily Challenge #26 - Ranking Position 27) Daily Challenge #27 - Unlucky Days 28) Daily Challenge #28 - Kill the Monster! 29) Daily Challenge #29 - Xs and Os 30) Daily Challenge #30 - What is the price? 31) Daily Challenge #31 - Count IPv4 Addresses 32) Daily Challenge #32 - Hide Phone Numbers 33) Daily Challenge #33 - Did you mean...? 34) Daily Challenge #34 - WeIrD StRiNg CaSe 35) Daily Challenge #35 - Find the Outlier 36) Daily Challenge #36 - Let's go for a run! 37) Daily Challenge #37 - Name Swap 38) Daily Challenge #38 - Middle Name 39) Daily Challenge #39 - Virus 40) Daily Challenge #40 - Counting Sheep 41) Daily Challenge #41 - Greed is Good 42) Daily Challenge #42 - Caesar Cipher 43) Daily Challenge #43 - Boardgame Fight Resolver 44) Daily Challenge #44 - Mexican Wave 45) Daily Challenge #45 - Change Machine 46) Daily Challenge #46 - ??? 47) Daily Challenge #47 - Alphabets 48) Daily Challenge #48 - Facebook Likes 49) Daily Challenge #49 - Dollars and Cents 50) Daily Challenge #50 - Number Neighbor 51) Daily Challenge #51 - Valid Curly Braces 52) Daily Challenge #52 - Building a Pyramid 53) Daily Challenge #53 - Faro Shuffle 54) Daily Challenge #54 - What century is it? 55) Daily Challenge #55 - Building a Pile of Cubes 56) Daily Challenge #56 - Coffee Shop 57) Daily Challenge #57 - BMI Calculator 58) Daily Challenge #58 - Smelting Iron Ingots 59) Daily Challenge #59 - Snail Sort 60) Daily Challenge #60 - Find the Missing Letter 61) Daily Challenge #61 - Evolution Rate 62) Daily Challenge #62 - Josephus Survivor 63) Daily Challenge #63- Two Sum 64) Daily Challenge #64- Drying Potatoes 65) Daily Challenge #65- A Disguised Sequence 66) Daily Challenge #66- Friend List 67) Daily Challenge #67- Phone Directory 68) Daily Challenge #68 - Grade Book 69) Daily Challenge #69 - Going to the Cinema 70) Daily Challenge #70 - Pole Vault Competition Results 71) Daily Challenge #71 - See you next Happy Year 72) Daily Challenge #72 - Matrix Shift 73) Daily Challenge #73 - ATM Heist 74) Daily Challenge #74 - Free Pizza 75) Daily Challenge #75 - Set Alarm 76) Daily Challenge #76 - Bingo! (or not...) 77) Daily Challenge #77 - Bird Mountain 78) Daily Challenge #78 - Number of Proper Fractions with Denominator d 79) Daily Challenge #79 - Connect Four 80) Daily Challenge #80 - Longest Vowel Change 81) Daily Challenge #81 - Even or Odd 82) Daily Challenge #82 - English Beggars 83) Daily Challenge #83 - Deodorant Evaporator 84) Daily Challenge #84 - Third Angle of a Triangle 85) Daily Challenge #85 - Unwanted Dollars 86) Daily Challenge #86 - Wouldn't, not Would. 87) Daily Challenge #87 - Pony Express 88) Daily Challenge #88 - Recursive Ninjas 89) Daily Challenge #89 - Extract domain name from URL 90) Daily Challenge #90 - One Step at a Time 91) Daily Challenge #91 - Bananas 92) Daily Challenge #92 - Boggle Board 93) Daily Challenge #93 - Range Extraction 94) Daily Challenge #94 - Last Digit 95) Daily Challenge #95 - CamelCase Method 96) Daily Challenge #96 - Easter Egg Crush Test 97) Daily Challenge #97 - Greed is Good 98) Daily Challenge #98 - Make a Spiral 99) Daily Challenge #99 - Balance the Scales 100) Daily Challenge #100 - Round Up 101) Daily Challenge #101 - Parentheses Generator 102) Daily Challenge #102 - Pentabonacci 103) Daily Challenge #103 - Simple Symbols 104) Daily Challenge #104 - Matrixify 105) Daily Challenge #105 - High-Sum Matrix Drop 106) Daily Challenge #106 - Average Fuel Consumption 107) Daily Challenge #107 - Escape the Mines 108) Daily Challenge #108 - Find the Counterfeit Coin 109) Daily Challenge #109 - Decorate with Wallpaper 110) Daily Challenge #110 - Love VS. Friendship 111) Daily Challenge #111 - 99 Bottles of Beer 112) Daily Challenge #112 - Functions of Integers on the Cartesian Plane 113) Daily Challenge #113 - Iterative Rotation Cipher 114) Daily Challenge #114 - Speed Control

Write a function that calculates body mass index
Note: bmi = weight / height ^ 2

if bmi <= 18.5 return "Underweight"
if bmi <= 25.0 return "Normal"
if bmi <= 30.0 return "Overweight"
if bmi > 30 return "Obese"


This challenge comes from wichu on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

twitter logo DISCUSS (22)
markdown guide
 

Well, I tried something different:

f=(w,h,t=[18.5,25,30],k=['Underweight','Normal','Overweight'])=>k[t.indexOf(t.find(v=>w/(h^2)<=v))]||'Obese'

Apart from being ugly, it works like this:

  • Store the BMI steps in "t": t=[18.5,25,30]
  • Store the associated labels in "k": k=['Underweight','Normal','Overweight']
  • Find the first BMI step which is greater than the provided BMI: t.find(v=>w/(h^2)<=v). For example, it will return 25 if the processed BMI is between 18.5 and 25.
  • Find the associated array index: t.indexOf(...), in order to get the same element in the label array: k[...]. For example, if 25 is returned, it will fetch k[1], hence "Normal"
  • Finally, if no label is found (hence, if the BMI is greater than 30), return "Obese"
 

That is an interested method, but come back at your code in a few months with absolutely no explanation and I do think that it is not going to be that easy.

I think that a code should be self explanatory (especially to newcomers on your codebase) and, even if this one is a tour-de-force in terms of one-liner, it is not self explanatory. 😉

That is why, I would find that a switch case method would be better.

 

I completely agree with you on this one! (Hi fellow french dev!) I tend to try to golf a lot in these challenges, even though I completely agree on the fact that it's as ugly as it can be. That way, I can discover new principles (like the reduce method, that I hated a few months back even if it is really useful) that I can later use on my projects or at work.

Indeed, this challenge was clearly made for a switch/case solution, but as I had some free time I though I could try something different haha

 
 
 

Haskell

bmi :: Double -> Double -> String
bmi weight height
  | x <= 18.5 = "Underweight"
  | x <= 25 = "Normal" 
  | x <= 30 = "Overweight"
  | otherwise = "Obese"
  where x = weight / (height*height)
 

This seems to do the job

C++:

    const auto& bmi = [](const float& weight, const float& height){
        const float bmi = weight / (height * height);
        return bmi <= 18.0f ? "Underweight" :
               bmi <= 25.0f ? "Normal" :
               bmi <= 30.0f ? "Overweight" :
               "Obese";
    };
 

Might propose a change? Instead of underweight, overweight, obsese, etc, how about we replace the return values with "no idea, consult a health professional and have your body composition correctly measured"? It will be significantly more accurate! :D

 

Something like:

(w, h) => `A BMI of ${w/h**2}, means nothing. For an accurate representation of body mass measure water displacement in a big bath tub! To determine how healthy you are, consult a healthcare professional, do see how trip you are have your body composition measured, and to see how fit you are visit any good gym and undertake a fitness test in the area you wish to evaluate.`
 
 

I couldn't resist posting a quick JavaScript solution... 😀

const calcBMI = (height, weight) => {
  const bmi = Number(weight / (height * height)).toFixed(1);
  return bmi <= 18.5 ? "underweight" : 
         bmi <= 25 ? "normal" :
         bmi <= 30 ? "overweight" : "obese"; 
}
 

F#:

module BMI

[<Measure>]
type kg

[<Measure>]
type m

[<Measure>]
type lb

[<Measure>]
type inch

let (|LessOrEqual|_|) (target : float) (value : float) =
    if value <= target then Some()
    else None

let bmi ratio =
    match ratio with
    | LessOrEqual 18.5 -> "Underweight"
    | LessOrEqual 25.0 -> "Normal"
    | LessOrEqual 30.0 -> "Overweight"
    | _ -> "Obese"

module Metric =
    let calculate (weight : float<kg>) (height : float<m>) : string =
        float (weight / (height * height)) |> bmi

module Imperial =
    let calculate (weight : float<lb>) (height : float<inch>) : string =
        float (weight / (height * height)) * 703. |> bmi

When reading the problem description I wasn't 100% sure which units the weight and height were supposed to be in. I googled and found both a metric and imperial definition, so I decided to use units of measure and nested modules so callers of the code need to be explicit. Example:

BMI.Metric.calculate 85.0<kg> 1.85<m>;;
 val it : string = "Normal"

BMI.Imperial.calculate 187.39<lb> 72.83<inch>;;
 val it : string = "Normal"

BMI.Imperial.calculate 85<kg> 72.83<inch>;;
 Stopped due to error
 System.Exception: Operation could not be completed due to earlier error
 Type mismatch. Expecting a
     'float<lb>'   
 but given a
     'float<kg>'    
 The unit of measure 'lb' does not match the unit of measure 'kg' at 0,23   

The active pattern LessOrEqual was purely for fun, one could just use if-else expressions or pattern matching with guards.

 

Rust:

fn bmi(weight: f64, height: f64) -> &'static str {
    let bmi: f64 = weight / height.powi(2);

    if bmi <= 18.5 {
        "Underweight"
    } else if bmi <= 25.0 {
        "Normal"
    } else if bmi <= 30.0 {
        "Overweight"
    } else {
        "Obese"
    }
}
 

Let's say you need to run this a few billion times times, then a more complex look up table approach might be worth it.

function indexFromBmi(bmi) {
  // turn the BMI into an ranged integer still giving with enough precision and reach for our labels
  return Math.max(0, Math.ceil((Math.min(30.5, bmi) - 18.5) / 0.5));
}
const bmi_lut = []
bmi_lut[indexFromBmi(18.5)] = 'Underweight';
bmi_lut[indexFromBmi(25)] = 'Normal';
bmi_lut[indexFromBmi(30)] = 'Overweight';
bmi_lut[indexFromBmi(30.5)] = 'Obese';

// fill in the entries below each label, so that, for example, the entry for bmi 27 will be "Overweight"
for (let index = bmi_lut.length - 1, label; index >= 0; index--) {
  if (bmi_lut[index]) label = bmi_lut[index];
  else bmi_lut[index] = label;
}

function bmiLabel(weight, height) {
  return bmi_lut[indexFromBmi(weight / height ** 2)];
}
> bmiLabel(-100000000,1)
< "Underweight"
> bmiLabel(18.5,1)
< "Underweight"
> bmiLabel(18.5001,1)
< "Normal"
> bmiLabel(25,1)
< "Normal"
> bmiLabel(25.0001,1)
< "Overweight"
> bmiLabel(30,1)
< "Overweight"
> bmiLabel(30.001,1)
< "Obese"
> bmiLabel(10000000000,1)
< "Obese"
// Test the weight/height relationship
> bmiLabel(25 * 1e20 ** 2, 1e20)
< "Normal"
// Try some heavy lifting
> for (let i=1e9; i; i--) bmiLabel(18.5,1);
< //took 23 seconds for a billion bmis in the js console just now (oldish mac pro)

(in JS because I like it, and I find it can be nice to mock up performance code in a slowish language first up.)

 

Have you tested if your code is faster than a simpler conditional version (as most other solutions are written)?

 

Fair point Craig, in this case it turns out to be a bunch slower due to the complexity in indexFromBmi.
I just figured it was worth posting as an alternative approach as there are problems where luts are gold. In this case apparently not.

I'd say that here it's best to just use

function bmiLabel(weight, height) {
  const bmi = weight / height ** 2;
  return bmi <= 18.5 ? 'Underweight' : bmi <= 25 ? 'Normal' : bmi <= 30 ? 'Overweight' : 'Obese';
}

and be done with it.

 

A solution in gwion

fun string bmi(float weight, float height) {
  height *=> height;
  weight / height => float bmi;
  if(bmi <= 18.5) 
    return "Underweight";
  if(bmi <= 25.0)
    return "Normal";
  if(bmi <= 30.0)
    return "Overweight";
  return "Obese";
}
 

Scala:

object Body {
    def calculateBMI(weight: Double, height: Double): String = (weight / (height * height)) match {
        case x if x <= 18.5 => "Underweight"
        case x if x <= 25 => "Normal"
        case x if x <= 30 => "Overweight"
        case _ => "Obsese"
    }
}
 

rust:

fn bmiCalc(weight: f32, height: f32) -> &'static str {
    let bmi = weight / height.powi(2);
    match bmi {
        b if b <= 18.5 => "Underweight",
        b if b <= 25.0 => "Normal",
        b if b <= 30.0 => "Overweight",
        _ => "Obese"
    }
}
 

I did two functions, one that expects imperial units (Go America!) and one that expects metric units (the rest of the world!).

body.go

package body

const (
    underweight string = "Underweight"
    normal      string = "Normal"
    overweight  string = "Overweight"
    obese       string = "Obese"
)

// Attributes represents some properties of a person's physique, such as weight and height
type Attributes struct {
    Height float64
    Weight float64
}

// BMI gives a description of your body health based on your attributes using body mass index
func BMI(attr Attributes) string {
    return status((attr.Weight / (attr.Height * attr.Height)) * 703)
}

// BMIMetric serves the same purpose as BMI but expects the attributes to be in the metric scale (kilograms, meters)
func BMIMetric(attr Attributes) string {
    return status(attr.Weight / (attr.Height * attr.Height))
}

func status(bmi float64) string {
    switch {
    case bmi <= 18.5:
        return underweight
    case bmi <= 25.0:
        return normal
    case bmi <= 30.0:
        return overweight
    default:
        return obese
    }
}

body_test.go

package body

import "testing"

type testCase struct {
    description string
    input       Attributes
    expected    string
}

func TestBMI(t *testing.T) {
    testCases := []testCase{
        {
            "underweight boi",
            Attributes{71, 116.0},
            underweight,
        },
        {
            "normal boi",
            Attributes{71, 147.0},
            normal,
        },
        {
            "overweight boi",
            Attributes{67, 190},
            overweight,
        },
        {
            "obese boi",
            Attributes{65, 210},
            obese,
        },
    }

    for _, test := range testCases {
        if result := BMI(test.input); result != test.expected {
            t.Fatalf("FAIL: %s - BMI(%+v): %s - expected %s", test.description, test.input, result, test.expected)
        }
        t.Logf("PASS: %s", test.description)
    }
}

func TestBMIMetric(t *testing.T) {
    testCases := []testCase{
        {
            "underweight boi",
            Attributes{1.8034, 52.61671},
            underweight,
        },
        {
            "normal boi",
            Attributes{1.8034, 66.67808},
            normal,
        },
        {
            "overweight boi",
            Attributes{1.7018, 86.18255},
            overweight,
        },
        {
            "obese boi",
            Attributes{1.651, 95.2544},
            obese,
        },
    }

    for _, test := range testCases {
        if result := BMIMetric(test.input); result != test.expected {
            t.Fatalf("FAIL: %s - BMIMetric(%+v): %s - expected %s", test.description, test.input, result, test.expected)
        }
        t.Logf("PASS: %s", test.description)
    }
}

 
Classic DEV Post from May 19

Why Use Python for Startups?

The advantages of using Python for building your startup

dev.to staff profile image
The hardworking team behind dev.to ❤️
JOIN DEV NOW

What are you waiting for?