I use CarrierWave and MiniMagick for work with images. I upload image. Right now I need to get values of all pixels in image and store them into array
If I use next code it works well
def pixelcolor
#image = MiniMagick::Image.open(image.path)
return #image.get_pixels[56][34][1] # 56 and 34 - random numbers for row and column
end
next code works as well
def pixelcolor
#image = MiniMagick::Image.open(image.path)
width = #image.width
color = Array.new
for i in 0..width
color[i] = i
end
return color
end
But if I try to do next code it doesn't work
def pixelcolor
#image = MiniMagick::Image.open(image.path)
width = #image.width
color = Array.new
for i in 0..width
color[i] = #image.get_pixels[45][65][0]
end
return color
end
As result I need something like this
def pixelcolor
#image = MiniMagick::Image.open(image.path)
width = #image.width
height = #image.height
red = Array.new
green = Array.new
blue = Array.new
for i in 0..width
for j in 0..height
red[i][j] = #image.get_pixels[width][height][0]
green[i][j] = #image.get_pixels[width][height][1]
blue[i][j] = #image.get_pixels[width][height][2]
end
end
return red, green, blue
end
Much time ago I have created very similar project in C++ for working with images so I have idea of what do to but not sure how do do it correct in Ruby as I'm new in it
The problem you're facing stems from the fact that Ruby doesn't have a concept of "multidimensional arrays", individual arrays are always one-dimensional.
You can, however, have nested arrays, which are arrays that contain arrays, like [[], [], []].
Let's have a look at two particular places:
red = Array.new # Note: this could as well be `red = []`
So you have assigned red a single empty array. Alright. Here's what you do later on:
red[i][j] = ...
We don't even need the rest of the line, it's visibly broken already.
On the very first iteration red is still an empty array. Thus, red[i] is nil, since there's absolutely nothing inside. nil does not have array accessors ([] and []=), so you'll be getting errors trying to read or write to it as if it were an array.
So you need red[i] to be an actual array, so you can put data into it. Let's make it happen.
Since you know the size of your arrays in advance, you can just allocate and populate red with with arrays of appropriate length immediately:
red = Array.new(width) { Array.new(height) }
For instance, for width = 2 and height = 3 here's the result you'd get:
[
[nil, nil, nil],
[nil, nil, nil]
]
So now that you have allocated all the cells that you'll need, you can start overwriting the nils with something meaningful.
See the docs for Array.new for the various ways to construct an array.
Careful:
I have used a "blocky" form of Array.new for a reason.
Beginners often make a mistake of using a single value to populate the array like so:
red = Array.new(width, Array.new(height))
While this form has its uses, it won't have the effect that you need: all the entries in red will refer to one and the same array, so modifying any single row would look as if all of them are modified.
Side note: you probably want to swap width and height in dimensions, since in graphics programs usually deal with arrays of rows, not columns. Depends, though. Might not have any effect on your particular problem.
Related
I am new to ruby. I am trying to create a report_checker function that checks how often the word "green, red, amber" appears and returns it in the format: "Green: 2/nAmber: 1/nRed:1".
If the word is not one of the free mentioned, it is replaced with the word 'unaccounted' but the number of times it appears is still counted.
My code is returning repeats e.g if I give it the input report_checker("Green, Amber, Green"). It returns "Green: 2/nAmber: 1/nGreen: 2" as opposed to "Green: 2/nAmber: 1".
Also, it doesn't count the number of times an unaccounted word appears. Any guidance on where I am going wrong?
def report_checker(string)
array = []
grading = ["Green", "Amber", "Red"]
input = string.tr(',', ' ').split(" ")
input.each do |x|
if grading.include?(x)
array.push( "#{x}: #{input.count(x)}")
else
x = "Unaccounted"
array.push( "#{x}: #{input.count(x)}")
end
end
array.join("/n")
end
report_checker("Green, Amber, Green")
I tried pushing the words into separate words and returning the expected word with its count
There's a lot of things you can do here to steer this into more idiomatic Ruby:
# Use a constant, as this never changes, and a Set, since you only care
# about inclusion, not order. Calling #include? on a Set is always
# quick, while on a longer array it can be very slow.
GRADING = Set.new(%w[ Green Amber Red ])
def report_checker(string)
# Do this as a series of transformations:
# 1. More lenient splitting on either comma or space, with optional leading
# and trailing spaces.
# 2. Conversion of invalid inputs into 'Unaccounted'
# 3. Grouping together of identical inputs via the #itself method
# 4. Combining these remapped strings into a single string
string.split(/\s*[,|\s]\s*/).map do |input|
if (GRADING.include?(input))
input
else
'Unaccounted'
end
end.group_by(&:itself).map do |input, samples|
"#{input}: #{samples.length}"
end.join("\n")
end
report_checker("Green, Amber, Green, Orange")
One thing you'll come to learn about Ruby is that simple mappings like this translate into very simple Ruby code. This might look a bit daunting now if you're not used to it, but keep in mind each component of that transformation isn't that complex, and further, that you can run up to that point to see what's going on, or even use .tap { |v| p v }. in the middle to expand on what's flowing through there.
Taking this further into the Ruby realm, you'd probably want to use symbols, as in :green and :amber, as these are very tidy as things like Hash keys: { green: 0, amber: 2 } etc.
While this is done as a single method, it might make sense to split this into two concerns: One focused on computing the report itself, as in to a form like { green: 2, amber: 1, unaccounted: 1 } and a second that can convert reports of that form into the desired output string.
There are lots and lots of ways to accomplish your end goal in Ruby. I won't go over those, but I will take a moment to point out a few key issues with your code in order to show you where the most notable probelms are and to show you how to fix it with as few changes as I can personally think of:
Issue #1:
if grading.include?(x)
array.push( "#{x}: #{input.count(x)}")
This results in a new array element being added each and every time grading includes x. This explains why you are getting repeated array elements ("Green: 2/nAmber: 1/nGreen: 2"). My suggested fix for this issue is to use the uniq method in the last line of your method defintion. This will remove any duplicated array elements.
Issue #2
else
x = "Unaccounted"
array.push( "#{x}: #{input.count(x)}")
The reason you're not seeing any quantity for your "Unaccounted" elements is that you're adding the word(string) "Unaccounted" to your array, but you've also re-defined x. The problem here is that input does not actually include any instances of "Unaccounted", so your count is always going to be 0. My suggested fix for this is to simply find the length difference between input and grading which will tell you exactly how many "Unaccounted" elements there actually are.
Issue #3 ??
I'm assuming you meant to include a newline and not a forward slash (/) followed by a literal "n" (n). My suggested fix for this of course is to use a proper newline (\n). If my assumption is incorrect, just ignore that part.
After all changes, your minimally modified code would look like this:
def report_checker(string)
array = []
grading = ["Green", "Amber", "Red"]
input = string.tr(',', ' ').split(" ")
input.each do |x|
if grading.include?(x)
array.push( "#{x}: #{input.count(x)}")
else
array.push( "Unaccounted: #{(input-grading).length}")
end
end
array.uniq.join("\n")
end
report_checker("Green, Amber, Green, Yellow, Blue, Blue")
#=>
Green: 2
Amber: 1
Unaccounted: 3
Again, I'm not suggesting that this is the most effective or efficient approach. I'm just giving you some minor corrections to work with so you can take baby steps if so desired.
Try with blow code
add your display logic outside of method
def report_checker(string, grading = %w[ Green Amber Red ])
data = string.split(/\s*[,|\s]\s*/)
unaccounted = data - grading
(data - unaccounted).tally.merge('Unaccounted' => unaccounted.count)
end
result = report_checker("Green, Amber, Green, Orange, Yellow")
result.each { |k,v| puts "#{k} : #{v}"}
Output
Green : 2
Amber : 1
Unaccounted : 2
For a Memory game, I'm trying to randomly populate a 2D array with letters.
The grid is size × size and each letter occurs twice.
This is my code so far. I don't understand why it is giving an error
alpha = ("A".."Z").to_a
letters_range = alpha[0...size*size/2]
chosen_letters = (letters_range + letters_range).shuffle
(0...size).each do |row|
(0...size).each do |col|
letter = chosen_letters.select
#grid[row][col] = Card.new(letter)
letter_idx = chosen_letters.index(letter)
chosen_letters.delete_at(letter_idx) #error line
end
end
chosen_letters is an array containing single-character strings.
When running letter = chosen_letters.select, you may assume that Array#select returns a random element. However, when not passing it a block, it returns an Enumerator. As such, your letter variable does not contain an element from the chosen_letter array and thus, an index for this object can not be found, resulting in letter_idx to be nil.
To fix this, you may want to use a more appropriate statement to fetch an element, e.g. Array#pop to return and remove the last element from the array.
Since chosen_letters is already shuffled, you don't have to pick a random element. Instead you can just pop the last or shift the first element off the array which makes your loop a lot simpler:
(0...size).each do |row|
(0...size).each do |col|
letter = chosen_letters.shift
#grid[row][col] = Card.new(letter)
end
end
It might be a little cleaner to start with an array of cards, e.g. by creating a pair of two cards for each letter:
cards = letters_range.flat_map { |letter| [Card.new(letter), Card.new(letter)] }
cards.shuffle!
You can then assign the cards to the grid via:
(0...size).each do |row|
(0...size).each do |col|
#grid[row][col] = cards.shift
end
end
or you could build the #grid right out of the array via each_slice:
#grid = cards.each_slice(size).to_a
Please bear with me...I need basic concepts...I am not aware of advanced prog concepts yet.
I have a class called Circle which initializes and calculates area
class Circle
def initialize (radius)
#radius = radius
end
def area
3.14*#radius*#radius
end
end
I want to take user input and create however many instances of Circle objects and its sides.
p "How many Circles"
i = gets.to_i
j = 1
while j != i+1
p "Enter radius of Circle #{j}"
$s << Circle.new(gets.to_i)
j = j +1
end
The $s[] now holds array of objects I created.
Now, I want to do something like,
area_array[] = 0
area_array[Circle1] = Circle1.area
area_array[Circle1] = Circle2.area
and so on...where Circle1 and Circle2 are the objects I created earlier in my while loop....
Can someone tell me how can I put each of the created object in another array and assign an area value to it?
Do you need another array because you will modify or destroy the properties of the Circles in the first array? If so, and you can rely on the Cirlces' order in the array remaining the same, then just use the index value to correlate the values:
circle_area_hash = $s.reduce{|a, c| a[c.object_id] = c.area }
Also, consider that for your analyses, you may care more about the values, than the objects, per se. So then you could create
circle_area_hash = $s.reduce do |a, c|
a[c.area] = (a[c.area].nil?) ? [c] : a[c.area] << c
end
This make the hash-keys bey the area value as, and the hash-values are each an array of the objects that have that area.
Then to get the key (largest area) you can:
circle_area_hash.max_by{|k,v| v.count}
Also, as a thought:
puts "How many Circles"
$s = (1...gets.to_i).each |j|
puts "Enter radius of Circle #{j}"
$s << Circle.new(gets.to_i)
end
$s[3].area
To create a new array of areas:
area_array = $s.map{ |circle| circle.area }
area_array = $s.map( &:area ) # Same thing, but shorter
To create one big hash:
areas = Hash[ $s.map{ |circle| [ circle, circle.area ] } ]
This creates an array of arrays like:
[
[ <Circle #radius=3>, 28.27 ],
[ <Circle #radius=4>, 50.27 ],
…
]
…and then uses the Hash.[] method to convert that into a Hash.
Another technique is:
areas = $s.inject({}){ |hash,circle| hash.merge(circle=>circle.area) }
For more details, read up on Array#map and Enumerable#inject.
However, why would you want to create this hash? It seems like you're perhaps wanting to only calculate the area once each. Although it's not needed for this simple calculation, you can memoize a method's return value with a pattern like this:
class Circle
def initialize(radius)
#radius = radius
end
def area
#area ||= Math::PI*#radius*#radius
end
end
This will calculate the area the first time it's needed, and store it in an instance variable; thereafter it will just use the value of that variable as the return value of the method, without needing to recalculate it.
This is very straightforward. You should just iterate over $s, using each element as a hash key and the result of its area as the corresponding value.
Another few points that should be useful to you:
You can use Math::PI instead of 3.14
You should only use p for debugging. It prints the result of the inspect method of its parameter, which is rarely what you want for tidy output. Use print if you want to make your newlines explicit in the string, or puts to append a newline if there isn't one already
It is rarely appropriate to use while in Ruby. In this instance you just want i.times do { ... }
class Circle
def initialize (radius)
#radius = radius
end
def area
Math::PI * #radius * #radius
end
end
print 'How many Circles: '
i = gets.to_i
shapes = []
i.times do |n|
print "Enter radius of Circle #{n+1}? "
shapes << Circle.new(gets.to_i)
end
area_hash = {}
shapes.each do |shape|
area_hash[shape] = shape.area
end
However it seems more appropriate to memoize the area method here, writing it as
def area
#area = Math::PI * #radius * #radius unless #area
#area
end
Then you can use the method repeatedly and the calculation will be done only once.
After reading your comment on NewAlexandria's answer, perhaps something like this would work for you:
p "How many Circles"
(1..gets.to_i) do |j|
c = Circle.new
p "Enter radius of Circle #{j}"
s[c] = c.area(gets.to_i)}
end
where s is a pre-defined hash that may contain keys for instances of other circles, rectangles, etc.
This only makes sense, however, if you plan to add additional constants or methods to your shape classes that you will want to reference with the keys of s.
You should edit your question to incorporate your comment above.
Find the below code:
all_links = driver.find_elements(:xpath,"//fieldset[contains(#class,'attachmentTable')]/table/tbody/tr/td/a")
all_attachment_names = driver.find_elements(:xpath,"//fieldset[contains(#class,'attachmentTable')]/legend")
all_links.each do|link|
href = link.attribute("href").strip
puts href
end
all_attachment_names.each do |name|
text = name.attribute("text")
puts text
end
Can we make these two loop combined means to run them parallel?
provided that both of loop count is same.
I want to create hash where key will be text and item will be
href.
You can use zip to do this:
returned_hash = {}
all_links.zip(all_attachment_names) do |link, name|
returned_hash[name.text] = link.attribute("href").strip
end
You can also do it in a functional programming style by extracting the href and text with map:
hrefs = all_links.map{|link| link.attribute("href").strip}
names = all_attachment_names.map{|name| name.text}
returned_hash = Hash[names.zip(hrefs)]
Doing so is (arguably) more aesthetically pleasing but somewhat less efficient because it requires twice as many iterations, and creates a couple of extra arrays, but unless you have an enormous number of links that's not going to be an issue.
map = {}
all_attachment_names.zip(all_links) do |a, l|
map[a] = l
end
I have lots of math to do on lots of data but it's all based on a few base templates. So instead of say, when doing math between 2 arrays I do this:
results = [a[0]-b[1],a[1]-b[2],a[2]-b[3]]
I want to instead just put the base template: a[0]-b[1] and make it automatically fill say 50 places in the results array. So I don't always have to manually type it.
What would be the ways to do that? And would a good way be to create 1 method that does this automatically. And I just tell it the math and it fills out an array?
I have no clue, I'm really new to programming.
a = [2,3,4]
b = [1,2,3,4]
results = a.zip(b.drop(1)).take(50).map { |v,w| v - w }
Custom
a = [2,3,4..............,1000]
b = [1,2,3,4,.............900]
class Array
def self.calculate_difference(arr1,arr2,limit)
begin
result ||= Array.new
limit.send(:times) {|index| result << arr1[index]-arr2[index+=1]}
result
rescue
raise "Index/Limit Error"
end
end
end
Call by:
Array.calculate_difference(a,b,50)