简体   繁体   中英

Sorting an array of arrays with the delta

I have an array like this:

[[300, 400], [200, 300], [500, 600], [400, 200]] 

Which I want is to make an another array sorted by other value and the first item should be closer to that value, I tried to sort by delta:

c = [300, 50]
f = [[200, 300], [300, 400], [400, 200], [500, 600]].sort do |a, b|
  a_width_delta = a.first - c.first
  a_height_delta = a.last - c.last
  b_width_delta = b.first - c.first
  b_height_delta = b.last - c.last

  (a_width_delta + a_height_delta) <=> (b_width_delta + b_height_delta)
end
p "Input #{c}"
p f
c = [300, 400]
f = [[200, 300], [300, 400], [400, 200], [500, 600]].sort do |a, b|
  a_width_delta = a.first - c.first
  a_height_delta = a.last - c.last
  b_width_delta = b.first - c.first
  b_height_delta = b.last - c.last

  (a_width_delta + a_height_delta) <=> (b_width_delta + b_height_delta)
end
p "Input #{c}"
p f

This conditional is correct:

$> ruby fo.rb
"Input [300, 50]"
[[200, 300], # other elements] # correct!

Another one is wrong:

"Input [300, 400]"
[[200, 300], # other elements] # wrong!

It should be:

"Input [300, 400]"
[[300, 400], # other elements] # correct!

All that I need is an array with closest value at the first index.

I'm hoping your example of "correct" output is just wrong. Otherwise I don't understand the requirements. :-)

If I do understand correctly, the idea is to sort by the "distance" of each element from a starting point. The distance is defined as the sum of the differences of the component values. Eg for x1, y1 and x2, y2 : |x2-x1| + |y2-y1| |x2-x1| + |y2-y1| .

It's the absolute value that's missing... your current code "rewards" negative numbers inappropriately.

Here's a version that takes the absolute value of each calculation. It doesn't produce your desired output. If you really do want the output you specified, please describe what rules you're using to determine that.

c = [300, 50]
f = [[200, 300], [300, 400], [400, 200], [500, 600]].sort do |a, b|
  a_width_delta = (a.first - c.first).abs
  a_height_delta = (a.last - c.last).abs
  b_width_delta = (b.first - c.first).abs
  b_height_delta = (b.last - c.last).abs

  (a_width_delta + a_height_delta) <=> (b_width_delta + b_height_delta)
end
p "Input #{c}"
p f

# Output:
# "Input [300, 50]"
# [[400, 200], [200, 300], [300, 400], [500, 600]]


c = [300, 400]
f = [[200, 300], [300, 400], [400, 200], [500, 600]].sort do |a, b|
  a_width_delta = (a.first - c.first).abs
  a_height_delta = (a.last - c.last).abs
  b_width_delta = (b.first - c.first).abs
  b_height_delta = (b.last - c.last).abs

  (a_width_delta + a_height_delta) <=> (b_width_delta + b_height_delta)
end
p "Input #{c}"
p f

# Output:
# "Input [300, 400]"
# [[300, 400], [200, 300], [400, 200], [500, 600]]

EDIT

Per the edit to your question, it seems that all you care about is the first element in the output array (the minimum). In that case, why not just calculate the min?

c = [300, 50]
f = [[200, 300], [300, 400], [400, 200], [500, 600]].min_by { |x| (x.first - c.first).abs + (x.last - c.last).abs }
p "Input #{c}"
p f

# Output:
# "Input [300, 50]"
# [400, 200]


c = [300, 400]
f = [[200, 300], [300, 400], [400, 200], [500, 600]].min_by { |x| (x.first - c.first).abs + (x.last - c.last).abs }
p "Input #{c}"
p f

# Output:
# "Input [300, 400]"
# [300, 400]
▶ λ = lambda do |input, point|
    [input, point].transpose # to ease the subtraction
                  .map { |e| e.reduce &:- } # distances [x, y]
                  .map(&:abs) # absolute values of distances
                  .reduce(:+) # total distance
  end

▶ [[200, 300], [300, 400], [400, 200], [500, 600]].min_by &λ.curry[[300, 50]]
#⇒ [400, 200]

▶ [[200, 300], [300, 400], [400, 200], [500, 600]].min_by &λ.curry[[300, 400]]
#⇒ [300, 400]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM