# Consolidate a 6-axis 2-dimensional Vector

Typically, when we want to represent a magnitude and direction in 2D space, we use a 2-axis vector. These axes are typically called X and Y:

This isn't always convenient, however. The game BattleTech is played on a hexagonal grid, and it's convenient for the axes to line up with the sides of the hexagons. To represent velocity, thrust, and other such values, BattleTech uses a 6-axis Thrust Vector, with axes named A-F:

Here are a few example Thrust Vectors, all of which represent the following magnitude and direction:

• { A = 2, B = 1, C = 0, D = 0, E = 0, F = 0 }
• { A = 3, B = 0, C = 1, D = 0, E = 0, F = 0 }
• { A = 5, B = 0, C = 0, D = 3, E = -1, F = 0 }

This follows from two rules of equivalence for Thrust Vectors:

• Adding or subtracting the same value to two opposite axes (A/D, B/E or C/F) leads to an equivalent Thrust Vector: { A = 1, D = 1 } => {A = 0, D = 0}
• Subtracting a value from two axes with a single axis between them (A/C, B/D, C/E, D/F, E/A, F/B) and adding that value to the axis between them leads to an equivalent Thrust Vector: { A = 1, B = 0, C = 1 } => { A = 0, B = 1, C = 0}

It is possible to make a simple Thrust Vector look very complicated by placing value in axes in a different direction than the vector actually points in, which is undesirable. A Consolidated Thrust Vector is a Thrust Vector that fulfills the following conditions:

• No axes hold a negative value.
• There are up to 2 axes with nonzero values.
• If there are 2 axes with nonzero values, they are adjacent axes.

Of the above example vectors, only the first is Consolidated.

Your challenge is to accept a 6-axis vector (as a 6-element list or other convenient structure) of integer magnitudes, and return an equivalent vector which has been consolidated. There is only one possible solution for any given input.

Test cases:

• -1, 0, 0, 0, 0, 0 => 0, 0, 0, 1, 0, 0
• 1, 0, 0, 2, 0, 0 => 0, 0, 0, 1, 0, 0
• 1, 0, 2, 0, 0, 0 => 0, 1, 1, 0, 0, 0
• 1, 2, 3, 4, 5, 6 => 0, 0, 0, 0, 6, 0
• 1, 0, 2, 0, 3, 0 => 0, 0, 0, 1, 1, 0
• 1, 0, 1, 0, 1, 0 => 0, 0, 0, 0, 0, 0
• 0, 0, 0, 0, 0, 0 => 0, 0, 0, 0, 0, 0
• 0, 0, 0, 0, 0, 1 => 0, 0, 0, 0, 0, 1
• 1, 0, 0, 0, 1, 0 => 0, 0, 0, 0, 0, 1
• -1, -1, -1, 1, 1, 1 => 0, 0, 0, 0, 4, 0
• What was the task (that you mentioned in the comment on my answer) that led to this problem? Commented Feb 28, 2023 at 7:14
• @ParclyTaxel Part of playing aerospace in battletech is determining the relative angle of attack when firing on an enemy (hitting an enemy from behind is easier than hitting one moving horizontally across your field of view, for instance). Calculating this is slow and imprecise, so I wanted to create an app to do it for me, which requires wrapping my head around some tedious math. For instance, an attack hitting at an angle of exactly 30 degrees, through the corner of a hex, is a special case, so I find myself not being able to use many helpful builtins due to floating point imprecision. Commented Feb 28, 2023 at 11:20
• I decided to work on some easier related problems first to get into the right headspace, then thought that it would be interesting to see how others might solve the same problems. Commented Feb 28, 2023 at 11:21

# Python, 78 bytes

-29 from ovs
-3 from loopy walt

lambda v:-clip(A@v,v@A,0)
from numpy import*
A=1-abs((r_[:6]-c_[:6])%6*2-5)//2


Attempt This Online!

We want a function that returns the indicated number if positive, else 0, for the element corresponding to the black axis. That function is $$\\max(\min(x,y),0)\$$, where $$\(x,y)\$$ are the coordinates of the vector's endpoint with respect to the neighbouring grey axes.

Each coordinate in each of the six coordinate frames needed for the whole consolidated vector is in turn a linear function of the input coordinates. The linear functions can be combined into two circulant matrices – which is really one circulant matrix and its transpose.

clip is abused here with negation to implement $$\\max(\min(x,y),0)\$$. $$\-x\$$ is clipped to bounds $$\-y\$$ and 0, and then

• if $$\y\ge0\$$ normal clipping occurs and $$\\max(\min(x,y),0)\$$ is returned at the end by reversing the negation.
• if $$\y<0\$$, clip is coded in NumPy to always return the upper bound of 0. But this is what we want, since $$\\min(x,y)\$$ will be negative regardless of $$\x\$$ and $$\\max(\min(x,y),0)\$$ will be 0.
• -4 Commented Feb 27, 2023 at 8:00
• This puzzle comes straight from an actual task I had to solve, and frankly this is much better than the naive solution I ended up coming up with. I might steal this algorithm as soon as I finish wrapping my head around it. Commented Feb 27, 2023 at 9:29
• A=r_[[1,1,0,-1,-1,0]][r_[:6]-c_[:6]].T creates the circulant matrix without the scipy.linalg import, which leads to 94 bytes by my count.
– ovs
Commented Feb 27, 2023 at 10:52
• fmin and fmax save a bit more
– ovs
Commented Feb 27, 2023 at 11:13
• One last one: numpy.clip can be abused to do both min and max here. It's a bit tricky to get this, -clip(-A@v,v@-A,0) seems to work: 81 (But make sure to verify this actually works, I'm not 100% sure, it does pass all the test cases)
– ovs
Commented Feb 27, 2023 at 11:23

