8
\$\begingroup\$

Challenge

Given ASCII-encoded text input of any size, detect numbers between -1,000,000,000,000,000,000 and 1,000,000,000,000,000,000 exclusive and rewrite them with thousands separators. Use commas only and ignore leading zeroes. Leave the rest of the input unchanged.

Test cases

Input Output Description
123 123
1234 1,234
-12345 -12,345
x12345y x12,345y ignore surrounding characters
1234 xyz 56789 1,234 xyz 56,789
1234,56789 1,234,56,789 1,234 and 56,789 are separate numbers
0001234 0001,234 zeroes are not part of the number
10000000 10,000,000 only leading zeroes are ignored
1234567891234567890 1234567891234567890 1,234,567,891,234,567,890 is outside the range
0123456789123456789 0123,456,789,123,456,789

Standard loopholes apply. Shortest code in bytes wins.

\$\endgroup\$
5
  • 5
    \$\begingroup\$ I think handling the test cases 0001234 and 1234567891234567890 as exceptions makes this a less attractive challenge. \$\endgroup\$ Mar 13 at 17:22
  • \$\begingroup\$ Suggested test case: 123456789000123456789 -> 123456789000123456789 (solutions might conceivably parse it by mistake as 123456789 plus 000123456789) \$\endgroup\$
    – DLosc
    Mar 13 at 17:24
  • \$\begingroup\$ May input contain dots? May input already contain commas? What should the program do for these situations? \$\endgroup\$
    – tsh
    Mar 14 at 2:33
  • \$\begingroup\$ @tsh There is a test case with a comma in the input. \$\endgroup\$
    – noname
    Mar 14 at 3:09
  • 1
    \$\begingroup\$ Suggested test case: 0123456789123456789 -> 0123,456,789,123,456,789 (18 digits if the leading zero is disgarded). Some entries fail on this one. \$\endgroup\$
    – Jitse
    Mar 14 at 8:45

12 Answers 12

6
\$\begingroup\$

Perl 5 -p, 58 50 bytes

s%[1-9]\d*%$&<1E18?$&=~s/.\K(?=(...)+$)/,/gr:$&%ge

Try it online!

\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6), 66 bytes

-3 thanks to @tsh

s=>s.replace(/0|\d+/g,s=>s[18]?s:s.replace(/.(?=(...)+$)/g,"$&,"))

Try it online!

Commented

s =>                   // s = input string
s.replace(             // replace in s:
  /0|\d+/g,            //   we define a number as either a single zero
                       //   or a sequence of digits without leading zeros
  s =>                 //   for each such number s,
  s[18] ?              //   if it's longer than 18 characters:
    s                  //     leave it unchanged
  :                    //   else:
    s.replace(         //     replace in s:
      /.(?=(...)+$)/g, //       each digit followed by one or several groups of
                       //       3 digits covering exactly the end of the string
      "$&,"            //       with the same digit followed by a comma
    )                  //     end of replace()
)                      // end of replace()
\$\endgroup\$
1
  • 1
    \$\begingroup\$ maybe change the regex into /0|\d+/g \$\endgroup\$
    – tsh
    Mar 14 at 3:23
3
\$\begingroup\$

Retina 0.8.2, 52 bytes

r`(?<=[1-9]0*)\d{3}(?(?=\d)\G|(?<![1-9]\d{18,}))
,$&

Try it online! Link includes test cases. Explanation:

r`

Use right-to-left matching. In particular, this starts matching from the end of the string and works back to the start. This is important for the \G reference, which effectively now matches the start of the next match rather than the end of the previous match.

(?<=[1-9]0*)

Each set of three digits must be preceded by at least one nonzero digit.

\d{3}

Match three digits.

(?(?=\d)\G|(?<![1-9]\d{18,}))

If the next character is also a digit, then the matches need to be contiguous, otherwise there must not be a non-zero digit at least 18 digits away.

,$&

Precede the three digits with a comma.

\$\endgroup\$
3
\$\begingroup\$

Python 3, 88 85 bytes

-3 bytes thanks to movatica.

lambda s:re.sub('[1-9]\d*',lambda n:len(a:=n[0])<19and f'{int(a):,}'or a,s)
import re

Try it online!

Right tool for the job.

More regex-y solution, 93 92 bytes

-2 bytes thanks to ovs. Another one bytes the dust thanks to movatica.

lambda s:re.sub('(^0*|\D0*)([1-9]\d{,17})(?!\d)',lambda n:n[1]+f'{int(n[2]):,}',s)
import re

Try it online!

Tries to check for the length restriction within the regex, but I'm not very good at regex so it could probably be better. :P

