12
\$\begingroup\$

Introduction:

Like Twitter and Instagram and others, I wanted to display numbers like 1.2K and 3.8 M instead of 1,222 or 3,823,456.

But that's not all! As we all know, there might be some human beings which undoubtely won't like these abbreviations and will try to reverse them. So, 1.2k will become 1,200 and 3.8 M will become 3,800,000.

The task:

  • your task is to write a program or a function that converts a list of numbers (which are given as strings) into their abbreviate pairs and vice-versa.

For example, if the input list (or any STDIN) was ['1.4k', '1,234,567', '7.99M'], then you should output:

['1,400', '1.2M', '7,990,000']

You can follow the next schema for abbreviations:

  • 103 -> one kilo -> K
  • 106 -> one million -> M
  • 109 -> one billion -> B

Your code may assume all lowercase, all uppercase, mixed case or undefined case for input and use any of these for output, but should be consistent.

Rules and restrictions:

  • you may write a program or function, taking input via STDIN (or closest alternative), command-line argument or function argument and outputting the result via STDOUT (or closest alternative), function return value or function (out) parameter.
  • input may be in any convenient list or string format. You may assume that the ai are less than 231 each and that the list contains at least one element.
  • each abbreviated number will contain only one . while a normal number will contain as many , as necessary (you may assume that this numbers won't be altered).
  • you MAY NOT enter a number as '123456' but rather 123,456
  • standard rules apply.

Test cases:

Input: ['1.5M', '77.6k', '123,456,789']         Output: ['1,500,000', '77,600', '123.4M']
Input: ['3,000,000,000', '581k', '2b']          Output: ['3B', '581,000', '2,000,000,000']
Input: ['0.1k']                                 Output: ['100']
Input: ['888', '33']                            Output: ['0.888k', '0.033k']

Clarifications:

  • for numbers < 1000 after the decimal point in abbreviation output you should have as many digits as required to get the correct result. (e.g: 2 -> will become 0.002k) - that means 3 decimals at most; for numbers > 1000 you can have a maximum of 1 decimal.
  • the abbreviation may be in both lower or upper case
  • I removed the built-ins restriction as suggested in the comments

The shortest code in bytes wins!

\$\endgroup\$
25
  • 1
    \$\begingroup\$ Requests for clarification: how many digits after the decimal point in abbreviation output? how to abbreviate numbers < 1000? uppercase or lowercase or both in input and output? \$\endgroup\$
    – edc65
    Nov 3, 2016 at 12:07
  • 1
    \$\begingroup\$ Shouldn't '123,456,789' -> '123.4M'? Also, this doesn't clarify how many decimals to use. Surely anything under 1000 shouldn't need to be abbreviated anyways. \$\endgroup\$
    – Kade
    Nov 3, 2016 at 12:48
  • 1
    \$\begingroup\$ @anonymous2 read the third rule. \$\endgroup\$ Nov 3, 2016 at 13:25
  • 4
    \$\begingroup\$ "you're not allowed to use any built-in module / function" Any built-in function? \$\endgroup\$ Nov 3, 2016 at 14:12
  • 1
    \$\begingroup\$ Your usage of "kilo" suggests SI prefixes and those would be ["k", "M", "G"]. What does "while a normal number will contain as many , as necessary" mean, in my country it would be a mistake to use any. \$\endgroup\$
    – Angs
    Nov 3, 2016 at 14:31

5 Answers 5

3
\$\begingroup\$

05AB1E, 67 bytes

…KMBU3L3*°Vε',Ku¤©ai¨®XY‡*ïR3ô',ýRëY/ʒ1@N_~}ε4°z+N_·Ì.ò¨0Ü'.ÜXNè«}θ

What a mess.. :/

Try it online.

Explanation:

…KMB                     # Push string "KMB"
    U                    # Pop and store it in variable `X`
3L                       # Push list [1,2,3]
  3*                     # Multiply each by 3: [3,6,9]
    °                    # Take 10 to the power each: [1000,1000000,1000000000]
     V                   # Pop and store it in variable `Y`
ε                        # Map over the (implicit) input-list:
 ',K                    '#  Remove all ","
    u                    #  Convert it to uppercase
 ¤                       #  Get the last character (without popping)
  ©                      #  Store this in variable `®` (without popping)
   ai                    #  If it's a letter:
     ¨                   #   Remove it from the string
      ®                  #   Push the letter from variable `®`
       XY‡               #   Transliterate it to the correct value in `Y` based on `X`
          *              #   Multiply the two numbers together
           ï             #   Cast it to an integer
     R                   #   Reverse it
      3ô                 #   Split it into parts of size 3
        ',ý             '#   Join it with "," delimiter
           R             #   Reverse it back
    ë                    #  Else:
     Y/                  #   Divide it by each value in list `Y`
       ʒ                 #   Filter this triplet by:
        1@               #    Check if it's >= 1
            ~            #    OR
          N_             #    The 0-based filter-index is 0
                         #    (so we'll still have a result left if <1000)
       }ε                #   After the filter: map over the remaining values:
         4°z+            #    Add 0.0001 to the number:
         4°              #     Push 10 to the power 4
           z             #     Divide 1 by this
            +            #     Add this 1/10000 to the number
         N               #    Push the 0-based map-index
          _              #    Check if it's 0 (1 if 0; 0 otherwise)
           ·             #    Double it (2 if 0; 0 otherwise)
            Ì            #    Increase by 2 (4 if 0; 2 otherwise)
             .ò          #    Round to that many decimals
               ¨         #    Remove the last digit
                0Ü       #    First trim potential trailing "0"
                '.Ü     '#    And then also trim a potential trailing "."
                   XNè«  #    Append the `N`'th letter of string `X`
        }θ               #   After the inner map: pop and leave the last item
                         # (after which the mapped list is output implicitly as result)
