I have an array date_array. If i is 0, I'd like date_array[i-1] to return nil or an exception. I thought I could derive a subclass of Array for this, but I'm not sure where to go after that. Any ideas?
You could do this, but you shouldn't need to. You're going about this the wrong way, I feel, since the responsibility is yours, not Array's, to check the index value being passed.
Merely an example
def get_array_value (i)
return data_array[i - 1] unless i < 0
return nil
end
However, if you insist, this solution may work for you.
# arr = SpecialArray.new([1, 2, 3, 4, 5])
class SpecialArray < Array
def [](i)
return super(i) unless i < 0
return nil
end
end
Well, I agree with Adam, it'd be ideal to control the indexes instead of doing this:
class Ary < Array
def [](i)
return nil if i < 0
super
end
end
a = Ary.new([1, 2, 3])
b = Array.new([1, 2, 3])
#try access with -1 (normally would show last)
p a[-1] #=> nil
p b[-1] #=> 3
I think you may be doing things incorrectly. That being said:
You could use a hash instead of an array. Hashes won't re-interpret hash[-1] to mean something else.
Related
I'm working on creating a method that pads an array, and accepts 1. a desired value and 2. an optional string/integer value. Desired_size reflects the desired number of elements in the array. If a string/integer is passed in as the second value, this value is used to pad the array with extra elements. I understand there is a 'fill' method that can shortcut this - but that would be cheating for the homework I'm doing.
The issue: no matter what I do, only the original array is returned. I started here:
class Array
def pad(desired_size, value = nil)
desired_size >= self.length ? return self : (desired_size - self.length).times.do { |x| self << value }
end
end
test_array = [1, 2, 3]
test_array.pad(5)
From what I researched the issue seemed to be around trying to alter self's array, so I learned about .inject and gave that a whirl:
class Array
def pad(desired_size, value = nil)
if desired_size >= self.length
return self
else
(desired_size - self.length).times.inject { |array, x| array << value }
return array
end
end
end
test_array = [1, 2, 3]
test_array.pad(5)
The interwebs tell me the problem might be with any reference to self so I wiped that out altogether:
class Array
def pad(desired_size, value = nil)
array = []
self.each { |x| array << x }
if desired_size >= array.length
return array
else
(desired_size - array.length).times.inject { |array, x| array << value }
return array
end
end
end
test_array = [1, 2, 3]
test_array.pad(5)
I'm very new to classes and still trying to learn about them. Maybe I'm not even testing them the right way with my test_array? Otherwise, I think the issue is I get the method to recognize the desired_size value that's being passed in. I don't know where to go next. Any advice would be appreciated.
Thanks in advance for your time.
In all 3 of your tries, you are returning the original array if desired_size is greater than the original array size. You have that backwards. In other words, you just return instead of padding.
Your first attempt was close. You need to:
1) Fix your conditional check.
2) It's OK to modify the self array, so the more complicated tries are not necessary.
3) Make sure you return self no matter what you do.
By modifying self, not only do you return the modified array, but you also change the array held by the variable test_array. So if you were to do:
test_array = [1, 2, 3]
puts test_array.pad(5, 4).inspect // prints [1, 2, 3, 4, 4]
puts test_array // prints [1, 2, 3, 4, 4]
In Ruby, when a function modifies self, the function name ends with a !, so if you were to write it modifying self, it would be better to name it pad!.
If you want to write it so that it doesn't modify self, you could start with:
array = self.dup
and then do all of your operations on array.
I'm looking for something similar to #detect in enumerables, but not quite. This is what enumerable does:
[1, 2, 3].detect {|i| i > 1 } #=> 2
it returns the first instance of the array which matches the condition. Now, my purpose is to return the value of the block. Concern is not exactly the conditions, but for instance, the first which is not nil. Something like this:
[var1, var2, var3].wanted_detect {|var| another_function(var) }
in which the function would return the first result of another_function call which isn't nil.
Mapping the values of applying the method on the variables and then using detect is not an option. This one would ideally have to work in lazy enumerators, for which the early mapping of all possible values is a no-go
[var1, var2, var3].lazy.map { |var| another_function(var) }.reject(&:nil?).first
If you don't have access to Enumerable#lazy, it is easy enough to implement what you want:
module Enumerable
def wanted_detect
self.each do |obj|
val = yield obj
return val if val
end
end
end
Demo:
[1, 2, 3, 4].wanted_detect { |x| x*x if x > 2 }
# => 9
EDIT: Sorry, I missed the last paragraph till falsetru pointed it out.
Thanks for the comments, falsetru.
I have a class Test:
class Test
attr_accessor :data
def initialize
#data = [0, 1, 2, 3]
end
def map
#data.map!{|i| i = yield i }
end
end
and I attempt to use it like:
a = Test.new
a.map{|i|
if(i==2)
i+=1
break i #<--- -This line is the focus
else
1
end
}
puts a.data
The output I expect is [1, 1, 3, 3]. Instead, I get [1, 1, 2, 3]. The last iteration of the block in map doesn't return the modified i.
I replaced break i with next i. This performed as I expected, and produced the output [1, 1, 3, 1].
How can I modify this piece of code (or, ideally the line I point out in my second code-snippet) so that I would get the output [1, 1, 3, 3]? In other words, how can I make the block finish, but pass one last value back to map? Is there a neat and readable way to do this (besides, say, toggling a boolean flag break_now)?
I'm assuming you're asking how to leave a block and make use of the last value that was calculated rather than how to calculate a specific set of numbers; for the latter, there is probably a clever one-liner.
How about something like this:
class Test
attr_accessor :data
def initialize
#data = [0, 1, 2, 3]
end
def modify
#data.map! {|i| yield i }
end
end
a = Test.new
a.modify do |i|
break i if #done
#done = i == 2
#done ? (i + 1) : 1
end
puts a.data
An additional thought—#map is an important method in Ruby with a specific interface. In your example you're violating the interface by modifying a field in Test. For this reason I've used the name #modify instead.
In general, you could get away with this by modifying the yielded values in place. For example, if your array consisted of Strings instead of Fixnums:
class Test
attr_accessor :data
def initialize
#data = %w{a b c d}
end
def map
#data.map! { |i| yield i }
end
end
a = Test.new
a.map do |i|
if i == 'c'
i.next!
break
else
'b'
end
end
p a.data #=> ["b", "b", "d", "d"]
The problem with your example is this:
Fixnum objects have immediate value. This means that when they are assigned or passed as parameters, the actual object is passed, rather than a reference to that object. Assignment does not alias Fixnum objects. There is effectively only one Fixnum object instance for any given integer value…
Fixnums can't be altered in-place, so your expression i += 1 in the lower block doesn't affect the value of i in the upper block. That's why you get 2 in your example but d in my example.
You have to do this:
a.map{ |i| (i % 2 == 0) ? i + 1 : i }
When you use map function you don't change 'a' variable, if you want change 'a' variable do this:
a.map!{ |i| (i % 2 == 0) ? i + 1 : i }
The new value of 'i' is the value return by the block, so don't do something like:
a.map{|i| i = 1 }
because if you do:
a.map{|i| i = 1; 5 }
the result will be:
[5, 5, 5, 5]
I can generate a few lines of code that will do this but I'm wondering if there's a nice clean Rubyesque way of doing this. In case I haven't been clear, what I'm looking for is an array method that will return true if given (say) [3,3,3,3,3] or ["rabbits","rabbits","rabbits"] but will return false with [1,2,3,4,5] or ["rabbits","rabbits","hares"].
Thanks
You can use Enumerable#all? which returns true if the given block returns true for all the elements in the collection.
array.all? {|x| x == array[0]}
(If the array is empty, the block is never called, so doing array[0] is safe.)
class Array
def same_values?
self.uniq.length == 1
end
end
[1, 1, 1, 1].same_values?
[1, 2, 3, 4].same_values?
What about this one? It returns false for an empty array though, you can change it to <= 1 and it will return true in that case. Depending on what you need.
I too like preferred answer best, short and sweet. If all elements were from the same Enumerable class, such as Numeric or String, one could use
def all_equal?(array) array.max == array.min end
I would use:
array = ["rabbits","rabbits","hares", nil, nil]
array.uniq.compact.length == 1
I used to use:
array.reduce { |x,y| x == y ? x : nil }
It may fail when array contains nil.
I have an array of objects that I need to sort by a position attribute that could be an integer or nil, and I need the objects that have the nil position to be at the end of the array. Now, I can force the position to return some value rather than nil so that the array.sort doesn't fail, but if I use 0 as this default, then it puts those objects at the front of the sort. What's the best way to to do this sort? should I just set the nil values to some ridiculously high number that is 'almost' always guaranteed to be at the end? or is there some other way i could cause the array.sort method to put the nil attribute objects at the end of the array? the code looks like this:
class Parent
def sorted_children
children.sort{|a, b| a.position <=> b.position}
end
end
class Child
def position
category ? category.position : #what should the else be??
end
end
now, if i make the 'else' something like 1000000000, then it's most likely gonna put them at the end of the array, but I don't like this solution as it's arbitrary
I would just tweak your sort to put nil items last. Try something like this.
foo = [nil, -3, 100, 4, 6, nil, 4, nil, 23]
foo.sort { |a,b| a && b ? a <=> b : a ? -1 : 1 }
=> [-3, 4, 4, 6, 23, 100, nil, nil, nil]
That says: if a and b are both non-nil sort them normally but if one of them is nil, return a status that sorts that one larger.
How about in Child defining <=> to be based on category.position if category exists, and sorting items without a category as always greater than those with a category?
class Child
# Not strictly necessary, but will define other comparisons based on <=>
include Comparable
def <=> other
return 0 if !category && !other.category
return 1 if !category
return -1 if !other.category
category.position <=> other.category.position
end
end
Then in Parent you can just call children.sort.
I handle these kinds of things like this:
children.sort_by {|child| [child.position ? 0 : 1,child.position || 0]}
To be fair, I'm not very familiar with Ruby, so take this as more of an algorithm idea rather than a code one... and rewrite the ?: operator as whatever Ruby has that's cleaner.
Can't you just check for nil in the comparison:
class Parent
def sorted_children
children.sort{|a,b|( a and b ) ? a <=> b : ( a ? -1 : 1 ) }
end
end
Edited to use Glenra's code, which implements the same thing as mine but in a smaller (and probably easier to read) amount of code.
The most simple solution for me is
def sorted_children(children)
children.sort_by { |child| child.position || -1}
end
I haven't done Ruby in a while, but you could split the null-checking from the sorting (and just allow Child#position to return null):
def sorted_children
children.reject{|c| c.position.nil?}.sort_by(&:position) +
children.select{|c| c.position.nil?}
end
Admittedly it's not the most efficient solution, but it doesn't have any magic numbers.
You can do this without overriding the spaceship operator by defining a new comparison method.
class Child
include Comparable
def compare_by_category(other)
return 0 if !category && !other.category
return 1 if !category
return -1 if !other.category
category.position <=> other.category.position
end
end
The sort method can take a block, so you can then sort using this new method:
children.sort {|a,b| a.compare_by_category(b) }