Generate hex numbers based on percentage - ruby

I'm looking for a way to generate a gradual hexadecimal color based on a percentage with Ruby.
0% = #6da94a
50% = #decc30
100% = #ce2d4a
Then programmatically generate the hexadecimal values in between those.
So I might have something like percent_to_hex(10) and that would spit out whatever hexadecimal value is 10% along the gradual gradient between 0% (green) and 50% (yellow).

Actually there is a small mistake in tralston's answer.
(x + percent * 100 * (y - x)).round
should be changed to:
(x + percent / 100.0 * (y - x)).round
Also i.to_s(16) would be a problem if you have 0 (255), as you can get a result like "ff0ff". I would recommend using "%02x" % i instead.
Here is the complete example:
def percent_to_hex(percent, start, stop)
colors = [start,stop].map do |c|
c.scan(/../).map { |s| s.to_i(16) }
end
colors_int = colors.transpose.map do |x,y|
(x + percent / 100.0 * (y - x)).round
end
colors_int.map { |i| "%02x" % i }.join("")
end

Not a very polished method, but here's a good start:
# Example input: percent_to_hex(25, "abcdef", "ffffff") => "c0daf3"
def percent_to_hex(percent, start, stop)
colors = [start,stop].map do |c|
c.scan(/../).map { |s| s.to_i(16) }
end
colors_int = colors.transpose.map do |x,y|
(x + percent * 100 * (y - x)).round
end
colors_int.map { |i| i.to_s(16) }.join("")
end
Of course if you could customize it further to add or remove the leading "#" of the hex color code, etc.

Related

Infinite While loops in ruby

I have the following program which should count the number of years it should take a population to grow to the desired size. Whenever I run this I get an infinite loop. Can someone help me identify my error?
def pop_growth(start, percent, desired)
year_count = 0
while start <= desired
year_count += 1
start = start + (start * (percent / 100))
end
return year_count
end
I'm sure that you are trying with Integers (instead floats), so you are losing precision try this
def pop_growth(start, percent, desired)
year_count = 0
while start <= desired
year_count += 1
start = start + (start * (percent.to_f / 100))
end
return year_count
end
and let me know if it works for you. if not can you send me your start, percent and desired values?
The proper answer is given by Horacio, let me rewrite this in idiomatic ruby:
def pop_growth start, percent, desired
(0..Float::INFINITY).inject(start) do |memo, years|
break years if memo > desired
memo *= (1.0 + percent / 100.0)
end
end
or, with infinite loop:
def pop_growth start, percent, desired
loop.each_with_object(years: 0, count: start) do |_, memo|
break memo[:years] if memo[:count] > desired
memo[:years] += 1
memo[:count] *= (1.0 + percent / 100.0)
end
end
Three ways.
#1 Solve equation
Solve desired = start * (1.0 + 0.01 * percent)**n for n:
def pop_growth(start, percent, desired)
Math.log(desired.to_f/start)/Math.log(1.0 + percent/100.0)
end
years = pop_growth(100, 10, 200)
#=> 7.272540897341713
years.ceil #=> 8 if desired.
#2 Compound until desire met
def pop_growth(start, percent, desired)
return 0 if start >= desired
alpha = 1.0 + 0.01 * percent
1.step.find { (start *= alpha) >= desired }
end
pop_growth 100, 10, 200
#=> 8
#3 Use recursion
def pop_growth(start, percent, desired, years=0)
return years if start >= desired
pop_growth(start*(1.0+0.01*percent), percent, desired, years+1)
end
pop_growth 100, 10, 200
#=> 8
Just add .to_f method to percent or divide by 100.0, which will convert the integer into float.
start + (start * (percent / 100))
When you are dividing, you need at least one float number in order to return the exact division answer, else Ruby will round it down to nearest whole number, which in this case percent / 100 will result in 0, assuming that the value in percent is less than 100. This will cause this statement start + (start * (percent / 100)) to become start = start + 0, which is why you are seeing the infinite loop.

Reducing RGB color intensity in Ruby with Rmagick