\$\endgroup\$
5
  • 2
    \$\begingroup\$ Your second solution matches part of longer numbers when they contain a 0 (numbers like 1111111111111111111011111111111). I think it can be fixed with (^0*|\D0*) instead of (^|\D|0). \$\endgroup\$
    – ovs
    Mar 13 at 11:26
  • 2
    \$\begingroup\$ And a negative lookahead will save a bit: (?=\D|$) -> (?!\d) \$\endgroup\$
    – ovs
    Mar 13 at 11:27
  • 1
    \$\begingroup\$ You can omit the 0 as start of a range: \d{0,17} == \d{,17} \$\endgroup\$
    – movatica
    Mar 13 at 21:33
  • 1
    \$\begingroup\$ 85 bytes for the first solution by introducing the walruss operator \$\endgroup\$
    – movatica
    Mar 13 at 21:41
  • \$\begingroup\$ 84 bytes using a format trick like in Jitses Python 3 post \$\endgroup\$
    – movatica
    Mar 14 at 16:54
3
\$\begingroup\$

JavaScript (Node.js), 60 bytes

f=s=>s.replace(/0|(\d+)(\d{3})/g,(_,a,b)=>a<1e15?[f(a),b]:_)

Try it online!

f=s=>                 // Will recurse
s.replace(            // Replace
  /0|(\d+)(\d{3})/g,  // Leading zero treated as a single part
                      // which would also convert to 0
  (_,a,b)=>           // (matched, matched[:-3], matched[-3:]
    a<1e15?           // If matched < 1e18, then a < 1e15
                      // Also 1e15 is smaller than MAX_SAFE_INTEGER
                      // so no error would occur 
      [f(a),b]        // matched[-3:] is a group
    :                 // Notice that matched[:-3] also work as a part
      _               // If matched >= 1e18 or matched is undefined
                      // aka. matching a single 0,
)                     // then return as-is.

Looks portable to other languages.

\$\endgroup\$
3
\$\begingroup\$

Scala 3, 90 bytes

Thanks for @movatica for code improvement.

In golfed version, scala.util.matching.Regex is imported implicitly.


Golfed version. Attempt This Online!

s=>"[1-9]\\d*".r.replaceAllIn(s,m=>{val x=m.matched;if(x.size<19)f"${x.toLong}%,d"else x})

Ungolfed version. Attempt This Online!

import scala.util.matching.Regex

object Main {
  def main(args: Array[String]): Unit = {
    val tests = List(
      "123",
      "1234",
      "-12345",
      "x12345y",
      "1234 xyz 56789",
      "1234,56789",
      "0001234",
      "10000000",
      "1234567891234567890",
      "0123456789123456789"
    )

    tests.foreach(t => println(replaceNumbers(t)))
  }

  val replaceNumbers: (String => String) = s =>
    "[1-9]\\d*".r.replaceAllIn(
      s,
      m => if (m.matched.size < 19) f"${m.matched.toLong}%,d" else m.matched
    )
}
\$\endgroup\$
4
  • \$\begingroup\$ This seems to fail on 0123456789123456789 -> 0123,456,789,123,456,789 \$\endgroup\$
    – Jitse
    Mar 14 at 8:45
  • \$\begingroup\$ @Jitse Yes, you are right. Any correction would be greatly appreciated. \$\endgroup\$
    – 138 Aspen
    Mar 14 at 9:36
  • \$\begingroup\$ How about a port of the Python 3 answer: s=>"[1-9]\\d*".r.replaceAllIn(s,m=>if(m.matched.size<19)f"${m.matched.toLong}%,d"else m.matched) \$\endgroup\$
    – movatica
    Mar 14 at 17:11
  • \$\begingroup\$ Besides, I'm pretty sure, the import statement needs to be included in the bytecount \$\endgroup\$
    – movatica
    Mar 14 at 17:13
1
\$\begingroup\$

Uiua SBCS, 51 bytes

⍜⊜□⍚(⟨◌|/◇⊂↘1♭≡⊂@,⊜□⇌\+=0◿3⇡⟩<19.⧻.)↧\+≠@0⟜∊,+@0⇡10

Try it!

⍜⊜□⍚(⟨◌|/◇⊂↘1♭≡⊂@,⊜□⇌\+=0◿3⇡⟩<19.⧻.)↧\+≠@0⟜∊,+@0⇡10
                                             +@0⇡10  # the digits
                                           ∊,        # mask of locations
                                     \+≠@0⟜          # mask of non-leading zeros
                                    ↧                # combine
⍜⊜□⍚(                              )                 # map over mask groups
     ⟨◌|                    ⟩<19.⧻.                  # only process if < 19 digits
                     \+=0◿3⇡                         # mask of triplet groups
                    ⇌                                # reverse
                  ⊜□                                 # split
           ↘1♭≡⊂@,                                   # put comma between each triplet
        /◇⊂                                          # join
\$\endgroup\$
1
\$\begingroup\$

Pip, 31 bytes

