22
\$\begingroup\$

To mark the anniversary of World IPv6 day, the Internet Society has published a campaign to Turn Off IPv4 on 6 June 2014 for One Day.


IPv6 addresses may be represented in their long form as eight colon-separated 16-bit hex values. Depending on the address, they may also be shortened as described in item 2 of section 2.2 Text Representation of Addresses of RFC 3513:

In order to make writing addresses containing zero bits easier a special syntax is available to compress the zeros. The use of "::" indicates one or more groups of 16 bits of zeros. The "::" can only appear once in an address. The "::" can also be used to compress leading or trailing zeros in an address.

  • Entries to this challenge will be programs that accept exactly one IPv6 address formatted in either the long or shortened format, and will display the same address in both the long and short formats, in that order.

  • The input may come from command-line arguments, STDIN, or any other input source that suits your choice of language.

  • Libraries or utilities specifically for parsing IPv6 addresses are banned (e.g. inet_{ntop,pton}()).

  • If the input address is invalid, the output will be empty (or some suitable error message indicating the address is invalid is given)

  • In cases where :: shortening occurs, only one shortening operation may happen for a given address. If there are more than one potential shortening operations for a given address, the operation that gives the overall shortest address must be used. If there is a tie in this regard, the first operation will be used. This is illustrated in the examples below.

  • Standard loopholes to be avoided

Examples:

Input                         Output

1080:0:0:0:8:800:200C:417A    1080:0:0:0:8:800:200C:417A
                              1080::8:800:200C:417A

FF01::101                     FF01:0:0:0:0:0:0:101
                              FF01::101

0:0:0:0:0:0:0:1               0:0:0:0:0:0:0:1
                              ::1

::                            0:0:0:0:0:0:0:0
                              ::

1:0:0:2:0:0:0:3               1:0:0:2:0:0:0:3
                              1:0:0:2::3

1:0:0:8:8:0:0:3               1:0:0:8:8:0:0:3
                              1::8:8:0:0:3

1:2:3:4:5:6:7:8               1:2:3:4:5:6:7:8
                              1:2:3:4:5:6:7:8

ABCD:1234                     <Invalid address format - no output>

ABCDE::1234                   <Invalid address format - no output>

1:2:3:4:5:6:7:8:9             <Invalid address format - no output>

:::1                          <Invalid address format - no output>

codegolf puzzle               <Invalid address format - no output>

This is , so the shortest answer in bytes on June 6th 2014 will be accepted as the winner.

\$\endgroup\$
  • \$\begingroup\$ Say the input is 1:0:0:2:2::3. Would the shortened output be identical to that or 1::2:2:0:0:3? Same for non-optimally shortened input. \$\endgroup\$ – Martin Ender May 27 '14 at 20:49
  • \$\begingroup\$ @m.buettner In this case, I'll let you pick either. \$\endgroup\$ – Digital Trauma May 27 '14 at 20:52
  • \$\begingroup\$ Is 1::2:0:0:0:3 a possible input? \$\endgroup\$ – ace May 27 '14 at 20:59
  • \$\begingroup\$ @ace In accordance with the late great Jon Postel's Robustness principle, yes. \$\endgroup\$ – Digital Trauma May 27 '14 at 21:03
  • 2
    \$\begingroup\$ I think this is the only way anyone's ever going to get me to learn ipv6. +1 \$\endgroup\$ – Obversity May 27 '14 at 22:30
4
\$\begingroup\$

JavaScript (ES6) - 198, 183, 180, 188, 187 bytes

f=s=>/^(:[\da-f]{1,4}){8}$/i.test(':'+(s=s[r='replace'](d='::',':0'.repeat((n=8-s.split(/:+/).length%9)||1)+':')[r](/^:0|0:$/g,n?'0:0':0)))&&[s,s[r](/(\b0(:0)*)(?!.*\1:0)/,d)[r](/::+/,d)]

And, a bit longer, interactive version with some pop-ups (203 bytes):

/^(:[\da-f]{1,4}){8}$/i.test(':'+(s=(s=prompt())[r='replace'](d='::',':0'.repeat((n=8-s.split(/:+/).length%9)||1)+':')[r](/^:0|0:$/g,n?'0:0':0)))&&alert(s+'\n'+s[r](/(\b0(:0)*)(?!.*\1:0)/,d)[r](/::+/,d))

Ungolfed:

function ipv6(str) {
    "use strict";
    var zeros = 8 - str.split(/:+/).length % 9

        ,longIP = str
            .replace('::', ':0'.repeat(zeros || 1) + ':')
            .replace(/^:0|0:$/g, zeros ? '0:0' : '0')

        ,shortIP = longIP
            .replace(/(\b0(:0)*)(?!.*\1:0)/,':')
            .replace(/::+/,'::');

    return /^(:[\da-f]{1,4}){8}$/i.test(':'+longIP) && [longIP, shortIP];
}

Explanation:

To calculate the long version of the IPv6 address:

8 - str.split(/:+/).length % 9 - calculate how many zeros we need to insert. They are 8 - the number of the hex values. Here % 9 is a guard so it will never be a negative number.

replace('::', ':0'.repeat(zeros || 1) + ':') - replace the "::" with colon separated zeros. If there are no zeros to add it still adds one so the address won't be valid in the end

replace(/^:0|0:$/g, zeros ? '0:0' : '0') - this deals with the special case when the address starts or ends with "::" as the split function adds 1 to the number of hex values (::1 -> ["", "1"])

That's it! Now let's calculate the short form:

replace(/(\b0(:0)*)(?!.*\1:0)/,':') - replace longest row of zeros with colon(s) (It doesn't matter how many).

replace(/::+/,'::') - remove the extra colons if any

return /^(:[\da-f]{1,4}){8}$/i.test(':'+longIP) && [longIP, shortIP]; - test if the long version is valid IPv6 and return both versions or false if the test fails.

Tests in Firefox:

>>> f('1080:0:0:0:8:800:200C:417A')
["1080:0:0:0:8:800:200C:417A", "1080::8:800:200C:417A"]
>>> f('FF01::101')
["FF01:0:0:0:0:0:0:101", "FF01::101"]
>>> f('0:0:0:0:0:0:0:1')
["0:0:0:0:0:0:0:1", "::1"]
>>> f('::')
["0:0:0:0:0:0:0:0", "::"]
>>> f('1:0:0:2:0:0:0:3')
["1:0:0:2:0:0:0:3", "1:0:0:2::3"]
>>> f('1:0:0:8:8:0:0:3')
["1:0:0:8:8:0:0:3", "1::8:8:0:0:3"]
>>> f('1:2:3:4:5:6:7:8')
["1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8"]
>>> f('ABCD:1234')
false
>>> f('ABCDE::1234')
false
>>> f('1:2:3:4:5:6:7:8:9')
false
>>> f(':::1')
false
>>> f('1:2:3:4::a:b:c:d')
false
>>> f('codegolf puzzle')
false
\$\endgroup\$
  • \$\begingroup\$ So much better than mine! Just needs some correction to handle input like this ::1:, :1:: \$\endgroup\$ – edc65 May 29 '14 at 21:44
  • \$\begingroup\$ This one accepted the invalid 1:2:3:4::a:b:c:d \$\endgroup\$ – kernigh Jun 2 '14 at 21:05
6
\$\begingroup\$

Javascript (E6) 246 305 284 292 319

Heavily revised Special case for :: specifically handled, compress phase avoids the for loop (but not very shorter indeed) I'm sure that the final compress phase can be made shorter. Not now anyway

F=i=>(c=':',d=c+c,z=':0'.repeat(9-i.split(c,9).length)+c,i=i==d?0+z+0:i[R='replace'](/^::/,0+z)[R](/::$/,z+0)[R](d,z>c?z:d),/^(:[\da-f]{1,4}){8}:$/i.test(k=c+i+c)&&[i,k[R]((k.match(/:(0:)+/g)||[]).sort().pop(),d)[R](/^:([^:])|([^:]):$/g,'$1$2')])

Thanks to nderscore

As a program

Input and output using js popup, basically: p=prompt,p(F(p())) Rewriting with popup and without the function definition, the char count should be under 260

Ungolfed and commented a bit

F = i => (
  c = ':',
  d = c+c,
  z = ':0'.repeat(9-i.split(c,9).length) + c, 
  i = i == d ? 0+z+0 /* special case '::' */
    : i.replace(/^::/,0+z) /* special case '::...' */
       .replace(/::$/,z+0) /* special case '...::' */
       .replace(d, z > c ? z : d), /* here, if z==c, not valid: too much colons */
  /^(:[\da-f]{1,4}){8}:$/i.test(k = c+i+c) /* Check if valid */
  && [
   i, 
   k.replace((k.match(/:(0:)+/g)||[]).sort().pop(),d) /* find longest 0: sequence and replace it */
    .replace(/^:([^:])|([^:]):$/g,'$1$2') /* cut leading and trailing colons */
  ]
)

Test In console

i=['1080:0:0:0:8:800:200C:417A'
, '::1:2:3:4:5:6:7', '1:2:3:4:5:6:7::'
, '1:2:3:4::5:6:7:8'
, ':1:2:3:4:5:6:7', '1:2:3:4:5:6:7:'
, 'FF01::101', '0:0:0:0:0:0:0:1'
, '::', '1::', '::1', ':::1', '1:::'
, '1:0:0:2:0:0:0:3', '1:0:0:0:2:0:0:3', '1::8:0:0:0:3'
, '1:2:3:4:5:6:7:8'
, 'ABCD:1234', 'ABCDE::1234', ':::', '::::::::::'
, '1:2:3:4:5:6:7:8:9', '::::1', 'codegolf puzzle'];
i.map(x=>x+' => '+F(x)).join('\n')

Test output

"1080:0:0:0:8:800:200C:417A => 1080:0:0:0:8:800:200C:417A,1080::8:800:200C:417A
::1:2:3:4:5:6:7 => 0:1:2:3:4:5:6:7,::1:2:3:4:5:6:7
1:2:3:4:5:6:7:: => 1:2:3:4:5:6:7:0,1:2:3:4:5:6:7::
1:2:3:4::5:6:7:8 => false
:1:2:3:4:5:6:7 => false
1:2:3:4:5:6:7: => false
FF01::101 => FF01:0:0:0:0:0:0:101,FF01::101
0:0:0:0:0:0:0:1 => 0:0:0:0:0:0:0:1,::1
:: => 0:0:0:0:0:0:0:0,::
1:: => 1:0:0:0:0:0:0:0,1::
::1 => 0:0:0:0:0:0:0:1,::1
:::1 => false
1::: => false
1:0:0:2:0:0:0:3 => 1:0:0:2:0:0:0:3,1:0:0:2::3
1:0:0:0:2:0:0:3 => 1:0:0:0:2:0:0:3,1::2:0:0:3
1::8:0:0:0:3 => 1:0:0:8:0:0:0:3,1:0:0:8::3
1:2:3:4:5:6:7:8 => 1:2:3:4:5:6:7:8,1:2:3:4:5:6:7:8
ABCD:1234 => false
ABCDE::1234 => false
::: => false
:::::::::: => false
1:2:3:4:5:6:7:8:9 => false
::::1 => false
codegolf puzzle => false"   
\$\endgroup\$
  • \$\begingroup\$ I'd rather a program than a function. I don't know javascript well enough to know if this is possible. \$\endgroup\$ – Digital Trauma May 28 '14 at 17:29
  • \$\begingroup\$ @nderscore Oops - typo. Corrected in a new comment. \$\endgroup\$ – Digital Trauma May 28 '14 at 17:30
  • \$\begingroup\$ It could be made into a program by taking input from prompt(). Here are some optimizations that bring it down to 290: pastie.org/private/3ccpinzqrvvliu9nkccyg \$\endgroup\$ – nderscore May 28 '14 at 18:23
  • \$\begingroup\$ @nderscore: thx, the 1st replace does not work for input = '::', great work anyway! \$\endgroup\$ – edc65 May 28 '14 at 22:10
  • \$\begingroup\$ @edc65 I found a fix for that replace :) pastie.org/private/kee0sdvjez0vfcmlvaxu8q \$\endgroup\$ – nderscore May 29 '14 at 17:56