I'm translating a function made with MATLAB that reduces the color intensity from an image's sector to Ruby using Rmagick
for i=round(f/3):f
for j=1:round(c)
for k=1:p
A(i,j,k) = B(i,j,k) - a;
end
end
end
for i=1:round(2*f/3)
This reduces the color intensity of all 3 RGB matrix by some value (a). Trying to reproduce this with ruby code yielded this:
(0..imagen.columns).each do |x|
((2 * imagen.rows) / 3..imagen.rows).each do |y|
imagen.pixel_color(x, y, 'pink')
end
end
I can change the lower third of my image to a set color, in this example, to pink.
Trying to reduce each component by some value (atenuacion) with each color's method won't work, it returns the same image.
(0..imagen.columns).each do |x|
((2 * imagen.rows) / 3..imagen.rows).each do |y|
pixel = imagen.pixel_color(x, y)
pixel.red - atenuacion
pixel.green - atenuacion
pixel.blue - atenuacion
imagen.pixel_color(x, y, pixel)
end
end
Any tips or suggestions are welcome, thank you in advance :D
The problem is here:
pixel.red - atenuacion
pixel.green - atenuacion
pixel.blue - atenuacion
You're not changing the value of pixel.red, etc. You're just subtracting atenuacion from the value of pixel.red and then doing nothing with the result. Try this:
pixel.red -= atenuacion
pixel.green -= atenuacion
pixel.blue -= atenuacion
In the above, pixel.red -= atenuacion is shorthand for pixel.red = pixel.red - atenuacion. I'm just guessing that pixel.red et al are setters as well as getters. If not, you may need to do something like this instead:
pixel = imagen.pixel_color(x, y)
new_color = Magick::Pixel.new(
pixel.red - atenuacion,
pixel.green - atenuacion,
pixel.blue - atenuacion,
pixel.opacity)
imagen.pixel_color(x, y, new_color)

How do I generate a number in a percentage range?

I am making a text adventure game and have to randomise the stats of my hero's enemies.
Is there a way to generate a random whole number from within a percentage range?
Like this: BaseHealth ± 10%, where BaseHealth is a variable.
def randomize_value(value, percent)
bottom = (value * (1 - percent / 100.0)).to_i
up = (value * (1 + percent / 100.0)).to_i
(bottom..up).to_a.sample
end
health = randomize_value(BaseHealth, 10)
This is assuming that health is to be integer.
If BaseHealth is integer,
def take_random base, percent
d = (base * percent / 100.0).to_i
base - d + rand(d * 2)
end
take_random(BaseHealth, 10)
or following Stefan's suggestion,
def take_random base, percent
d = (base * percent / 100.0).to_i
rand(base - d..base + d)
end
take_random(BaseHealth, 10)
I understand what you mean now
You can do this:
BaseHealth = ( rand(BaseHealth * 0.2) + BaseHealth*0.9 ).to_i
This can be accomplished with some basic arithmetic:
TotalHealth = BaseHealth + (BaseHealth * (rand(21)-10)/100)
This will take the BaseHealth and multiply it by a random number 0..20 minus 10, converted to a percent.
Assume BaseHealth = 20:
If rand returns 17, you get 7/100 = .07 so TotalHealth = 21.41
If rand returns 7, you get -7/100 = -.07 so TotalHealth = 18.6

Can this check digit method be refactored?

