Parse a Quaternion

Retina, 115

\b[ijk]
1$&
^(?!.*\d([+-]|$))
0+
^(?!.*i)
+0i+
^(?!.*j)
0j+
^(?!.*k)
0k+
O$`[+-]*[\d.]*(\w?)
$1
-
+-
^\+

S`[ijk+]+

Try it online!

1 byte saved thanks to @Chris Jester-Young.

A bug fixed and 6 bytes saved thanks to @Martin Büttner

Found a couple bugs involving some edge cases, bumped up byte count quite a bit.

Returns the numbers newline separated. Anyway, this has a mostly elegant solution that sort of gets ruined by edge cases but hey I got to use sort mode, that means I used the right tool for the job, right?

Explanation:

Stage by stage, as usual.

\b[ijk]
1$&

The only characters in the input that can create word boundaries are -+.. This means that if we find a boundary followed by a letter, we have an implicit 1 which we add in with the replacement. $& is a synonym for $0.

^(?!.*\d([+-]|$))
0+

Big thanks to Martin for this one, this one adds in the implicit 0 for the real part if it was missing in the input. We make sure that we can't find a number that is followed by a plus or minus sign, or the end of the string. All the complex numbers will have a letter after them.

^(?!.*i)
+0i+

The next 3 stages are all pretty much the same, barring which letter they impact. All of them look to see if we can't match the letter, and if we can't we add a 0 term for it. The only reason i has an extra + before it is to prevent the real value from being unreadable with the is coefficient, the other numbers are all separated by their complex variable.

O$`[+-]*[\d.]*(\w?)
$1

Ah, the fun part. This uses the newish sort stage, denoted by the O before the option separator backtick. The trick here is to grab the whole number followed optionally by a word character, which in this case will only ever match one of ijk. The other option used is $ which causes the value used to sort these matches to be the replacement. Here we just use the optional letter left over as our sort value. Since Retina sorts lexicographically by default, the values are sorted like they would be in a dictionary, meaning we get the matches in "", "i", "j", "k" order.

-
+-

This stage puts a + sign in front of all the minus signs, this is needed if we have a negative value for i in the split stage, later.

^\+

We remove the leading + to make sure we have no extra leading newline.

S`[ijk+]+

Split the remaining lines on runs of the complex variables or the plus sign. This nicely gives us one value per line.


Pyth, 48 bytes

jm+Wg\-K--e|d0G\+K1+]-I#GJczfT.e*k<b\.zm/#dJ"ijk

Demonstration Test suite

The output format is newline separated. The test suite code uses space separation, for ease of reading, but is otherwise the same.

Outputs a -0 in the last 2 cases, which I hope is OK.

Explanation to follow.


Lua, 185 187 195 183 166 bytes (try it online) [used regex]

Thanks to @Chris Jester-Young for the improved regex.

Thanks to @Katenkyo for bringing it down to 166 bytes.

Golfed:

r={0,0,0,0}for u in(...):gsub("([+-])(%a)","%11%2"):gmatch("-?[%d.]+%a?")do n,i=u:match("(.+)(%a)")r[i and(" ijk"):find(i)or 1]=(n or u)end print(table.concat(r," "))

Ungolfed:

n = "42i+j-k+0.7"

result = {0,0,0,0}

for unit in n:gsub("([+-])(%a)","%11%2"):gmatch("-?[%d.]+%a?") do
  num, index = unit:match("(.+)(%a)")
  if index == "i" then
    result[2] = num
  elseif index == "j" then
    result[3] = num
  elseif index == "k" then
    result[4] = num
  else
    result[1] = unit
  end
end

print(table.concat(result," "))