4
\$\begingroup\$

Perl - 204 176 190 191 197

(202 chars + 2 for -p flag)

$_=uc;(9-split/:/)||/^:|:$/||last;s/^::/0::/;s/::$/::0/;s|::|':0'x(9-split/:/).':'|e;/::|^:|:$|\w{5}|[^A-F0-:].*\n/||(8-split/:/)and last;s/\b0*(?!\b)//g;print;s/\b((0:)*0)\b(?!.*\1:0\b)/::/;s/::::?/::/

Example:

$ perl -p ipv6.pl <<< 1:0:2:0::3
1:0:2:0:0:0:0:3
1:0:2::3
$ perl -p ipv6.pl <<< somethinginvalid
$ perl -p ipv6.pl <<< 1:2:0:4:0:6::8
1:2:0:4:0:6:0:8
1:2::4:0:6:0:8

Explanation:

# -p reads a line from stdin and stores in $_
#
# Convert to uppercase
$_ = uc;

# Detect the annoying case @kernigh pointed out
(9 - split /:/) || /^:|:$/ || last;

# Fix :: hanging on the beginning or the end of the string
s/^::/0::/;
s/::$/::0/;

# Replace :: with the appropriate number of 0 groups
s|::|':0' x (9 - split /:/) . ':'|e;