aR`[1-9]\d+`{#a<19?R:Ra<>3J',a}

Attempt This Online!

Explanation

aR`[1-9]\d+`{#a<19?R:Ra<>3J',a}­⁡​‎‎⁡⁠⁡‏⁠‎⁡⁠⁢‏‏​⁡⁠⁡‌⁢​‎‎⁡⁠⁣‏⁠‎⁡⁠⁤‏⁠‎⁡⁠⁢⁡‏⁠‎⁡⁠⁢⁢‏⁠‎⁡⁠⁢⁣‏⁠‎⁡⁠⁢⁤‏⁠‎⁡⁠⁣⁡‏⁠‎⁡⁠⁣⁢‏⁠‎⁡⁠⁣⁣‏⁠‎⁡⁠⁣⁤‏‏​⁡⁠⁡‌⁣​‎‎⁡⁠⁤⁡‏⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠‎⁡⁠⁢⁤⁣‏‏​⁡⁠⁡‌⁤​‎‎⁡⁠⁤⁢‏⁠‎⁡⁠⁤⁣‏⁠‎⁡⁠⁤⁤‏⁠‎⁡⁠⁢⁡⁡‏⁠‎⁡⁠⁢⁡⁢‏⁠‎⁡⁠⁢⁡⁣‏‏​⁡⁠⁡‌⁢⁡​‎‎⁡⁠⁢⁢⁢‏⁠‎⁡⁠⁢⁢⁣‏‏​⁡⁠⁡‌⁢⁢​‎‎⁡⁠⁢⁢⁤‏⁠‎⁡⁠⁢⁣⁡‏⁠‎⁡⁠⁢⁣⁢‏‏​⁡⁠⁡‌⁢⁣​‎‎⁡⁠⁢⁣⁣‏⁠‎⁡⁠⁢⁣⁤‏⁠‎⁡⁠⁢⁤⁡‏‏​⁡⁠⁡‌⁢⁤​‎‎⁡⁠⁢⁡⁤‏⁠‎⁡⁠⁢⁢⁡‏‏​⁡⁠⁡‌⁣⁡​‎‎⁡⁠⁢⁤⁢‏‏​⁡⁠⁡‌­
aR                               ; ‎⁡In command-line argument, replace
  `[1-9]\d+`                     ; ‎⁢1-9 followed by more digits
            {                 }  ; ‎⁣with the following callback function:
             #a<19?              ; ‎⁤ If the length of the match is less than 19 digits:
                     Ra          ; ‎⁢⁡  Reverse the match
                       <>3       ; ‎⁢⁢  Group into substrings of length 3
                          J',    ; ‎⁢⁣  Join on comma
                   R:            ; ‎⁢⁤  Reverse back
                             a   ; ‎⁣⁡ Otherwise, return the match unchanged
💎

Created with the help of Luminespire.

The double-reversal is necessary because <> groups from the left; for example, 1234<>3 == [123;4].

\$\endgroup\$
1
\$\begingroup\$

Charcoal, 42 40 bytes

F⊞O⪪S¹ω¿№IΦχ∨υλι⊞υι«⮌⪫⪪⮌⪫υω³…,‹Lυ¹⁹≔⟦⟧υι

Try it online! Link is to verbose version of code. Explanation:

F⊞O⪪S¹ω

Split the input into characters, append the empty string to the list, then loop over them.

¿№IΦχ∨υλι

Test whether the current character is a digit from 0-9 or 1-9 depending on whether any digits have been collected yet.

⊞υι

If it is a collectable digit then collect it.

«

Otherwise:

⮌⪫⪪⮌⪫υω³…,‹Lυ¹⁹

Reverse the digits, regroup them into strings of three characters, join them with commas if there are fewer than 19, and reverse the result.

≔⟦⟧υ

Clear the list of digits.

ι

Output the current "non-digit" (could be 0 if we're not collecting digits yet).

\$\endgroup\$
1
\$\begingroup\$

Python 3.8 (pre-release), 112 bytes

f=lambda s,x=1:s and(s[':'>s>'1':1]or f'{int(s[:(x:=[*map(str.isdigit,s),0].index(0))]):{","[:x<19]}}')+f(s[x:])

Try it online!

Wrong tool for the job (but sans regex)

-15 thanks to totallyhuman

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Hah, that's funny. You can use the format specifier , to save some bytes. \$\endgroup\$ Mar 13 at 9:31
  • 1
    \$\begingroup\$ @totallyhuman I did not know that, thanks! \$\endgroup\$
    – Jitse
    Mar 13 at 9:32
1
\$\begingroup\$

Vyxal 3 d, 28 bytes

ᵛK¯0Jƒ“ƛ⌊[ᶤ⌊∦ΘİfƛL19<[Ṛ⁻',jṚ

Try it Online!

no regex needed

ᵛK¯0Jƒ“ƛ⌊[ᶤ⌊∦ΘİfƛL19<[Ṛ⁻',jṚ­⁡​‎‎⁡⁠⁡‏⁠‎⁡⁠⁢‏‏​⁡⁠⁡‌⁢​‎‎⁡⁠⁣‏⁠‎⁡⁠⁤‏⁠‎⁡⁠⁢⁡‏‏​⁡⁠⁡‌⁣​‎‎⁡⁠⁢⁢‏⁠‎⁡⁠⁢⁣‏‏​⁡⁠⁡‌⁤​‎‏​⁢⁠⁡‌⁢⁡​‎‎⁡⁠⁢⁤‏‏​⁡⁠⁡‌⁢⁢​‎⁠‎⁡⁠⁣⁡‏⁠‎⁡⁠⁣⁢‏‏​⁡⁠⁡‌⁢⁣​‎‎⁡⁠⁣⁣‏⁠‎⁡⁠⁣⁤‏⁠‎⁡⁠⁤⁡‏⁠‎⁡⁠⁤⁢‏⁠‎⁡⁠⁤⁣‏⁠‎⁡⁠⁤⁤‏‏​⁡⁠⁡‌⁢⁤​‎⁠‎⁡⁠⁢⁡⁡‏⁠⁠⁠⁠⁠‏​⁡⁠⁡‌⁣⁡​‎‎⁡⁠⁢⁡⁢‏⁠‎⁡⁠⁢⁡⁣‏⁠‎⁡⁠⁢⁡⁤‏⁠‎⁡⁠⁢⁢⁡‏⁠‎⁡⁠⁢⁢⁢‏‏​⁡⁠⁡‌⁣⁢​‎‎⁡⁠⁢⁢⁣‏⁠‎⁡⁠⁢⁢⁤‏⁠⁠⁠⁠‏​⁡⁠⁡‌⁣⁣​‎‎⁡⁠⁢⁣⁡‏⁠‎⁡⁠⁢⁣⁢‏⁠‎⁡⁠⁢⁣⁣‏⁠‎⁡⁠⁢⁣⁤‏‏​⁡⁠⁡‌⁣⁤​‎‏​⁢⁠⁡‌­
ᵛK                            # ‎⁡is each character numeric
  ¯0J                         # ‎⁢pairwise differences with a zero appended
     ƒ“                       # ‎⁣split after truthy indices and concatenate sublists
# ‎⁤This splits the string on numbers
       ƛ                      # ‎⁢⁡map over each group
        ⌊[                    # ‎⁢⁢if it is a non-zero number
          ᶤ⌊∦Θİf              # ‎⁢⁣slice at the first non-zero index
                ƛ             # ‎⁢⁤map over each new group
                 L19<[        # ‎⁣⁡if the length is less than 19
                      Ṛ⁻      # ‎⁣⁢reverse and split into triplets
                        ',jṚ  # ‎⁣⁣join by commas and reverse
# ‎⁣⁤d flag concatenates until no sublists remain
💎

Created with the help of Luminespire.

Vyxal d, 25 bytes

⁽±Ḋƛ±[⁽⌊Ǒ₍ẎȯƛL19<[Ṙ3ẇ\,jṘ

Try it Online! vyxal 2 port

\$\endgroup\$
2
  • \$\begingroup\$ Seems to fail for the test case 0001234. \$\endgroup\$
    – Neil
    Mar 13 at 19:58
  • \$\begingroup\$ didnt reply, but its fixed @Neil \$\endgroup\$
    – pacman256
    Mar 14 at 5:09
1
\$\begingroup\$

05AB1E, 30 bytes

.γd}εDdiïDg19‹iR3ô',ýRyγн0Ãì]J

Try it online.

Explanation:

.γ                     # Group the characters of the (implicit) input-string by:
  d                    #  Check whether its a non-negative number (aka a digit)
   }ε                  # After the group-by: Map over each group:
     D                 #  Duplicate it
      di               #  If this is a group of digits:
        ï              #   Remove leading 0s by casting it to an integer
         D             #   Duplicate it
          g19‹i        #   If its length is smaller than 19:
               R       #    Reverse it
                3ô     #    Split it into triplets
                  ',ý '#    Join it with "," delimiter
                     R #    Reverse it back
               y       #    Push the current group's string of digits
                γ      #    Split it into equal adjacent groups
                 н     #    Leave only the first group
                  0Ã   #    Leave only the 0s in this group
                    ì  #    Prepend this string (either "" or 0s) to the string
                       #   (implicit) else: use the duplicated integer that's too large
                       #  (implicit) else: use the duplicated non-digit group
    ]                  # Close both the nested if-statements and map
     J                 # Join all groups back together
                       # (after which the result is output implicitly)
\$\endgroup\$

Your Answer

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

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