\$\endgroup\$
2
\$\begingroup\$

PHP, 234 224 213 201 205 bytes

for(;$x=$argv[++$n];){$y=str_replace(",","",$x)/1e3;for($i=0;$y>999;$i++)$y=($y|0)/1e3;echo(A<$c=substr($x,strlen($x)-1))?number_format($x*[k=>1e3,m=>1e6,b=>1e9][$c]):($i?($y*10|0)/10:$y).kmb[$i]," ";}

6 bytes saved by insertusernamehere, 4 bytes inspired by that.


  • takes input from command line arguments, prints results space-separated with a trailing separator
  • expects lower case abbreviation
  • run with -r

-2 bytes if underscore as separator is ok: Replace " " with _.
-1 byte if correct rounding is ok: Replace ($y*10|0)/10 with round($y,1).
-17 bytes for PHP 7.1: Replace substr($x,strlen($x)-1) with $x[-1].


80 (63) bytes for expanding one argument only:

<?=number_format(($x=$argv[1])*[K=>1e3,M=>1e6,B=>1e9][substr($x,strlen($x)-1)]);

save to file, then execute (or replace <?= with echo +space and run with -r.

\$\endgroup\$
10
  • \$\begingroup\$ Your second example doesn't compile. \$\endgroup\$ Nov 3, 2016 at 14:15
  • 1
    \$\begingroup\$ You have unbalanced parens. \$\endgroup\$ Nov 3, 2016 at 14:17
  • 1
    \$\begingroup\$ -4 bytes: for($j=1;$x=$argv[$j++];) – instead of foreach($argv as$i=>$x)if($i) \$\endgroup\$ Nov 3, 2016 at 17:05
  • 1
    \$\begingroup\$ -2 bytes: kmb[$i] – instead of "kmb"[$i]. \$\endgroup\$ Nov 3, 2016 at 17:20
  • 1
    \$\begingroup\$ @insertusernamehere Negative string indexes are coming in PHP 7.1; and that´s a RC (yet). Thanks for the other bytes! \$\endgroup\$
    – Titus
    Nov 4, 2016 at 10:18
2
\$\begingroup\$

JavaScript, 545 524 522 518 514 508 504 498 494 214 bytes

Thanks to @ETHproductions for saving 180 bytes!

d=F=>F.map(f=>1/f.slice(-1)?f=(f=f.replace(/,/g,""))[9]?(f/1e8|0)/10+"B":f[6]?(f/1e5|0)/10+"M":f/1e3+"k":R(R(f.slice(0,-1)+"e"+' kMB'.indexOf(f.substr(-1))*3-0+"").match(/.{1,3}/g)+""),R=x=>[...x].reverse().join``)

To call the function:

d(["1.5M","1,500,000"]) //["1,500,500","1.5M"]

Outputs as alert, where each alert contains a different element of the input

Readable version:

d = F => F.map(f => 1 / f.slice(-1) ? f = (f = f.replace(/,/g, ""))[9] ? (f / 1e8 | 0) / 10 + "B" : f[6] ? (f / 1e5 | 0) / 10 + "M" : f / 1e3 + "k" : R(R(f.slice(0, -1) + "e" + ' kMB'.indexOf(f.substr(-1)) * 3 - 0 + "").match(/.{1,3}/g) + ""), R = x => [...x].reverse().join ``)

Summary of edits: converted function to an arrow function

  • removed semi-colons ';'
  • removed var
  • converted to an arrow function
  • used map to iterate through the individual elements of the array
  • used |0 instead of floor
  • used regex for testing
  • used ternary operators instead of if-else statements
  • included a separate function for .reverse().join''
\$\endgroup\$
1
2
\$\begingroup\$

Python, 322 321 318 bytes

import math
def f(l,r=[]):
 for s in l:
  if(c:=s[-1])in(p:=' kMB'):r+=[f'{int(float(s[:-1])*10**(p.index(c)*3)):,}']
  elif(i:=int(s.replace(',','')))>999:
   if(l:=math.log10(i))<4.5:r+=[f'{round(i/1e3,1)}k']
   elif l<7.5:r+=[f'{round(i/1e6,1)}M']
   else:r+=[f'{round(i/1e9,1)}B']
  else:r+=[f'{i/1e3}k']
 return r

-3 thanks to jezza_99

Attempt This Online!

\$\endgroup\$
1
  • \$\begingroup\$ -3 bytes by removing unnecessary whitespace \$\endgroup\$
    – jezza_99
    Oct 25, 2022 at 19:50
1
\$\begingroup\$

Python, 207 203 bytes

lambda L:[s[-1]in'kMB'and f'{int(float(s[:-1])*10**(" kMB".index(s[-1])*3)):,}'or((i:=int(s.replace(',','')))>999>(j:=(i>1e6)+(i>1e9))and f'{(i/10**(j*3+2)+.5)//1/10}{"kMB"[j]}'or f'{i/1e3}k')for s in L]

Attempt This Online!

Similar to @TheThonnu's answer , but in a single line lambda and with sufficient differences that I feel it deserves a seperate answer

-4 bytes thanks to @97.100.97.109

\$\endgroup\$
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.