45
\$\begingroup\$

Related: Multiply Quaternions

Challenge

Given a string made of ijk, interpret it as the product of imaginary units of quaternion and simplify it into one of the eight possible values 1, -1, i, -i, j, -j, k, -k.

The evaluation rules are as follows:

$$ ii = jj = kk = -1 \\ ij = k, \quad jk = i, \quad ki = j \\ ji = -k, \quad kj = -i, \quad ik = -j $$

The multiplication of quaternions is associative but not commutative. This means that you may do the simplification in any order, but you cannot reorder the items.

For the I/O format, function parameter and return from a function should be done as a string (list of chars or list of codepoints is also OK). You may assume the input is not empty. You may output any amount of leading and trailing whitespace (spaces, tabs, newlines).

Test cases

i -> i
j -> j
k -> k
ij -> k
ji -> -k
kk -> -1
ijkj -> -j
iikkkkkjj -> k
kikjkikjk -> -k
kjijkjikijkjiki -> 1
jikjjikjikjikjjjj -> j
\$\endgroup\$
4
  • \$\begingroup\$ Must the input be taken as a string or may it be a list? \$\endgroup\$
    – DanTheMan
    May 26, 2021 at 1:20
  • 1
    \$\begingroup\$ @DanTheMan "list of chars or list of codepoints is also OK." \$\endgroup\$
    – Bubbler
    May 26, 2021 at 1:21
  • 2
    \$\begingroup\$ Since quaternion multiplication is not commutative, I think it would be helpful to clarify in the question text that (-1)i = i(-1) = -i, and similarly for j and k. \$\endgroup\$
    – DLosc
    May 27, 2021 at 18:18
  • \$\begingroup\$ @Bubbler I thought about posting this exact challenge around a year ago, but I saw the linked challenge and viewed it as a dupe. :( \$\endgroup\$ May 27, 2021 at 19:57

27 Answers 27

21
\$\begingroup\$

Charcoal, 32 29 27 bytes

≔⁰θFS≔⁺×θX³℅ι℅ιθ‹³﹪θ⁸§1ijkθ

Try it online! Link is to verbose version of code. Explanation: The values are encoded to integers equivalent to 0..7 (modulo 8) in the order 1, i, j, k, -1, -i, -j, -k. The multiplications by i, j and k have the following effect on the integer:

  • Multiplying by i is equivalent to tripling the integer and adding 1.
  • Multiplying by j is equivalent to adding 2 to the integer.
  • Multiplying by k is equivalent to tripling the integer and adding 3.

Edit: Thanks to @NickKennedy for saving 3 bytes by pointing out that the ordinals of i, j and k are equivalent (mod 8) to 1, 2 and 3 respectively. This means that the integer needs to be tripled if the ordinal is odd, and then the ordinal can be added to it.

Edit: Furthermore, tripling the integer (mod 8) if the ordinal is odd is equivalent to multiplying the integer by 3 to the power of the ordinal, for a further 2 byte saving.

≔⁰θ

Start with 0.

FS

Loop over the input...

≔⁺×θX³℅ι℅ιθ

... multiply the current value by 3 to the power of the ordinal, then add the ordinal.

‹³﹪θ⁸

Output a - sign if the result (mod 8) is greater than 3. (There are other ways to express this in Charcoal but sadly I couldn't do better than 5 bytes.)

§1ijkθ

Output 1, i, j, or k, depending on the result (mod 4).

\$\endgroup\$
5
  • \$\begingroup\$ I think you can save three bytes: tio.run/… This works because the code point for i is 1 mod 8. \$\endgroup\$ May 27, 2021 at 0:30
  • 1
    \$\begingroup\$ @NickKennedy That's a great find! I have to admit I hadn't even twigged the mod 4 relationship, which would already have saved me a byte. \$\endgroup\$
    – Neil
    May 27, 2021 at 9:08
  • 1
    \$\begingroup\$ Very nifty! I wonder if there's a theoretical reason you can represent the quaternions this way. It looks like the invertible affine functions modulo 8, which have form \$f(x) = a x + b\$ with \$a\$ odd form a 32-element group under composition. And the ones you list generate an 8-element subgroup isomorphic to the quaternions, restricted to \$a\$ being \$1\$ or \$3\$, and \$b\$ being even when \$a\$ is \$1\$ and odd otherwise. \$\endgroup\$
    – xnor
    May 28, 2021 at 1:11
  • \$\begingroup\$ @Neil I forgot to ask you at the time, did you find this just by playing around, via some higher-level insight, or by some more exhaustive method of search? \$\endgroup\$
    – Jonah
    Jun 2, 2021 at 1:12
  • \$\begingroup\$ @Jonah Since there are 8 outputs, I wanted to number the outputs so that I could at the least use a look-up table to multiply. I don't know why I first tried with the order 1ijk-1-i-j-k, but when the look-up table for x=>xj luckily turned out to be 23456701, some playing around quickly turned up my original formulas for i and k. \$\endgroup\$
    – Neil
    Jun 2, 2021 at 9:37
11
\$\begingroup\$

sed -r 4.2.2, 71

Using @Neil's Retina 0.8.2 approach:

:
s/k/ij/
s/ii|jj/-/
s/ji/-ij/
s/(\w)-/-\1/
s/--//
t
s/ij/k/
s/^-?$/&1/

Try it online!


Previous answer:

sed -r 4.2.2, 97

Saved 3 bytes thanks to @FryAmTheEggman.

:
s/(\w)\1/-/
s/(\w)-/-\1/
s/--//
s/ij/k/
s/jk/i/
s/ki/j/
s/ji/-k/
s/kj/-i/
s/ik/-j/
t
s/^-?$/&1/

Try it online!

\$\endgroup\$
0
7
\$\begingroup\$

JavaScript (Node.js), 82 bytes

e=>e.map(q=>p=+p?q:o[i=o.search(p+q),s^=i<5,i+2],p=s=1,o=' 1jikjijkij')&&'- '[s]+p

Try it online!

Input array of chars, output string.


JavaScript (Node.js), 66 bytes

e=>e.map(q=>p=p<99?q:(s^=(~p+q)%3&&36,p^=q)?104|p:49,p=s=9)&&[s,p]

Try it online!

Input array of codepoints, output array of codepoints.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Wow, you had basically the same idea as I did. \$\endgroup\$
    – EasyasPi
    May 26, 2021 at 3:08
7
\$\begingroup\$

Retina, 43 bytes

3{`\bii
-
ij
k
\bik
-j
}T`ijk`jki
--

\B$
1

Try it online! Link includes test cases. Explanation:

3{`

Loop three times.

\bii
-
ij
k
\bik
-j

Perform some simplifications.

T`ijk`jki

Rotate by 120° along the i=j=k axis.

}`

Repeat the above loop until the output converges. (Since the inner loop iterates three times, the rotations will cancel out.)

--

\B$
1

Tidy up the result.

\$\endgroup\$
6
\$\begingroup\$

JavaScript (ES6),  60 55 53  52 bytes

Expects an array of ASCII codes. Returns a string.

This is based on @Neil's excellent insight.

a=>["-"[a.map(y=>x+=y%2*2*x+y,x=4),x&4]]+"1ijk"[x&3]

Try it online!

How?

According to Neil:

at each step if the input is i or k then the integer is tripled and incremented, and if the input is j or k then 2 is added to it

In JS at least, it is shorter to express the above as follows:

$$x \gets x + (y \bmod 2)\times 2\times x+(y \bmod 4)$$

where \$x\$ is the sum and \$y\$ is the ASCII code of the next input character.

@NickKennedy noticed that it may actually be optimized one step further (thanks to Neil as well for pointing that out):

$$x \gets x + (y \bmod 2)\times 2\times x+y$$

We end up with the following encoding for \$x\$:

… x x 2 1 0
\___/ | \_/
  |   |  |
  |   |  +---> 00 = 1, 01 = i, 10 = j, 11 = k
  |   +------> sign bit, set for positive
  |            (which is why we start with x = 4)
  +----------> upper bits are ignored
\$\endgroup\$
1
  • 1
    \$\begingroup\$ As @NickKennedy points out, y&4==0, so you don't need to reduce y mod 4, assuming the intermediate value doesn't exceed the maximum save integer. \$\endgroup\$
    – Neil
    May 27, 2021 at 9:22
5
\$\begingroup\$

Ruby, 72 67 66 64 bytes

->s{w=k=0;s.bytes{|x|k>0&&w^=13[k+2-x&3];k^=x&3};?-*w+"1ijk"[k]}

Try it online!

Thanks dingledooper for -2 bytes.

\$\endgroup\$
2
  • \$\begingroup\$ Very impressive. Congrats. \$\endgroup\$ May 26, 2021 at 17:12
  • 1
    \$\begingroup\$ -2 bytes \$\endgroup\$ May 26, 2021 at 19:56
5
\$\begingroup\$

05AB1E, 40 37 bytes

ΔJ2ôεDg<iËi¼õë…ijkyмyC3%<½]J1«н¾É'-×ì

Try it online! or Try all cases!

Δ                # until the output doesn't change:
 J2ô             #   split the string into groups of 2
                 #   (last one might be a single character)
 ε               #   for each group y:
  Dg<i           #     if the length of y is 2:
      Ëi         #       if all characters are the same:
        ¼õ       #         increment the '-'-counter and return empty string
       ë         #       else:
        …ijkyм   #         remove the two characters from "ijk"
        yC3%<½   #         increment the '-'-counter if y converted from binary ...
                 #         ... is equal to 2 modulo 3
]                # close all loops and if statements
J1«              # append a 1
   н             # take the first character
    ¾É'-×ì       # if the '-'-counter is odd, prepend a '-'

05AB1E, 44 43 bytes

Δ…ijk©Dâ•KĀ ‰9Ï•„ -®«ÅвJ#:Σ'-Ê]„--KD'-såi1«

Try it online! or Try all cases!

Δ                    # Until the output doesn't change:
…ijk©Dâ              #   = ii ij ik ji jj jk ki kj kk
•KĀ ‰9Ï•" -®«ÅвJ#    #   =  -  k -j -k  -  i  j -i  -
:                    #   Replace
Σ'-Ê                 #   Sort "-" to the front
„--K                 # Remove all non-overlapping occurences of "--"
'-såi                # If the result is a substring of "-":
1«                   #   Append a 1
\$\endgroup\$
1
  • 1
    \$\begingroup\$ -1 byte in your 37 byte solution by changing J1« to , and -1 in your 43 byte solution by changing Σ'-Ê] to '-†}. \$\endgroup\$ Jul 27, 2021 at 13:57
4
\$\begingroup\$

Retina 0.8.2, 44 bytes

k
ij
ii|jj
-
ji
-ij
--

}`.-
-$&-
ij
k
\B$
1

Try it online! Link includes test cases. Explanation:

k
ij

Replace k with ij as an interim measure.

ii|jj
-

Substitute -1 for ii and jj.

ji
-ij

Substitute -ij for ji.

--

.-
-$&-

Propagate -s to the left.

}`

Repeat until no more changes can be made.

ij
k

Convert ij back into k.

\B$
1

Output 1 if there is no i, j or k.

My original idea unfortunately turned out to be as long as the naïve approach which has since been golfed anyway:

^(..)+$
-ijk$&
+`^(.*)((\w)\3|(j|k)(i|j))
-$1$5$4
ijk
-
--

\B$
1

Try it online! Link includes test cases. Explanation:

^(..)+$
-ijk$&

Prepend -ijk if there are an even number of letters, because the next stage doesn't affect the parity and we want to end up with a single letter if possible.

+`^(.*)((\w)\3|(j|k)(i|j))
-$1$5$4

Repeatedly delete two identical letters or exchange two unsorted letters. These operations both negate the final result.

ijk
-

Reduce any remaining ijk to -1.

--

\B$
1

Tidy up the result.

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

K (ngn/k), 71 63 bytes

-8 bytes thanks to @coltim!

(r,"-",'r:"1ijk")@((8!A,4+A:a,'4+a:4 4#0,8\5717110953644).,)/4!

Try it online!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I was able to trim 8 bytes by making things more tacit \$\endgroup\$
    – coltim
    May 26, 2021 at 15:41
  • \$\begingroup\$ @coltim great! i was expecting you might be able to improve it but not by that many bytes :) \$\endgroup\$
    – ngn
    May 26, 2021 at 19:24
4
\$\begingroup\$

Python 3, 89 bytes

a=0
for c in input():a=b'147223453614'[ord(c)%3*4+a%4]^(a&4)
print('-'[:a&4]+'1ijk'[a%4])

Try it online!

It encodes the unit quaternions 1, i, j, k, -1, -i, -j, -k as the numbers 0..7 respectively, so that a%4 represents one of 1, i, j, k, and a&4 represents whether the value is negated.

The loop works by accumulating a result in the variable a, multiplying one quaternion from the input on each iteration, much like you'd write a = 1; for x in numbers: a *= x to compute a product normally.

The byte-string b'147223453614' encodes a multiplication table for i, j, k multiplied by 1, i, j, k. Indexing a byte-string gives the ASCII values as integers, but we only care about the lowest 3 bits and, conveniently, the ASCII value of each digit 0..7 has the correct lowest 3 bits, so this is practically like indexing a list of integers. The table is 3x4 instead of 8x8 because one operand (from the user input) can only be i, j or k, and we can ignore the sign from the other operand (the accumulator variable) and deal with the sign afterwards.

The expression ord(c)%3 converts the ASCII characters i, j, k into 0, 1, 2 respectively; the ASCII values of i, j and k are correct for this modulo 3. (If they weren't, the multiplication table could be changed.) The ^(a&4) part XORs the result with the third bit of a, swapping the sign of the result from the multiplication table if this operand was negative, since we ignored the sign before.

The output uses string slicing to conditionally print the minus sign ('-'[:4] conveniently isn't an error in Python).

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

Jelly, 23 20 bytes

3*żæ.ƒ0%8ṃ“ijk1””--¦

Try it online!

Try all examples

A full program taking a list of code points and printing to STDOUT.

Uses Neil’s clever observation about the effect of multiplication by I, j and k in his Charcoal answer so be sure to upvote that one too! Thanks also to Neil for saving a byte with the observation that \$3 ^ x \mod 8\$ is 3 for all odd \$x\$ and 1 for all even \$x\$.

Thanks to @cairdcoinheringaahing for saving a byte by pointing out that I could remove the trailing . Thanks to @JonathanAllan for a neat 1 byte save using .

Explanation (outdated)

 3*                    | 3 to the power of each codepoint
   ż                   | Zip with original list of codepoints
    æ.ƒ0               | Starting with zero, reduce the list using dot product (extending the shorter argument with ones)
        %8             | Mod 8
          d4           | Divmod 4
            ị"“- “ijk1 | Index into zipped "- ", "ijk1"
\$\endgroup\$
4
  • 1
    \$\begingroup\$ You can remove the trailing to have it as a full program: Try it online! \$\endgroup\$ May 27, 2021 at 0:08
  • \$\begingroup\$ @cairdcoinheringaahing thanks for pointing out! \$\endgroup\$ May 27, 2021 at 0:16
  • 1
    \$\begingroup\$ You don't need the . This works because 3² is 1 mod 8. (I can't believe I got to say that...) \$\endgroup\$
    – Neil
    May 27, 2021 at 9:13
  • 1
    \$\begingroup\$ I believe 3*żæ.ƒ0%8ṃ“ijk1””--¦ should work for a byte save. When the %8 result is greater than 3 will be length two, and ¦ will replace the first character with a '-'. \$\endgroup\$ May 27, 2021 at 12:24
3
\$\begingroup\$

Jelly, 61 bytes

,ḅ8‘ị“;CAĖ*VÆSȮṀƒḳẹ²^÷d5ñYƭẏ§O’b8¤
_ȷ2ç/ị“1“-i“-j“-k“i“j“k“-1

Try it online!

-2 bytes thanks to caird coinheringaahing


Takes input as a list of codepoints, outputs as a string.

The indexing to get the final answer is terrible. Looking for a way to improve that.

This is very unclever. The first link just encodes the entire multiplication table into a size-64 list, converts the left and right arguments from base 8, and indexes into that. The main link takes the input as codepoints, subtracts 100 (which makes ijk equal to 567 and thus I put -1, 1, -i, -j, -k into 01234), reduces the list from the left by the helper link, and then indexes the answer into a valid output format.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ -2 bytes by starting the helper link with , and reducing as ç/ instead of ,Ç¥/, and by removing the trailing \$\endgroup\$ May 26, 2021 at 1:46
3
\$\begingroup\$

C (gcc), 104 103 101 bytes

a,z,n,h='h';f(s,d)int*s,*d;{for(a=n=9;z=*s++;n^=(~a+z)%3&&a>h?0:36)a=a>h?a^z^h:z;*d++=n;*d=a>h?a:49;}

Try it online!

Apparently I had basically the same idea as tsh, using xor.

It still needs some optimization, considering how tsh's is like half the size in calculations.

  • -1 byte for using tab (9) instead of space (32)
  • -2 bytes for making a temp for 'h'

Input is a null terminated array of codepoints, output is stored to a pointer to a bunch of codepoints.

void f(const int *src, int dst[2])
{
    int acc, tmp, sign;
    for (acc = sign = '\t'; (tmp = *src++); ) {
        // My XOR math will end up with less than 'i' for 1.
        // TODO: fix this efficiently and I can remove some checks.
        if (acc < 'i') {
            acc = tmp;
        } else {
            // Xoring all of them together will result in the result or 'h'
            acc = acc ^ tmp ^'h';
        }
        // Magic for testing when we need to negate
        if ((acc - tmp + 1) % 3 == 0 && acc > 'h') {
             // Switches between tab and '-'
             sign ^= 36;
         }
     }
     // Output sign
     dst[0] = sign;
     // Correct our xor math
     dst[1] = a < 'i' ? '1' : a;
}
\$\endgroup\$
2
  • \$\begingroup\$ Using a different output method gives (string embedded in an int) 93 bytes \$\endgroup\$
    – rtpax
    May 27, 2021 at 18:13
  • 1
    \$\begingroup\$ Also, I got a 95 byte answer using a modular arithmetic instead of xoring. Thought I'd share \$\endgroup\$
    – rtpax
    May 27, 2021 at 18:15
3
\$\begingroup\$

APL(Dyalog Unicode), 33 26 bytes SBCS

A port of Nick Kennedy's Jelly answer.

' -' '1ijk'⌷⍨¨2 4⊤⊢⊥⍨3*2|⊢

Try it on APLgolf!

A train submission which takes a list of codepoints as the right argument.

APL has mixed radix decode () which makes the calculation part quite short:

       2|⊢  ⍝ each value in the input modulo 2
     3*     ⍝ raise 3 to this power
  ⊢⊥⍨       ⍝ use the resulting numbers as a mixed radix to decode the input

Technically the 2| is not needed, but without it even the test cases with 2 characters start to fail because of very large numbers.

2 4⊤ calculates 4|⊢ and 2|4÷⍨⊢, and both resulting integers are used to index into one string.

\$\endgroup\$
2
\$\begingroup\$

Vyxal,95 91 55 bytes

λ2ẇ«½∇«:Ẋ«∨uOŻN€bMċ∇Dk«⌈Ŀṅ\e/:L‹\e*$ṅ+;Ẋ‛eeo:\erß›\e\-V

Try it Online!

Partially ported ovs. Do I win 'Most improved'?

Makes a lot more sense now.

λ                                     ;                 # Lambda
 2ẇ                                                     # Chunks of 2
   «½∇«                                                 # Compressed `ijk`
       :Ẋ                                               # Cartesian square
         «∨uOŻN€bMċ∇Dk«                                 # Compressed string `e k ej ek e i j ei e`
                       ⌈                                # Split by spaces
                        Ŀ                               # Transliterate
                         ṅ                              # Join
                          \e/                           # Split by `e`
                             :                          # Duplicate
                              L‹\e*                     # (Length - 1) es
                                   $ṅ+                  # Prepended (so es are bubbled to start
                                       Ẋ                # Repeat on input until result doesn't change 
                                        ‛eeo            # Get rid of double es
                                            :\erß       # If result is a substring of 'e'
                                                 ›      # Append a 1
                                                  \e\-V # Replace es with -s
\$\endgroup\$
3
  • \$\begingroup\$ You lost to Retina - a lang that doesn't even have addition - but +1 \$\endgroup\$ May 27, 2021 at 7:47
  • \$\begingroup\$ @StackMeter Retina's perfect for stuff like this, and I have an idea... \$\endgroup\$
    – emanresu A
    May 27, 2021 at 8:43
  • \$\begingroup\$ ok then go ahead \$\endgroup\$ May 27, 2021 at 9:05
2
\$\begingroup\$

Retina, 65 58 bytes

(\w)\1
-
ij
k
jk
i
ki
j
ji|kj|ik
-$^$&
--

}`.-
-$&-
\B$
1

Try it online!

Saved 7 bytes thanks to Neil!

The main tricks deal with handling negative signs and the negative products. Negative signs are "bubbled" towards the beginning of the string to prevent needing to account for them in the other rules. The products that produce a negative such as ji are all reversed and then negated, to avoid dealing with them specifically. At the end, if there is no letter, we add a 1.

\$\endgroup\$
4
  • \$\begingroup\$ If you double the ( in the header you can change the ) to a } and drop the leading {, saving two bytes. \$\endgroup\$
    – Neil
    May 26, 2021 at 9:32
  • \$\begingroup\$ You can use (\w)\1 instead of ii|jj|kk. \$\endgroup\$
    – Neil
    May 26, 2021 at 9:42
  • \$\begingroup\$ I think replacing ji|kj|ik with -$^$& works. \$\endgroup\$
    – Neil
    May 26, 2021 at 9:44
  • \$\begingroup\$ @Neil Thanks for the tips! The $^ idea is particularly nice. Separately, your answers are quite clever, using the idea of rotating or in getting rid of k early to avoid writing out most of the cases. \$\endgroup\$ May 26, 2021 at 16:35
2
\$\begingroup\$

Factor + math.matrices math.quaternions, 125 bytes

[ "1ijk"4 identity-matrix zip tuck substitute 1 n>q [ q* ] reduce dup sum 0 < 45 32 ? -rot vabs swap assoc-invert at 2array ]

Try it online!

On TIO it's 129 bytes; this is because Factor on TIO is ancient and doesn't have tuck which is the same as dup -rot.

Explanation:

It's a quotation (anonymous function) that takes an array of code points as input from the data stack and leaves an array of code points as output on the data stack.

  • "1ijk"4 identity-matrix zip tuck substitute Convert the input to quaternions that Factor can do arithmetic on. For example, "ijkj" -> { { 0 1 0 0 } { 0 0 1 0 } { 0 0 0 1 } { 0 0 1 0 } }
  • 1 n>q [ q* ] reduce Take the product.
  • dup sum 0 < 45 32 ? -rot Place the code point for the sign on the data stack.
  • vabs swap assoc-invert at Convert the product quaternion to a code point.
  • 2array Make an array out of the code points that are on the stack.
\$\endgroup\$
2
\$\begingroup\$

Python 3, 199 \$\cdots\$ 182 173 bytes

f=lambda s,n=0:s<'.'and f(s[1:],1-n)or len(s)>1and f(s<'2'and s[1:]or(s[0]==s[1]and'-1'or{'ij':'k','jk':'i','ki':'j','ji':'-k','kj':'-i','ik':'-j'}[s[:2]])+s[2:],n)or'-'*n+s

Try it online!

Slightly longer version without direct mappings of \$i\$, \$j\$, and \$k\$ inter-multiplications:

Python 3.8, 174 bytes

f=lambda s,n=0,o='ijkijki':s<'.'and f(s[1:],1-n)or len(s)>1and f(s<'2'and s[1:]or(s[0]==s[1]and'-1'or((b:=o.find(s[1],(a:=o.find(s[0]))))-a-1)*'-'+o[2*b-a])+s[2:],n)or'-'*n+s

Try it online!

\$\endgroup\$
2
\$\begingroup\$

Haskell, 135 84 bytes

x#'i'=x*3+1
x#'j'=x+2
x#'k'=x*3+3
t=(cycle([pure,('-':).pure]<*>"1ijk")!!).foldl(#)0

Try it online!

Had a lot of trouble doing this efficiently in Haskell. Hopefully this will inspire someone to create a better Haskell answer.

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

PowerShell, 55 bytes

Port of the Arnauld's solution.

Expects an array of ASCII codes. Returns a string.

,4+$args|%{$x+=$_%2*2*$x+$_}
'-'[$x-band4]+'1ijk'[$x%4]

Try it online!


PowerShell, 122 120 bytes

Thanks @Neil for the k -> ij simplification.

for($s="$args";$p-ne($p=$s)){'k ij9ii|jj -9ji -ij9--9.- -$0-9ij k'-split9|%{$s=$s-replace-split$_}}
$p+'1'*($p-in'','-')

Try it online!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ The $s-replace-split$_ is so impressive, good one ! \$\endgroup\$
    – Julian
    May 27, 2021 at 10:14
1
\$\begingroup\$

Haskell, 58 bytes

(\m->['-'|m 8>3]++["1ijk"!!m 4]).mod.foldl(\x y->3^y*x+y)0

Try it online!

A port of Neil's Charcoal answer that takes a list of codepoints as input.

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

Shue, 46 bytes

1
i
j
k
-1
-i
-j
-k
=1
--=
ii=-
jk=i
k=ij
j=ki

Try it online!

It's... beautiful.

Uses an i-dominated internal representation. Basically, the number of j and k characters is decreasing. The last two rules allow the movement of j and k between is.

Due to the rule =1, this is a bit slow. If you want to test this answer, I suggest you use This equivalent program.

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

Lexurgy, 108 bytes

a propagate:
``=>*
{ii,jj,kk,ij,jk,ki,ji,kj,ik}=>{`,`,`,k,i,j,`k,`i,`j}
[]$1 `=>` $1
b:
*=>\1/{$,`} _ $
`=>\-

Explanation:

# while the input changed last iteration...
a propagate:
# cancel out negatives
``=>*
# ijk conversion
{ii,jj,kk,ij,jk,ki,ji,kj,ik}=>{`,`,`,k,i,j,`k,`i,`j}
# move negative signs to the front
[]$1 `=>` $1 

# cleanup
b: 
# put in 1s on empty string or leading `
*=>\1/{$,`} _ $
# ` to minus
`=>\-
\$\endgroup\$
0
\$\begingroup\$

J, 41 bytes

'1ijk -'&([({~4&|,~4+3&<)(8|]+[*3^])/@i.)

Try it online!

Neil's excellent idea applied to J.

\$\endgroup\$
0
\$\begingroup\$

Python 3, 289 bytes

R={"ii":"-","jj":"-","kk":"-","ij":"k","jk":"i","ki":"j","ji":"-k","kj":"-i","ik":"-j"}
def f(x):
    if x=="-":return"-1"
    if len(x)==1:return x
    if len(x)==2and x[0]=="-":return x
    for s,e in R.items():x=x.replace(s,e)
    return f(("-"if x.count("-")%2else"")+"".join(c for c in x if c!="-"))

Try it online! Takes input as a string.

\$\endgroup\$
0
\$\begingroup\$

Python 3, 132 bytes

Function that iterates over the characters of the expression, evaluating step by step the product of a pair of characters.

def f(e,s=0):
 while e[1:]:p,e=e[:2],e[2:];a=set('ijk')-set(p);e=a.pop()[len(a):]+e;s^=p not in'ijki'
 return'-'[:s]+['1',e][len(e)]

Try it online!


Code explanation:

  • def f(e, s=0):
    function definition: takes the expression to be evaluated as input e; the 2nd parameter s is the sign, passed as optional parameter to be initialized (0 => +, 1 => -).
  • while e[1:]:
    main loop: iterates over the characters in the expression, while there are at least 2 characters left (the empty string evaluates to False).
  • p, e = e[:2], e[2:];
    take the first two characters p (to evaluate their product), and the remaining part of the expression.
  • a = set('ijk') - set(p);
    evaluates the set a as difference between the set of 3 chars {i, j, k} and the set of 2 chars in p. This can be used to distinguish the different cases: if p is a mixed product (ij, jk, ...) then the difference set will contain the char resulting from their product; otherwise, the difference set will have len==2.
  • e = a.pop()[len(a):] + e;
    prepend to the remaining expression the char resulting from the product. This exploits the set a built before: pop() will return the correct char only if we have a mixed product: the difference set will contain only the resulting char. It is taken by slicing the single char string with len: actually len=0 accounts for the fact that a.pop is evaluated before len.
  • s ^= p not in 'ijki'
    evaluates the sign: the product will have negative sign for {ii, jj, kk} and {ji, kj, ik}, all of them are not contained in the string 'ijki'. The boolean evaluates to integer to compute the xor function: sign xor (1, 0) follows the sign multiplication rules.
  • return '-'[:s] + ['1', e][len(e)]
    finally, the function returns the results of the expression: first the sign - only if s==1. Then the results of the expression: e if it contains the remaining single char (exit from loop), otherwise 1.
\$\endgroup\$
0
\$\begingroup\$

x86-64 machine code, 36 bytes

30 C1 74 06 28 C8 04 02 7B 05 35 31 C9 31 C0 AC 24 03 75 EC 9E 79 03 B0 2D AA 8D 41 31 E3 02 04 37 66 AB C3

Try it online!

Following the standard calling convention for Unix-like systems (from the System V AMD64 ABI), this takes in RDI an address at which to place the result, as a null-terminated byte string; and the address of the input, as a null-terminated byte string, in RSI. The starting point is after the first 11 bytes.

In assembly:

# ECX holds 0 for 1, 1 for i, 2 for j, or 3 for k.
# The high bit of AH holds a sign: 1 to negate or 0 to not negate.

multiply:       # Here, AL is 1, 2, or 3 for the current input letter (as above).
    xor cl, al  # Exclusive-or CL (the low byte of ECX) with that value.
                    # This produces the correct new value.
    jz flipsign # If the result is 0 (i*i, j*j, k*k), jump to flip the sign.
    sub al, cl  # Subtract CL from AL.
    add al, 2   # Add 2 to AL. The possibilities based on the original CL and AL are:
                #  0: j*i=-k
                #  1: k*i=j  i*j=k
                #  2: 1*i=i  1*j=j  1*k=k
                #  3: k*j=-i i*k=-j
                #  4: j*k=i
    jpo next    # Jump if the sum of the low 8 bits is odd, to skip the sign flipping.
                #  It is odd for 1, 2, and 4, so the sign is flipped for 0 and 3.
flipsign:
    .byte 0x35  # This byte combines with the next two instructions to form
                #  'xor eax, 0xC031C931', which in particular flips the high bit of AH.
f:
    xor ecx, ecx    # (Start here) Set ECX to 0.
    xor eax, eax    # Set EAX to 0.
next:
    lodsb           # Load a byte from the input string into AL, advancing the pointer.
    and al, 3       # AND that byte with 3, converting i,j,k to 1,2,3 respectively.
    jnz multiply    # Jump if the result is not 0. It is 0 for the null byte.
    sahf            # Set flags from AH. In particular, the high bit of AH goes into SF.
    jns skipminus   # Jump if SF=0 to skip the minus sign.
    mov al, 0x2D    # Set AL to the ASCII code of HYPHEN-MINUS.
    stosb           # Add that to the output string, advancing the pointer.
skipminus:
    lea eax, [rcx+0x31] # Set EAX to ECX+0x31. 0x31 is the ASCII code of 1.
    jrcxz oneskip       # Jump if ECX is 0.
    add al, 0x37        # (Otherwise) Add 0x37 to AL, for a net addition of 0x68,
                        #  which makes 1,2,3 into the ASCII codes of i,j,k respectively.
oneskip:
    stosw   # Add AL and AH (which is 0) to the output string, advancing the pointer.
    ret     # Return.
\$\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.