I have the following method for doing a check digit on a tracking number, but it just feels lengthy/sloppy. Can it be refactored and just generally cleaned up?
I'm running Ruby 1.8.7.
def is_fedex(number)
n = number.reverse[0..14]
check_digit = n.first.to_i
even_numbers = n[1..1].to_i + n[3..3].to_i + n[5..5].to_i + n[7..7].to_i + n[9..9].to_i + n[11..11].to_i + n[13..13].to_i
even_numbers = even_numbers * 3
odd_numbers = n[2..2].to_i + n[4..4].to_i + n[6..6].to_i + n[8..8].to_i + n[10..10].to_i + n[12..12].to_i + n[14..14].to_i
total = even_numbers + odd_numbers
multiple_of_ten = total + 10 - (total % 10)
remainder = multiple_of_ten - total
if remainder == check_digit
true
else
false
end
end
EDIT: Here are valid and invalid numbers.
Valid: 9612019950078574025848
Invalid: 9612019950078574025847
def is_fedex(number)
total = (7..20).inject(0) {|sum, i| sum + number[i..i].to_i * ( i.odd? ? 1 : 3 ) }
number[-1].to_i == (total / 10.0).ceil * 10 - total
end
I believe you should keep your code. While it's not idiomatic or clever, it's the one you will have the least trouble to understand a few months from now.
I'm not a ruby programmer, so if any of the syntax is off, I apologize but you should get the general idea. A few things I see: First, you don't need to slice the array, a single index should be sufficient. Second, Instead of splitting even and odd, you could do something like this:
total = 0
for i in (1..14)
total += n[i].to_i * ( i % 2 == 1 ? 1 : 3 )
end
Third, remainder could be simplified to 10 - (total % 10).
I realize you're running 1.8.7, but here's my attempt using each_slice and inject in conjunction, a 1.9.2 feature:
def is_fedex(number)
total = number.reverse[1..14].split(//).map(&:to_i).each_slice(2).inject(0) do |t, (e,o)|
t += e*3 + o
end
10 - (total % 10) == number[-1].to_i
end
It passes both tests
Give this a try:
#assuming number comes in as a string
def is_fedex(number)
n = number.reverse[0..14].scan(/./)
check_digit = n[0].to_i
total = 0
n[1..14].each_with_index {|d,i| total += d.to_i * (i.even? ? 3 : 1) }
check_digit == 10 - (total % 10)
end
> is_fedex("12345678901231") => true
edit incorporating simplified remainder logic as Kevin suggested
Something like this?
def is_fedex(number)
even_arr, odd_arr = number.to_s[1..13].split(//).map(&:to_i).partition.with_index { |n, i| i.even? }
total = even_arr.inject(:+) * 3 + odd_arr.inject(:+)
number.to_s.reverse[0..0].to_i == (total + 10 - (total % 10)) - total
end
If you can give me a valid and invalid number I can test if it works and maybe tweak it further :)
This function should to:
def is_fedex(number)
# sanity check
return false unless number.length == 15
data = number[0..13].reverse
check_digit = number[14..14].to_i
total = (0..data.length-1).inject(0) do |total, i|
total += data[i..i].to_i * 3**((i+1)%2)
end
(10 - total % 10) == check_digit
end
The arithmetic expression 3**((i+1)%2) might look a bit complex, but is essentially the same as (i.odd? ? 1 : 3). Both variants are correct, which you use is up to you (and might affect speed...)
Also note, that if you use Ruby 1.9, you can use data[i] instead of data[i..i] which is required for for Ruby 1.8.

Generating pastel colors

I neeed to generate random color. But I need pstel ones. Not too dark, not too bright.
I can generate colors this way:
color = (1..3).to_a.map{ ( c = rand(255).to_s(16) ).size < 2 ? "0#{c}" : c }.to_s
But it will return colors from all palette.
Try this:
start_color = 128 # minimal color amount
total_offset = 64 # sum of individual color offsets above the minimal amount
'#' +
[0, rand(total_offset), rand(total_offset), total_offset].sort.each_cons(2).map{|a,b|
"%02x" % (start_color+b-a)
}.join
Actually, here's tiny Sinatra app that you can play with and see the results instantly:
require 'sinatra'
def get_pastel start_color, total_offset
'#' +
[0, rand(total_offset), rand(total_offset), total_offset].sort.each_cons(2).map{|a,b|
"%02x" % (start_color+b-a)
}.join
end
get '/:start_color/:total_offset' do |start_color, total_offset|
(0..20).map{c = get_pastel(start_color.to_i, total_offset.to_i)
"<span style='background-color:#{c}'>#{c}</span>\n"
}.join
end
Then fire up the browser and see how it looks:
http://localhost:4567/192/64
http://localhost:4567/128/128
;)
This might give you something useful:
colour_range = 128
colour_brightness = 64
color = (1..3).to_a.map{ ( c = rand(colour_range)+colour_brightness.to_s(16) ).size < 2 ? "0#{c}" : c }.to_s
I think it will limit you to mid saturation, mid-brightness colours.

Resources