# Charcoal, 47 bytes

ＵＭθ⁻ι§θ⁺³κ≔✂θ¹χ²ηＵＭθ∧﹪κ²⁻ι⁻Ση⁺⌊η⌈ηＩＥθ⌈⟦⁰⁻ι§θ⁺³κ


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

ＵＭθ⁻ι§θ⁺³κ


Subtract each element from its opposite.

≔✂θ¹χ²η


Get alternate elements so we can find the median (calculated below as ⁻Ση⁺⌊η⌈η).

ＵＭθ∧﹪κ²⁻ι⁻Ση⁺⌊η⌈η


Subtract the median from the alternate elements and zero out the other elements for now.

ＩＥθ⌈⟦⁰⁻ι§θ⁺³κ


Reverse any negative elements and output the result.

Example for given test case of 0, 1, 1, 2, 3, 5:

1. Subtract each element from its opposite, resulting in -2, -2, -4, 2, 2, 4.

2. Take alternate elements, resulting in -2, 2, 4. The median is 2.

3. Subtract 2 from the alternate elements, resulting in 0, -4, 0, 0, 0, 2.

4. Reverse the -4, resulting in 0, 0, 0, 0, 4, 2 which is the final answer.

# Vyxal, 16 bytes

y1Ǔ-:∆ṁ-:N1ǔY0v∴


Try it Online!

y                 # uninterleave
1Ǔ               # rotate left by one
-              # subtract
:             # duplicate
∆ṁ           # median
-          # subtract
:         # duplicate
N        # negate
1ǔ      # rotate right by one
Y     # interleave
0v∴  # elementwise maximum with 0


# 05AB1E, 16 bytes

ιÀ-DÅm-D(Á.ιDd*


Port of @AndrovT's Vyxal and @Neil's Charcoal answers, so make sure to upvote them as well!

Explanation:

                  #  e.g. input=[1,2,3,4,5,6]
ι                # Uninterleave the (implicit) input-list into 2 parts
#  STACK: [[1,3,5],[2,4,6]]
# Pop and push both inner lists to the stack
#  STACK: [1,3,5],[2,4,6]
À              # Rotate the top list once towards the left
#  STACK: [1,3,5],[4,6,2]
-             # Element-wise subtract the values in the lists from one another
#  STACK: [-3,-3,3]
D            # Duplicate this list
#  STACK: [-3,-3,3],[-3,-3,3]
Åm          # Pop and push its median
#  STACK: [-3,-3,3],-3
-         # Subtract that from each value in the list
#  STACK: [0,0,6]
D(       # Create a negative copy
#  STACK: [0,0,6],[0,0,-6]
Á      # Rotate that list once towards the right
#  STACK: [0,0,6],[-6,0,0]
.ι    # Uninterleave the two lists to a single list
#  STACK: [0,-6,0,0,6,0]
Dd* # Convert all negative values to 0s:
D   #  Duplicate this list
#   STACK: [0,-6,0,0,6,0],[0,-6,0,0,6,0]
d  #  Check for each value whether it's non-negative (>=0)
#   STACK: [0,-6,0,0,6,0],[0,0,0,0,1,0]
* #  Element-wise multiply the values in the lists
#   STACK: [0,0,0,0,6,0]
# (after which the result is output implicitly)

• I think you mean median, not arithmetic mean, right? Commented Feb 28, 2023 at 20:31

# [Wolfram Language (Mathematica)], 150143 bytes

 (a=#;h=(a+a[[#1]]*RotateRight[{-1,1,-1,0,0,0},#1-#2])&;Do[a=h[i,1],{i,4}];Catch[Do[If[AllTrue[a,NonNegative],Throw[a]];a=h[i,3],{i,6,1,-1}]])&


Try it online!

I' did something looks like Python Parcly Taxel' answer, but it' s uncompetitive.
So I just start to play with equivalent transformations. If we apply four times function

 tr1[arr_,i_]:=(a=arr;d=arr[[i]];
a[[i]]=0;
a[[Mod[i+1,6,1]]]+=d;
a[[Mod[i+2,6,1]]]-=d;
a)


for i = {1,2,3,4} we get array {0,0,0,0,x,y}, but where x, y could be negative.
And if we start going backwards from i=6 with

 tr2[arr_,i_]:=(a=arr;d=arr[[i]];
a[[i]]=0;
a[[Mod[i-1,6,1]]]+=d;
a[[Mod[i-2,6,1]]]-=d;
a)


than sooner or later we will get the array of the required type.
No later than 6 steps but I haven't found out exactly when, and had to apply the condition.
Try to improve the answer later