# Silently exit if found an extra ::, a hanging :, a 5-char group, an invalid
# character, or there's not 8 groups
/::|^:|:$|\w{5}|[^A-F0-:].*\n/ || (8 - split /:/) and last;

# Remove leading zeros from groups
s/\b0*(?!\b)//g;

# Output the ungolfed form
print;

# Find the longest sequence of 0 groups (a sequence not followed by something
# and a longer sequence) and replace with ::
# This doesn't replace the colons around the sequence because those are optional
# thus we are left with 4 or 3 colons in a row
s/\b((0:)*0)\b(?!.*\1:0\b)/::/;

# Fix the colons after previous transformation
s/::::?/::/

# -p then prints the golfed form of the address
\$\endgroup\$
  • 1
    \$\begingroup\$ "Died at ipv6.pl line 1, <> line 1". This was asked about in the question comments. If there is a message, it must be clear that it is because of an invalid message. I tried to clarify that in the question. Otherwise looks good! \$\endgroup\$ – Digital Trauma May 28 '14 at 15:05
  • 1
    \$\begingroup\$ @DigitalTrauma Changed die to a silent exit. \$\endgroup\$ – mniip May 28 '14 at 15:23
  • 1
    \$\begingroup\$ A bug? This program accepts the invalid address 1:2:3:4::a:b:c:d. This is an annoying special case, because most eight-colon addresses are invalid, but ::2:3:4:a:b:c:d and 1:2:3:4:a:b:c:: are both valid. \$\endgroup\$ – kernigh Jun 2 '14 at 20:04
3
\$\begingroup\$

sed, 276

I have 275 bytes in ipshorten.sed, plus 1 byte for the -r switch in sed -rf to use extended regular expressions. I used OpenBSD sed(1).

Usage: echo ::2:3:4:a:b:c:d | sed -rf ipshorten.sed

s/^/:/
/^(:[0-9A-Fa-f]{0,4})*$/!d
s/:0*([^:])/:\1/g
s/://
s/::/:=/
s/(.:=)(.)/\10:\2/
s/^:=/0&/
s/=$/&0/
:E
/(.*:){7}/!{/=/!d
s//=0:/
bE
}
s/=//
/^:|::|:$|(.*:){8}/d
p
s/.*/:&:/
s/:((0:)+)/:<\1>/g
:C
s/0:>/>0:/g
/<0/{s/<>//g
bC
}
s/<>(0:)+/:/
s/<>//g
/^::/!s/://
/::$/!s/:$//

I use 22 regular expressions, as sed can't compare numbers or make arrays. For each line of input, sed runs the commands and prints the line. During testing, I put several lines of alleged IP addresses in a file, and fed this file to sed. A reference to extended regular expressions is in re_format(7).

  1. s/^/:/ adds an extra colon to the beginning of the line. I use this extra colon to golf the next two commands.
  2. /^(:[0-9A-Fa-f]{0,4})*$/!d checks if the whole line matches zero or more groups of colons followed by zero to four hexadecimal digits. ! negates the check, so d deletes lines with too big hexadecimal numbers or with invalid characters. When d deletes a line, sed runs no more commands on this line.
  3. s/:0*([^:])/:\1/g deletes leading 0s from each number. It would change :0000:0000: to :0:0:. I must do this because my contraction loop only works with single-digit 0s.
  4. s/:// deletes the extra colon. It deletes only the first colon.
  5. s/::/:=/ changes the first :: to :=. This is so later commands can match = rather than ::, and so = does not count as a colon. If there is no ::, this substitution safely does nothing.
    • Now :: must make at least one 0, but there are three different cases for placing this 0.
  6. s/(.:=)(.)/\10:\2/ is the first case. If :: was between two other characters, then := becomes :=0:. This is the only case that adds a colon.
  7. s/^:=/0&/ is the second case. If :: was at beginning of line, then put 0 there.
  8. s/=$/&0/ is the third case, for :: at end of line.
  9. :E is the label for the expansion loop.
  10. /(.*:){7}/!{/=/!d begins a conditional block if the line has fewer than 7 colons. /=/!d deletes lines that had no :: and not enough colons.
  11. s//=0:/ adds one colon. Empty // repeats the last regular expression, so this is really s/=/=0:/.
  12. bE branches to :E to continue the loop.
  13. } closes the block. Now the line has at least seven colons.
  14. s/=// deletes =.
  15. /^:|::|:$|(.*:){8}/d is a final check after expansion. It deletes lines with a leading colon, an extra :: that was not expanded, a trailing colon, or eight or more colons.
  16. p prints the line, which is an IP address in long form.
  17. s/.*/:&:/ wraps the address in extra colons.
    • The next task is to find the longest group of 0s, like :0:0:0:, and contract it into ::.
  18. s/:((0:)+)/:<\1>/g eats each group of 0s, so :0:0:0: would become :<0:0:0:>.
  19. :C is the label for the contraction loop.
  20. s/0:>/>0:/g moves one 0 from each mouth, so :<0:0:0:> would become :<0:0:>0:.
  21. /<0/{s/<>//g opens a conditional block if any mouth is not empty. s/<>//g deletes all empty mouths, because those groups are too short.
  22. bC continues the contraction loop.
  23. } closes the block. Now any mouth is empty and marks the longest group of 0s.
  24. s/<>(0:)+/:/ contracts the longest group, so :<>0:0:0: would become ::. In a tie, it picks the empty mouth on the left.
  25. s/<>//g deletes any other empty mouths.
  26. /^::/!s/:// deletes the first extra colon unless it is part of ::.
  27. /::$/!s/:$// does so for the last extra colon. Then sed prints the IP address in short form.
