10
\$\begingroup\$

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
\$\endgroup\$
3
  • \$\begingroup\$ What was the task (that you mentioned in the comment on my answer) that led to this problem? \$\endgroup\$ Feb 28 at 7:14
  • \$\begingroup\$ @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. \$\endgroup\$
    – Pavel
    Feb 28 at 11:20
  • \$\begingroup\$ 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. \$\endgroup\$
    – Pavel
    Feb 28 at 11:21

5 Answers 5

8
\$\begingroup\$

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.
\$\endgroup\$
6
  • \$\begingroup\$ -4 \$\endgroup\$ Feb 27 at 8:00
  • \$\begingroup\$ 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. \$\endgroup\$
    – Pavel
    Feb 27 at 9:29
  • \$\begingroup\$ 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. \$\endgroup\$
    – ovs
    Feb 27 at 10:52
  • \$\begingroup\$ fmin and fmax save a bit more \$\endgroup\$
    – ovs
    Feb 27 at 11:13
  • \$\begingroup\$ 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) \$\endgroup\$
    – ovs
    Feb 27 at 11:23
5
\$\begingroup\$

Charcoal, 47 bytes

UMθ⁻ι§θ⁺³κ≔✂θ¹χ²ηUMθ∧﹪κ²⁻ι⁻Ση⁺⌊η⌈ηIEθ⌈⟦⁰⁻ι§θ⁺³κ

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

UMθ⁻ι§θ⁺³κ

Subtract each element from its opposite.

≔✂θ¹χ²η

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

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

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

IEθ⌈⟦⁰⁻ι§θ⁺³κ

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.

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

Vyxal, 16 bytes

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

Try it Online!

Port of Neil's Charcoal answer.

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
\$\endgroup\$
2
\$\begingroup\$

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!

Try it online or verify all test cases.

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)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I think you mean median, not arithmetic mean, right? \$\endgroup\$
    – Pavel
    Feb 28 at 20:31
1
\$\begingroup\$

[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

\$\endgroup\$

Your Answer

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

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