The expression
[1, 2, 3] == [1, 2, 3]
evaluates to false in Coffeescript but is there a concise, idiomatic way to test array equality?
If you are dealing with arrays of numbers, and you know that there are no nulls or undefined values in your arrays, you can compare them as strings:
a = [1, 2, 3]
b = [1, 2, 3]
console.log "#{a}" is "#{b}" # true
console.log '' + a is '' + b # true
Notice, however, that this will break as soon as you start comparing arrays of other things that are not numbers:
a = [1, 2, 3]
b = ['1,2', 3]
console.log "#{a}" is "#{b}" # true
If you want a more robust solution, you can use Array#every:
arrayEqual = (a, b) ->
a.length is b.length and a.every (elem, i) -> elem is b[i]
console.log arrayEqual [1, 2, 3], [1, 2, 3] # true
console.log arrayEqual [1, 2, 3], [1, 2, '3'] # false
console.log arrayEqual [1, 2, 3], ['1,2', 3] # false
Notice that it's first comparing the lengths of the arrays so that arrayEqual [1], [1, 2, 3] doesn't return true.
If you don't mind introducing an Underscore.js dependency you could use some of it's utilities. It's not massively elegant, but I can't think of an easier way to do it with plain coffeescript:
a = [ 1, 2, 3 ]
b = [ 1, 2, 3 ]
equal = a.length == b.length and _.all( _.zip( a, b ), ([x,y]) -> x is y )
I wouldn't consider this idiomatic but this would be a way of doing it without adding an extra library:
a = [1, 2, 3, 4]
b = [22, 3, 4]
areEqual = true
maxIndex = Math.max(a.length, b.length)-1
for i in [0..maxIndex]
testEqual = a[i] is b[i]
areEqual = areEqual and testEqual
console.log areEqual
A cleaner approach would be using JavaScript's reduce() function. This is a bit shorter but I am not sure all browsers support reduce.
a = [1, 3, 4, 5]
b = [1, 3, 4, 5]
maxIndex = Math.max(a.length, b.length)-1
areEqual = true
[0..maxIndex].reduce (p, c, i, ar) -> areEqual = areEqual and (a[i] is b[i])
console.log "areEqual=#{areEqual}"
The following works great and requires no dependencies:
arrayEqual = (ar1, ar2) ->
JSON.stringify(ar1) is JSON.stringify(ar2)
I'm a big fan of Sugar.js. If you happen to be using that:
a = [1, 2, 3]
b = [1, 2, 3]
Object.equal(a, b)
This function returns true if arrays have same length and all values with same index have same value. It throws an error if either argument isn't an array.
isArray = Array.isArray || (subject) ->
toString.call(subject) is '[object Array]'
compareArrays = (a, b) ->
unless isArray(a) and isArray b
throw new Error '`arraysAreEqual` called with non-array'
return false if a.length isnt b.length
for valueInA, index in a
return false if b[index] isnt valueInA
true
Related
Suppose that you need to get all the elements that have the max value in an array.
A possible method would be to sort the array then use Enumerable#take_while:
array = [ 1, 3, 2, 3, 2, 3 ].sort {|a,b| b - a}
array.take_while { |e| e == array[0] }
#=> [3, 3, 3]
Now, when you are beautifully chaining methods and don't want to stop the chain just for storing the sorted array (which you'll need for referencing its first element in the take_while block), how would you do it?
I posted the question and an answer below for reference, but I probably missed better ways, so feel free to post your own method
Another way:
arr = [ 1, 3, 2, 3, 2, 3 ]
arr.sort {|a,b| b - a}.tap { |a| a.select! { |e| e == a.first } }
#=> [3, 3, 3]
Note that arr is not mutated.
ruby < 2.5
My original response to the question: sort.slice_when.first
[ 1, 3, 2, 3, 2, 3 ].sort {|a,b| b - a}.slice_when {|a,b| b != a}.first
#=> [3, 3, 3]
note: As slice_when returns an Enumerator, this solution won't walk through all the sorted array when chaining it with first. There is a more performant solution below tough.
ruby >= 2.5
Combining #engineersmnky and #Cary methods: then and max+select
[ 1, 3, 2, 3, 2, 3 ].then { |arr| mx = arr.max; arr.select { |elm| elm == mx } }
#=> [3, 3, 3]
You can try this
pry(main)> [ 1, 2, 2, 3, 3, 3 ].sort.slice_when {|a,b| b > a}.to_a.last
=> [3, 3, 3]
A bit similar of the last solution but also different.
Source https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-slice_when
I'm new here and new to coding and I can use some help with a problem I'm trying to solve.
I'm trying to remove all integers that are less than 5 from array
a = [1, 2, 3, 4, 5, 6] and put them into a new array b = [], and then print out the b array.
I've done many Google searches but I can't find anything that helps.
I'm starting to think this is not possible.
Please help!
Thanks
a = [1, 2, 3, 4, 5, 6]
b, a = a.partition { |i| i < 5 }
#=> [[1, 2, 3, 4], [5, 6]]
b #=> [1, 2, 3, 4]
a #=> [5, 6]
See Enumerable#partition.
a = [1, 2, 3, 4, 5, 6]
b = a.select { |i| i < 5 } # [1, 2, 3, 4]
a = a - b # [5, 6]
To actually remove elements from a while putting them in an existing array b, you could use reject!:
a = [1, 2, 3, 4, 5, 6]
b = []
a.reject! { |i| b << i if i < 5 }
a #=> [5, 6]
b #=> [1, 2, 3, 4]
If i < 5 evaluates to true, b << i puts that element in b and returns a truthy result which causes reject! to remove it from a.
Likewise, if i < 5 evaluates to false, b << i is skipped, the block returns a falsy result and that element remains in a.
I need to implement Array#flatten. This implementation removes all nested arrays:
a = [1, 2, [3, [4, 5]]]
def my_flatten(arr)
arr.reduce([]) do |result, item|
item.is_a?(Array) ? result + my_flatten(item) : result << item
end
end
my_flatten(a) #=> [1, 2, 3, 4, 5]
Prompt how to implement such behavior
a.flatten(1) #=> [1, 2, 3, [4, 5]]
Introduce a parameter to specify the max depth (defaulting to nil) and a parameter to keep track of the current depth (0 on the initial call and then incremented by 1 on each recursive call):
def my_flatten(arr, max_depth = nil, current_depth = 0)
arr.reduce([]) do |result, item|
item.is_a?(Array) && (max_depth.nil? || current_depth < max_depth) ?
result + my_flatten(item, max_depth, current_depth + 1) : result << item
end
end
You could replace the ?: with an if/else if you felt this was more readable:
def my_flatten(arr, max_depth = nil, current_depth = 0)
arr.reduce([]) do |result, item|
if item.is_a?(Array) && (max_depth.nil? || current_depth < max_depth)
result + my_flatten(item, max_depth, current_depth + 1)
else
result << item
end
end
end
This now returns the expected results:
my_flatten(a) #=> [1, 2, 3, 4, 5]
my_flatten(a, 1) #=> [1, 2, 3, [4, 5]]
I created something similar a long time back. Here is the gist link.
Code from gist:
class Array
def my_flatten(level = nil)
rb_flatten(self, [], level)
end
private
# apply recursion based on the level
# when no level provided, then produce a complete flatten array
# when level is given, then produce a flatten array flattened till that certain level
def rb_flatten(array, result, level)
array.each do |value|
if ((value.is_a? Array) && (level.nil? || (level && level > 0)))
rb_flatten(value, result, (level.nil? ? level : ((level || 0 ) - 1)))
else
result << value
end
end
return result
end
end
Hope that helps.
you can also use Proc like this.
class Array
def my_flatten(level = nil)
p = ->(arr, exp, lvl) do
arr.each { |val| Array === val && (!level || lvl < level) ? p.(val, exp, lvl+1) : exp << val }
exp
end
p.(self, [], 0)
end
end
a = [1, 2, [3, [4, 5]]]
p a.my_flatten
# => [1, 2, 3, 4, 5]
p a.my_flatten(0)
# => [1, 2, [3, [4, 5]]]
p a.my_flatten(1)
# => [1, 2, 3, [4, 5]]
p a.my_flatten(2)
# => [1, 2, 3, 4, 5]
There are two arrays:
A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
B = [3, 4, 1, 5, 2, 6]
I want to sort B in a way that for all the elements of B that exists in A, sort the elements in the order that is in array A.
The desired sorted resulted would be
B #=> [1, 2, 3, 4, 5, 6]
I have tried to do
B = B.sort_by { |x| A.index }
but it does not work.
This question differs from the possible duplicates because it deals with presence of elements in the corresponding array and no hashes are present here.
It perfectly works:
▶ A = [1,3,2,6,4,5,7,8,9,10]
▶ B = [3,4,1,5,2,6]
▶ B.sort_by &A.method(:index)
#⇒ [1, 3, 2, 6, 4, 5]
If there could be elements in B that are not present in A, use this:
▶ B.sort_by { |e| A.index(e) || Float::INFINITY }
I would start by checking what elements from B exist in A :
B & A
and then sort it:
(B & A).sort_by { |e| A.index(e) }
First consider the case where every element of B is in A, as with the question's example:
A = [1,2,3,4,5,6,7,8,9,10]
B = [3,6,1,5,1,2,1,6]
One could write the following, which requires only a single pass through A (to construct g1) and a single pass through B.
g = A.each_with_object({}) { |n,h| h[n] = 1 }
#=> {1=>1, 2=>1, 3=>1, 4=>1, 5=>1, 6=>1, 7=>1, 8=>1, 9=>1, 10=>1}
B.each_with_object(g) { |n,h| h[n] += 1 }.flat_map { |k,v| [k]*(v-1) }
#=> [1, 1, 1, 2, 3, 5, 6, 6]
If there is no guarantee that all elements of B are in A, and any that are not are to be placed at the end of the sorted array, one could change the calculation of g slightly.
g = (A + (B-A)).each_with_object({}) { |n,h| h[n] = 1 }
This requires one more pass through A and through B.
Suppose, for example,
A = [2,3,4,6,7,8,9]
and B is unchanged. Then,
g = (A + (B-A)).each_with_object({}) { |n,h| h[n] = 1 }
#=> {2=>1, 3=>1, 4=>1, 6=>1, 7=>1, 8=>1, 9=>1, 1=>1, 5=>1}
B.each_with_object(g) { |n,h| h[n] += 1 }.flat_map { |k,v| [k]*(v-1) }
#=> [2, 3, 6, 6, 1, 1, 1, 5]
This solution demonstrates the value of a controversial change to hash properties that were made in Ruby v1.9: hashes would thereafter be guaranteed to maintain key-insertion order.
1 I expect one could write g = A.product([1]).to_h, but the doc Array#to_h does not guarantee that the keys in the hash returned will have the same order as they do in A.
You just missed x in A.index, so the query should be:
B = B.sort_by { |x| A.index(x) }
Let's take the following Array:
[1, 4, 5, 3, 1, 4, 6, 5, 4]
It has the following turning points (when rise changes to fall, or vice versa):
5 (at index 2)
1 (at index 4)
6 (at index 6)
To make task more general:
There is an Array a = [a1, a2, ...]
There is function p(x,y) -> z, where z is Comparable
How to get all elements ai ∈ a (0 < i < a.length-1) for which p(ai-1, ai) != p(ai, ai+1)
I would like to write something like:
a.detect_edges{|prev, n| prev >= n} # => [[5,2], [1, 4], [6,6]]
What's the most elegant way to get those turning points with their respective indexes? Here's my code with which I'm not satisfied from the aesthetic point of view:
class Array
def detect_edges(&blk)
return nil if self.length < 2
prev = blk.call(self[0], self[1])
result = []
self[0..-2].each_with_index do |elem, i|
current = blk.call(elem, self[i+1])
if current != prev
result.push [elem, i]
end
prev = current
end
result
end
end
[1, 4, 5, 3, 1, 4, 6, 5, 4]
.each_cons(3).with_index(1)
.reject{|(e1, e2, e3), i| (e1 <=> e2) == (e2 <=> e3)}
.map{|(e1, e2, e3), i| [e2, i]}
# => [[5, 2], [1, 4], [6, 6]]
Look ma, no map!
a = [1, 4, 5, 3, 1, 4, 6, 5, 4]
a[1..-2].each.with_index(1).reject { |e,i| (a[i-1]<=>e) == e<=>a[i+1] }
#=> [[5, 2], [1, 4], [6, 6]]
So you basically want the elements and their indices, where the element is the local max in a 1 index range:
arr.each.with_index.select { |element, index| element == arr[index.pred..index.next].max }
# => [[5, 2], [6, 6]]
Note, you have to handle the case for the first element or if elements are equal.
EDIT: for your updated version, you just have to check if the result of <=> has changed. Note that you will again have to check the case when elements are equal:
arr.each.with_index.to_a.tap(&:pop).drop(1).select do |element, index|
(arr[index.pred] <=> element) != (element <=> arr[index.next])
end # => [[5, 2], [1, 4], [6, 6]]
I don't see reason to get more fancy than:
class Array
def detect_edges
self.collect.with_index do |e, i|
next if i == 0 || i >= size-1
yield(self[i-1],e) != yield(e,self[i+1]) ? [e, i] : nil
end.compact
end
end
Note that when patching Array one should probably use refinements.