views:

207

answers:

5

Greeting, I'm trying to solve the following puzzle:

I have a list of linear ranges which represent one big range.

                                          X'
 100    200 300    400 500    600 700     |       900    (X)
|----------|----------|----------|--------+----------|
 0                                        |       100    (Y)
                                          Y'

X consists of the following ranges (even and round numbers are just examples for ease of comprehension, they could be anything, no proportions here at all):

  • from 100 to 200
  • from 300 to 400
  • from 500 to 600
  • from 700 to 900

On the flip side, Y has just one range:

  • from 0 to 100

Both X and Y are of the same length, just different units. Lets say one is dollars and another is percents (or any other similarly unrelated units). So Y'0 == X'100 and Y'100 == X'900.

Question: Given any point in Y, what is equivalent point in X and vise-versa, given a point in X - what is it in Y?

Is this a typical math problem? Does it have a name? Anything that would set me in the right direction would help.

Thank you.

Here's solution based on Igor Krivokon's answer. I had to remove multiplication by two to make it work.

input_range = [
  [100, 200],
  [300, 400],
  [500, 600],
  [700, 800]
]

range_sum = 0
lookup_list = []

input_range.each do |range_start, range_end|
  lookup_list.push [ range_start, range_sum ]
  range_sum += range_end - range_start
end

def get_percent(value, lookup_list, range_sum)
  result = 0

  lookup_list.reverse.each do |range_start, cummulative_sum|
    if value >= range_start
      result = value + cummulative_sum - range_start
      break
    end
  end

  return result.to_f / range_sum.to_f
end

def get_value(percent, lookup_list, range_sum)
  result = 0
  value = range_sum * percent

  lookup_list.reverse.each do |range_start, cummulative_sum|
    if value >= cummulative_sum
      result = value - cummulative_sum + range_start
      break
    end
  end

  return result.to_f
end

puts "Input range: #{input_range.inspect}"
puts "Continous range sum: #{range_sum}"
puts "Lookup list: #{lookup_list.inspect}"

input_range.each do |range|
  range.each do |start_or_end|
    puts "#{start_or_end} is at " + get_percent(start_or_end, lookup_list, range_sum).to_s
  end
end

[ 0, 0.25, 0.5, 0.75, 1 ].each do |percent|
  puts "#{percent} is at " + get_value(percent, lookup_list, range_sum).to_s
end

$> ruby ranges.rb

Input range: [[100, 200], [300, 400], [500, 600], [700, 800]]
Continous range sum: 400
Lookup list: [[100, 0], [300, 100], [500, 200], [700, 300]]
100 is at 0.0
200 is at 0.25
300 is at 0.25
400 is at 0.5
500 is at 0.5
600 is at 0.75
700 is at 0.75
800 is at 1.0
0 is at 100.0
0.25 is at 300.0
0.5 is at 500.0
0.75 is at 700.0
1 is at 800.0

$> ruby ranges.rb

Input range: [[100, 200], [300, 400], [500, 600], [700, 1000]]
Continous range sum: 600
Lookup list: [[100, 0], [300, 100], [500, 200], [700, 300]]
100 is at 0.0
200 is at 0.166666666666667
300 is at 0.166666666666667
400 is at 0.333333333333333
500 is at 0.333333333333333
600 is at 0.5
700 is at 0.5
1000 is at 1.0
0 is at 100.0
0.25 is at 350.0
0.5 is at 700.0
0.75 is at 850.0
1 is at 1000.0

It seems to work with any range I give it, back and forth.

A: 

Say you have one range (a, b) and another one (c, d). Now you have a number i for which a < i < b. You can "normalize" it by subtracting a and dividing by b - a - this gives you a value between 0 and 1. You can then use this value to transfer it into the other range by reversing this calculation with the other bounds, so to speak multiply it by (d - c) and add c.

Say the corresponding point in the other range is i'. Then,

i' = (i - a) / (b - a) * (d - c) + c

The term you are searching for is scaling and translation.

bayer
If the ranges are discontinuous though (as the OP said), then this doesn't work so well... since from what I understand a to b isn't necessarily continuous... I could be misunderstanding the OP though...
DeadHead
A: 

