4

I get the sense that I'm missing some fundamental knowledge for bitwise manipulation on some seemingly simple things.

I have an enumeration of integers between 1 and 8, representing all the possible vectors of movement from a position in a grid.
(I imagine some of you may recognize this problem)

{
    TOP: 1,
    TOP_RIGHT: 2,
    RIGHT: 3,
    BOTTOM_RIGHT: 4,
    BOTTOM: 5,
    BOTTOM_LEFT: 6,
    LEFT: 7,
    TOP_LEFT: 8,
}

Given a path of positions on the grid, between two points, these can be used to express the movement along that path.

[
  {x: 22, y: 30}, 
  {x: 22, y: 29}, 
  {x: 22, y: 28}, 
  {x: 23, y: 27}, 
  {x: 24, y: 26}, 
  {x: 25, y: 25}, 
  {x: 26, y: 25}, 
  {x: 27, y: 24},
  {x: 26, y: 23},
  {x: 27, y: 22},
  {x: 26, y: 22}
]

is finally expressed as a string

"1122232827"

How do I best invert the direction?

I've been trying different bitwise operations to make this as fast as possible but I can't figure it out (this is a big bottleneck). So far, I've been using a shorthand if to just check if the direction is more than half the limit and then remove or add half of that. But, again, I sense that this can be done more efficiently with some bitwise jiggery. Feel free to do whatever with the code below.

Note: the lookup map, direction vectors and the method to get the direction are constants and can't be changed. They are actually interpretations of what happens under the hood of a certain API and are presumably much better implemented.

// Enum of vectors
let vectors = {
    TOP: 1,
    TOP_RIGHT: 2,
    RIGHT: 3,
    BOTTOM_RIGHT: 4,
    BOTTOM: 5,
    BOTTOM_LEFT: 6,
    LEFT: 7,
    TOP_LEFT: 8,
};
_.extend(window, vectors); // Add them to global

// Lookup table for vectors
let dirMap = {
    1:    { 1: TOP_RIGHT, 0: RIGHT, "-1": BOTTOM_RIGHT },
    0:    { 1: TOP, "-1": BOTTOM, },
    "-1": { 1: TOP_LEFT, 0: LEFT, "-1": BOTTOM_LEFT }
};

// Get the direction key
function getDirection(a, b){
    return dirMap[b.x - a.x][a.y - b.y];
}

// Example path
let path = [   
    {x: 22, y: 30}, 
    {x: 22, y: 29}, 
    {x: 22, y: 28}, 
    {x: 23, y: 27}, 
    {x: 24, y: 26}, 
    {x: 25, y: 25}, 
    {x: 26, y: 25}, 
    {x: 27, y: 24},
    {x: 26, y: 23},
    {x: 27, y: 22},
    {x: 26, y: 22}
];

let strPath = "", strInverted = "";

for(let i = 1, len = path.length; i < len; i++){
    let prev = path[i - 1];
    let direction = getDirection(prev, path[i]);
    let inverse = direction + (direction > 4 ? -4 : 4); // Can I turn this into a bitwise operation?
    strPath += direction;
    strInverted = inverse + strInverted;
}

console.log("Forward: ", strPath);
console.log("Reverse: ", strInverted);

document.getElementById("forward").innerHTML = strPath;
document.getElementById("reverse").innerHTML = strInverted;

// Just for debugging purposes
function getDirString(dirString){
    let arrDir = [];
  _.each(dirString, function(c){
    let val = parseInt(c), dir = "";
    _.find(vectors, function(o, i){ if(o === val){ dir = i; return true; }});
    arrDir.push(dir);
    });
  return arrDir.join(", ");
}

document.getElementById("full_forward").innerHTML = getDirString(strPath);
document.getElementById("full_reverse").innerHTML = getDirString(strInverted);
body{ font-family: Arial, Helvetica, sans-serif; }

#forward:before, #reverse:before, #full_forward:before, #full_reverse:before{
  display: inline-block;
  margin-right: 1em;
  width: 4em;
}

#full_forward, #full_reverse{font-size: 0.7em; white-space: nowrap;}

#forward:before{ content: "Forward:"; }
#reverse:before{ content: "Reverse:"; }
#full_forward:before{ content: "Forward:"; }
#full_reverse:before{ content: "Reverse:"; }
<script src="https://cdn.jsdelivr.net/lodash/2.1.0/lodash.compat.js"></script>

<div id="forward"></div>
<div id="reverse"></div>
<br>
<div id="full_forward"></div>
<div id="full_reverse"></div>

Here's a jsfiddle to play with.

Whiteboard I've been staring at for the past hour or so whiteboard of ultimate scratchheading

TylerH
  • 20,799
  • 66
  • 75
  • 101
ShadowScripter
  • 7,314
  • 4
  • 36
  • 54
  • 1
    _"I've been trying different bitwise operations to make this as fast as possible"_ Would bitwise operators perform greater number of operations than current approach at `let inverse = direction + (direction > 4 ? -4 : 4)`? – guest271314 Sep 23 '16 at 21:16
  • That's a good question. I don't really know the specifics of how javascript does bit operations under the hood but I would assume that it'd be faster as long as the operations are equal to or less than the operations required for the current approach? This is the only place I can actually do any performance improvements before the path is stored in memory so, every op counts. – ShadowScripter Sep 23 '16 at 21:22
  • See this [Answer](http://stackoverflow.com/a/34487506/) by @lleaff – guest271314 Sep 23 '16 at 23:12
  • 1
    Can you renumber them one lower? If not, `((x - 1) ^ 4) + 1` – harold Sep 25 '16 at 20:18
  • @harold Excellent. Care to explain your thought process? By the sound of it, you thought of it in terms of a range between 0-7 so that the Xor behaves in a specific way? Because I can't renumber the direction constants, your solution should theoretically have 1 more operation than the current one (with the shorthand if). Bit manipulation is not my strong suit, so I'd appreciate any tips you might have. – ShadowScripter Sep 26 '16 at 03:07
  • 1
    @ShadowScripter I looked at the +4 vs -4 thing, which already sounds like "xor with 4", but in order to make it "line up" it has to be so that -4 happens exactly when the bit with weight 4 is set and +4 when it is not. – harold Sep 26 '16 at 08:52

1 Answers1

1

I dont think you want bitwise operations here, I actually think that an array that has hardcoded values which you simply plop in your value as an index would work. Of course it is not as memory efficient but you list would only have a length of 8 so it is negligible.

Try this out:

var inverseDirList = [5,6,7,8,1,2,3,4];
var invertedDirection = inverseDirList[direction - 1];

If you really care about not doing any arithmetic operations on the direction value you can simply add a stub element, say -1, to the beginning of the list, so it would be essentially 1-based

As others have mentioned you can use devTools to check performance and compare, but according to this stackoverflow question/answer JS hash/array lookups have a constant time complexity

Community
  • 1
  • 1
splay
  • 327
  • 2
  • 12
  • Hm, I'll try it out. This does seem like the best approach. Though I'd still like to know if there's a way to do this using bitwise operations (if it is even possible?), only because I'm intellectually curious and stubborn. Will accept if no one can actually solve it :) – ShadowScripter Sep 23 '16 at 21:31
  • Either I'm using the profiling tools wrong, or it's actually showing no difference, or might be neglible? Over 10 million runs, I got differences of 100-200 ms (with the total time being 20 seconds for both) in favor of your approach (~2%). It's still better though. – ShadowScripter Sep 23 '16 at 22:16