2

I'm a beginner in ruby, trying to use it to help me analyse biological data. I need to try and match a set of data (numbers in an array) to another with a certain specificity (e.g number+/- 0.25) I have come up with this (so far) to change one data set into ranges, instead of numbers:

def range(arr)
  c = []
  arr.each do |b|
    b = (b-0.25..b+0.25)
    b = b.to_a
    c << b
  end
  c = c.flatten
  return c
end

the code gives the desired array, however I always get

TypeError: can't iterate from Float.

how can I fix that?

Background

this is a sample of my practical data:

119.0456 119.0714 119.0721 119.0737 120.0772 130.0746 131.0737 136.0721 140.0951 143.0697 154.038 154.0744 154.1108 155.0949 156.054 169.053 170.1422 171.0646 171.0686 174.0644 174.0795 180.0539 182.1059

I need to match it to a theoretical set, which I need to generate withtin a tolerance of 0.002 I am working on the code step by step to generate my theoretical set, since I'm still new to coding, and just wanted to know how to create a range of +/- 0.002 around my theoretical set to match it to the practical one.

Stefan
  • 109,145
  • 14
  • 143
  • 218
Mokhtar
  • 165
  • 2
  • 7
  • 1
    you're missing an `end` right before your `return c`. i'm assuming this is a stackoverflow typo since you said your function returns the desired array. – eiko Apr 03 '17 at 17:15
  • also, could you add the code which actually generates the error (the code where you iterate over the range? if you're trying to see if data fits within the range, i suspect there's a simpler way which side-steps this problem all together. – eiko Apr 03 '17 at 17:18
  • _"I need to try and match a set of data to another with a certain specificity"_ – could you show an example? – Stefan Apr 03 '17 at 17:28
  • just edited the code to include the whole script – Mokhtar Apr 03 '17 at 17:39
  • 119.0456 119.0714 119.0721 119.0737 120.0772 130.0746 131.0737 136.0721 140.0951 143.0697 154.038 154.0744 154.1108 155.0949 156.054 169.053 170.1422 171.0646 171.0686 174.0644 174.0795 180.0539 182.1059 this is a sample of my practical data, I need to match it to a theoretical set, which I need to generate, withtin a tolerance of 0.002. I am working on the code step by step to generate my theoretical set, since I'm still new to coding, and just wanted to know how to create a range of +/- 0.002 around my theoretical set to match it to the practical one – Mokhtar Apr 03 '17 at 18:08
  • @Mokhtar thanks, but please edit your question instead. Besides, how does / should the "theoretical set" look like, i.e. what's your expected result? – Stefan Apr 03 '17 at 18:14
  • @Stefan ideally, my theoretical set should look like my practical set (just more values). – Mokhtar Apr 03 '17 at 18:29

4 Answers4

17

Can ruby have a range starting with a Float?

Yes, you can create ranges with floats:

r = 0.25..0.75
#=> 0.25..0.75

But you can't use Range#each to traverse it, because each relies on succ (e.g. Integer#succ) and Float doesn't implement that method.

Instead, you can use Range#step with takes an explcit increment value:

r.step(0.1) { |f| puts f }

Output:

0.25
0.35
0.45
0.55
0.65
0.75

Just for fun, let's see what happens if we utilize Float#next_float:

class Float
  alias succ next_float
end

r = 0.25..0.75

r.each { |f| puts f }

Output:

0.25
0.25000000000000006
0.2500000000000001
0.25000000000000017
0.2500000000000002
0.2500000000000003
0.25000000000000033
0.2500000000000004
0.25000000000000044
0.2500000000000005
0.25000000000000056
0.2500000000000006
...

the code gives the desired array, however I always get

TypeError: can't iterate from Float.

how can I fix that

You could build an array of ranges instead:

def range(arr)
  arr.map { |b| b-0.25..b+0.25 }
end

range [1, 2, 3]
#=> [0.75..1.25, 1.75..2.25, 2.75..3.25]
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • I used your answer to modify my code and tested it with simple conditions, like steps of 0.1 in a range of 0.2 on an array [1, 2, 3]. It works, but sometimes it adds random number of 0s after the decimal. e.g. I get 1.900000000000000001 instead of 1.9 and 3.19999999999999997 instead of 3.2 I don't understand why that happens – Mokhtar Apr 03 '17 at 18:43
  • https://floating-point-gui.de/ helps to understand why you get those “odd” values. – Stefan Nov 07 '21 at 00:06
2

Firstly, this code:

def range(arr)
  c = []
  arr.each do |b|
    b = (b-0.25..b+0.25)
    c << b
  end
  return c
end

Basically takes an array and creates a new array with each item modified according to a rule. In Ruby, you can use .map() for that exact purpose:

def range(arr)
  arr.map { |b| (b-0.25..b+0.25) }
end

Secondly, as others have said, ranges of floats do exist, but you can't iterate over a range of floats because Ruby has no way of knowing what increment you want. The range (1.0 .. 2.0) technically holds an infinite amount of numbers (e.g. 1.2324123 and 1.9232311) so you can't iterate without saying how big each of your steps should be. You can do that like so:

range.step(0.1) { |f| #do stuff with f }

But if your goal is to compare arrays, to see if [1,2,3] fits into the range [0.75..1.25, 1.75..2.25, 2.75..3.25], you shouldn't have to iterate over ranges at all. You should use the .cover?() method instead, to see if a number is in a range:

def in_range(arr, ranges)
  return false unless arr.size == ranges.size
  arr.zip(ranges).do { |a, r| return false unless r.cover? a }
  return true
end
eiko
  • 5,110
  • 6
  • 17
  • 35
  • 1
    Although quite large, the number of distinct floats in `1.0..2.0` is finite ;-) – Stefan Apr 03 '17 at 17:37
  • @Stefan haha, true! but the number of values which are covered by a range of floats **is** far more vast because the logic is handled by the value rather than the range: `(1.0..2.0).cover? BigDecimal.new("1.2323232323232323223") #=> true` – eiko Apr 03 '17 at 17:45
  • i had no idea that was the case before you commented, but i checked and got lucky :3 – eiko Apr 03 '17 at 17:45
1

If I've understood your problem correctly, you can do this instead:

arr1 = [1,2,3]
arr2 = [1.25, 1.85, 4.25]
s = 0.25

arr1.zip(arr2).select { |a, b| (a-b).abs <= s }
#=> [[1, 1.25], [2, 1.85]]

key methods: Array#zip, Array#select and Float#abs.

Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
-1

You don't show a closing end for the range method - the end shown closes the arr.each loop.

Could that be the problem?

IanH
  • 191
  • 1
  • 5