This is not really solvable because the problem is underspecified. Even for the same ranges, there can be different sliders like this:

1  100 101       1000
|-----|-----------|

1        100 101 1000
|-----------|-----|

For each range like [1..100] you need to know how which percent points on the slider correspond to it. In the above examples this could be something like [0%..33%] or [0%..66%]. Once you have this information, it's easy to determine in which of the ranges and at which position of that range a given data point is and to what value it corresponds.

sth
The length of the range from 1 to 100 is always the same and so is the length from 101 to 1000. These are the same units, therefore length is consistent.So in your example 1-100 would represent 10% of the whole range. If your other range was from 900 to 1000, then 1-100 would represent 50% of the range.
alex
A: 
tom10
+2  A: 

How many ranges do you have? Is it acceptable that the algorithm is O(number of ranges)?

If so, below is the description of the algorithm. Let me explain it on your (original) example.

 100    200 300    400 500    600 700    800
|----------|----------|----------|----------|
 0%                                     100%

1) What you're doing to do is to map the value X in range A (100-800) to the value Y in continous range B (0-399) (as the total number of elements in your range is 400). Then it's easy to change position in B to percents, I will omit this part.

2) Create a list of records, where each records represents one range mapping.

struct RangeRecord {
  int start_in_a;
  int start_in_b;
};

In your case, you will get the following list:

{100, 0}, {300, 100}, {500, 200}, {700, 300}

3) When you need to map a number X from A to B, you iterate the list to find first record with start_in_a <= X.Then your value Y is

Y = X + start_in_b - start_in_a;

4) The algorithm is symmettric, you just iterate the list to find the first record with start_in_b <= Y, and then

X = Y + start_in_a - start_in_b.

Note 1. For error checking purposes, you might keep the range size in RangeRecord, as well.

Note 2. If O(number of ranges) is not good enough, keep the records as a tree instead of a list. You will need O(log(number of ranges)) operations then,

Igor Krivokon
I'm not entirely clear how you arrive to the list of RangeRecords, ie how do you know that when start_in_a is 300, start_in_b is 100?
alex
The start_in_a is always the same as the start of your original range. The value of start_in_b is the cumulative size of your ranges. If you need me to explain it more, please tell me where/how do you keep the information about the original ranges. I could write some pseudocode then.
Igor Krivokon
i've written up ruby code based on this http://pastie.org/497294 to confirm what I was getting on paper and it doesn't seem to work. The Y values for 400 and 500 should be near identical since both of them are smack in the middle. Same for 200 and 300, since they are near each other, their corresponding values should be very close.
alex
I got it to work... i'm not sure where multiplication by 2 comes from, it was messing everything up for me. Without 2x, it works. Please see my original post for solution.
alex
Seems like I've mistaken in the formula. Please try to remove 2 coefficient, i.e. make it result = value + cummulative_sum - range_start
Igor Krivokon
Ah, I didn't see your previous comment. Glad that it works now.
Igor Krivokon
Just finished implementing this into the program, it worked very well. Thank you!
alex
A: 

You have three things you need to adjust for in converting from some X' to Y' and vice versa:

  1. The ranges start at different places.
  2. One of them is discontinuous.
  3. The size of each step is different between the two ranges.

It might be helpful (at least while developing your solutions) to consider a similar range Z, which is the range 0 to 503 and has a one-to-one mapping with the 504 possible values in X. That is, for each discontinuity, if the X value is greater than the upper end of the discontinuity, subtract 99 (the size of the discontinuity). Then X'100 = Z'0, X'200 = Z'100, X'300 = Z'101, X'400 = Z'201, X'500 = Z'202, etc. The introduction of the Z range resolves problems 1 and 2 in the list above.

To convert from Z to Y, you just multiply by 101/504, which scales Z onto Y.

Iceman
this probably wouldn't scale well if my numbers are in the 10^10 range with gaps of 10^4 (which they are btw :)
alex
@alex, it's essentially the same thing Igor is doing in the accepted solution--my "Z" range is equivalent to his precomputed RangeRecord structs. Admittedly, he explained it better. :)
Iceman