[Edit: I see I misunderstood the question. I answered the question: "given a set integer-valued coordinates in 3-dimensional space, find all integer-valued coordinates that are each a neighbor of at least one coordinate in the set" (with "neighbor" suitably defined). My solution still works (by setting my_map => [location]), but it could be simplified to:
def neighbors(location)
locs = location.map { |x| [*(x-1..x+1)] }
locs.shift.product(*locs) - [location]
end
I will leave my answer as is, should any reader be interested in the more general question.]
This is how I would do it.
Code
def neighbors(my_map)
my_map.map do |l,_|
locs = location.map { |x| [*(x-1..x+1)] }
locs.shift.product(*locs)
end.reduce(:|) - my_map.keys
end
Example
my_map = { [1,2,3]=>"123", [2,3,2]=>"232", [1,1,2]=>"112" }
neighbors(my_map)
#=> [[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], [0, 2, 3], [0, 2, 4],
# [0, 3, 2], [0, 3, 3], [0, 3, 4], [1, 1, 3], [1, 1, 4], [1, 2, 2],
# [1, 2, 4], [1, 3, 2], [1, 3, 3], [1, 3, 4], [2, 1, 2], [2, 1, 3],
# [2, 1, 4], [2, 2, 2], [2, 2, 3], [2, 2, 4], [2, 3, 3], [2, 3, 4],
# [1, 2, 1], [1, 3, 1], [1, 4, 1], [1, 4, 2], [1, 4, 3], [2, 2, 1],
# [2, 3, 1], [2, 4, 1], [2, 4, 2], [2, 4, 3], [3, 2, 1], [3, 2, 2],
# [3, 2, 3], [3, 3, 1], [3, 3, 2], [3, 3, 3], [3, 4, 1], [3, 4, 2],
# [3, 4, 3], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 1, 1], [0, 2, 1],
# [1, 0, 1], [1, 0, 2], [1, 0, 3], [1, 1, 1], [2, 0, 1], [2, 0, 2],
# [2, 0, 3], [2, 1, 1]]
The three keys of my_map arr found to have a total of 56 unique neighbors (out of a possible total of 3**3 - 3 = 78, the '-3' to avoid counting the elements of my_map).
Explanation
Assume that my_map is as in the example above. (The hash values I gave are arbitrary.)
We will map each of the three key/value pairs of my_map into an array of neighboring cells, take the union of those arrays and lastly remove the hash keys from the union.
The first value map passes into its block is:
[[1,2,3], "123"]
Normally each element of this array (corresponding to the key and value of the hash element) would be represented by a block variable, but as we will not be using the value ("123"), I've replaced its variable with an underscore. The key, [1,2,3], is assigned to the block variable l.
Next we have
locs = location.map { |x| [*(x-1..x+1)] }
#=> [1,2,3].map { |x| [*(x-1..x+1)] }
#=> [[0, 1, 2], [1, 2, 3], [2, 3, 4]]
then
a = locs.shift #=> [0, 1, 2]
so now
locs #=> [[1, 2, 3], [2, 3, 4]]
meaning that [1,2,3]'s neighbors are:
b = a.product(*locs)
#=> [0, 1, 2].product([1, 2, 3], [2, 3, 4])
#=> [[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], [0, 2, 3], [0, 2, 4],
# [0, 3, 2], [0, 3, 3], [0, 3, 4], [1, 1, 2], [1, 1, 3], [1, 1, 4],
# [1, 2, 2], [1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 3], [1, 3, 4],
# [2, 1, 2], [2, 1, 3], [2, 1, 4], [2, 2, 2], [2, 2, 3], [2, 2, 4],
# [2, 3, 2], [2, 3, 3], [2, 3, 4]]
Notice that [1,2,3] is in this array, though is not a neighbor of itself. I'll remove it at the end. (The other two elements of my_map are also in this array, and therefore need to be remove, but there's no point doing that now, because both of the other elements of my_map will also have all there elements in their corresponding arrays.)
We repeat this for each of the other two elements of my_map, obtaining arrays that I'll denote c and d.
We want the union of these three arrays:
(b | c | d)
which will eliminate duplicates (and preserve order). We do this using Enumerable#reduce and Array#|:
e = [b,c,d].reduce(:|)
Lastly, we remove the elements in my_map using Array#-.
e - my_map.keys
Efficiency
If my_map were large, it probably would be more efficient to convert each array of 27 neighboring elements to a set before taking their union.
@map? What is a tile? What doesget_surroundingsdo?- coordto eliminate the self place. I was subtracting it on the wrong place. I think. Please take a look now if you want.