\$\endgroup\$
1
\$\begingroup\$

Python 3: 387 characters

Even works with improperly shortened input.

$ echo '1::2:0:0:0:3' | python3 ipv6.py 
1:0:0:2:0:0:0:3
1:0:0:2::3

The double replace of ':::' with '::' feels really bad but not sure how to cleanly deal with the longest string of 0's when it abuts one or both ends.

c=':'
p=print
try:
 B=[int(x,16)if x else''for x in input().split(c)];L=len(B)
 if any(B)-1:B=[''];L=1
 if L!=8:s=B.index('');B[s:s+1]=[0]*(9-L)
 for b in B:assert-1<b<2**16
 H=[format(x,'X')for x in B];o=c.join(H);p(o);n=''.join(str(h=='0')[0]for h in H)
 for i in range(8,0,-1):
  s=n.find('T'*i)
  if s>=0:H[s:s+i]=[c*2];p(c.join(H).replace(c*3,c*2).replace(c*3,c*2));q
 p(o)
except:0

Replace the final pass with raise to see how it's crashing protecting against malformed input.

$ cat ipv6-test.sh 
echo '1080:0:0:0:8:800:200C:417A' | python3 ipv6.py
echo '1:2:3:4:5:6:7:8' | python3 ipv6.py
echo 'FF01::101' | python3 ipv6.py
echo '0:0:0:0:0:0:0:1' | python3 ipv6.py
echo '0:0:0:0:1:0:0:0' | python3 ipv6.py
echo '1:0:0:0:0:0:0:0' | python3 ipv6.py
echo '::' | python3 ipv6.py
echo '1:0:0:2:0:0:0:3' | python3 ipv6.py
echo '1::2:0:0:0:3' | python3 ipv6.py
echo '1:0:0:8:8:0:0:3' | python3 ipv6.py
echo 'ABCD:1234' | python3 ipv6.py
echo 'ABCDE::1234' | python3 ipv6.py
echo '1:2:3:4:5:6:7:8:9' | python3 ipv6.py
echo ':::1' | python3 ipv6.py
echo 'codegolf puzzle' | python3 ipv6.py
$ ./ipv6-test.sh 
1080:0:0:0:8:800:200C:417A
1080::8:800:200C:417A

1:2:3:4:5:6:7:8
1:2:3:4:5:6:7:8

FF01:0:0:0:0:0:0:101
FF01::101

0:0:0:0:0:0:0:1
::1

0:0:0:0:1:0:0:0
::1:0:0:0

1:0:0:0:0:0:0:0
1::


0:0:0:0:0:0:0:0
::

1:0:0:2:0:0:0:3
1:0:0:2::3

1:0:0:2:0:0:0:3
1:0:0:2::3

1:0:0:8:8:0:0:3
1::8:8:0:0:3
\$\endgroup\$
  • \$\begingroup\$ @DigitalTrauma corrected. I was searching for "0:0:0..." and it was capturing trailing 0's \$\endgroup\$ – Nick T May 28 '14 at 5:52
  • \$\begingroup\$ You really don't hear the word "abuts" enough nowadays \$\endgroup\$ – Claudiu May 28 '14 at 23:25
  • \$\begingroup\$ A bug? This program accepted 1:2:3:4::a:b:c:d but rejected both ::2:3:4:a:b:c:d and 1:2:3:4:a:b:c::. I believe it was wrong all three times. \$\endgroup\$ – kernigh Jun 2 '14 at 20:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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