Delete duplicate entries in Ruby - ruby

What is the command for deleting duplicate elements in an array? This is my best try:
my_array.reject.with_string{s.clone}

If you want an array of unique values of my_array = [1, 2, 3, 3, 4], then do this:
my_array.uniq
# => [1, 2, 3, 4]
If your array contains objects with some field that you want to be unique, for example, :fname in:
my_array = [
{fname: "amanze", age: 28},
{fname: "ben", age: 13},
{fname: "ben", age: 4}
]
then you need to do this:
my_array.uniq { |obj| obj[:fname] }
# =>
# [
# {fname: "amanze", age: 28},
# {fname: "ben", age: 13}
# ]

Array#uniq is the best way to find out the uniq records, but as an alternate, you can use Array#&, which returns a new array containing the elements common to the two arrays, excluding any duplicates.
a = [1, 2, 3, 4, 5, 2, 2, 3, 4]
b = a & a
b #=> [1, 2, 3, 4, 5]

Related

How to sort only specific elements in an array?

I have an array of mixed elements, e.g. integers and strings:
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
and I want to apply a sort but only to specific elements. In the above example, the elements to-be-sorted are the integers (but it could be anything). However, even though they should be sorted, they have to stay in their "integer spots". And the strings have to remain in their exact positions.
The sort should work like this:
[3, "foo", 2, 5, "bar", 1, "baz", 4] # before
[1, "foo", 2, 3, "bar", 4, "baz", 5] # after
"foo" is still at index 1, "bar" is at index 4 and "baz" is at index 6.
I could partition the array into integers and non-integers along with their positions:
a, b = ary.each_with_index.partition { |e, i| e.is_a?(Integer) }
a #=> [[3, 0], [2, 2], [5, 3], [1, 5], [4, 7]]
b #=> [["foo", 1], ["bar", 4], ["baz", 6]]
sort the integers:
result = a.map(&:first).sort
#=>[1, 2, 3, 4, 5]
And re-insert the non-integers at their original positions:
b.each { |e, i| result.insert(i, e) }
result
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
But this seems rather clumsy. I particular dislike having to deconstruct and rebuild the array one string at a time. Is there a more elegant or more direct approach?
Possible solution
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
integers = ary.select(&->(el) { el.is_a?(Integer) }).sort
ary.map { |n| n.is_a?(Integer) ? integers.shift : n }
# => [1, "foo", 2, 3, "bar", 4, "baz", 5]
I am not proficient with Ruby. Though I'd like to take a shot at what I could come up with.
The idea is as evoked in my comment.
get the indices of the integers
sort the values of the indices
insert the sorted values back into array
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
=> [3, "foo", 2, 5, "bar", 1, "baz", 4]
indices = ary.map.with_index { |item,idx| idx if item.is_a?(Integer) }.compact
=> [0, 2, 3, 5, 7]
values = ary.values_at(*indices).sort
=> [1, 2, 3, 4, 5]
indices.zip(values).each { |idx, val| ary[idx]=val}
=> [[0, 1], [2, 2], [3, 3], [5, 4], [7, 5]]
ary
=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
I have assumed that, as in the example, if arr = ary.dup and arr is modified ary is not mutated. If that is not the case one must work with a deep copy of ary.
A helper:
def sort_object?(e)
e.class == Integer
end
sort_object?(3)
#=> true
sort_object?(-3e2)
#=> true
sort_object?("foo")
#=> false
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
obj_to_idx = ary.zip((0..ary.size-1).to_a).to_h
#=> {3=>0, "foo"=>1, 2=>2, 5=>3, "bar"=>4, 1=>5, "baz"=>6, 4=>7}
to_sort = ary.select { |k| sort_object?(k) }
#=> [3, 2, 5, 1, 4]
sorted = to_sort.sort
#=> [1, 2, 3, 4, 5]
new_idx_to_orig_idx = to_sort.map { |n| obj_to_idx[n] }
.zip(sorted.map { |n| obj_to_idx[n] })
.to_h
#=> {0=>5, 2=>2, 3=>0, 5=>7, 7=>3}
new_idx_to_orig_idx.each_with_object(ary.dup) do |(new_idx,orig_idx),a|
a[new_idx] = ary[orig_idx]
end
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
Some of these statements may of course be chained if desired.
In-Place Re-Assignment at Designated Array Indices
You could certainly make this shorter, and perhaps even skip converting things to and from Hash objects, but the intermediate steps there are to show my thought process and make the intent more explicit and debugging a bit easier. Consider the following:
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
ints = ary.each_with_index.select { |elem, idx| [elem, idx] if elem.kind_of? Integer }.to_h
ordered_ints = ints.keys.sort.zip(ints.values).to_h
ints.keys.sort.zip(ints.values).each { |elem, idx| ary[idx] = elem }
ary
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
The idea here is that we:
Select just the items of the type we want to sort, along with their index within the current Array using #each_with_index. If you don't like #kind_of? you could replace it with #respond_to? or any other selection criteria that makes sense to you.
Sort Array values selected, and then #zip them up along with the index locations we're going to modify.
Re-assign the sorted elements for each index that needs to be replaced.
With this approach, all unselected elements remain in their original index locations within the ary Array. We're only modifying the values of specific Array indices with the re-ordered items.
I will throw my hat in this ring as well.
It appears the other answers rely on the assumption of uniqueness so I took a different route by building a positional transliteration Hash
(Update: took it a little further than necessary)
def sort_only(ary, sort_klass: Integer)
raise ArgumentError unless sort_klass.is_a?(Class) && sort_klass.respond_to?(:<=>)
# Construct a Hash of [Object,Position] => Object
translator = ary
.each_with_index
.with_object({}) {|a,obj| obj[a] = a.first}
.tap do |t|
# select the keys where the value (original element) is a sort_klass
t.filter_map {|k,v| k if v.is_a?(sort_klass)}
.then do |h|
# sort them and then remap these keys to point at the sorted value
h.sort.each_with_index do |(k,_),idx|
t[h[idx]] = k
end
end
end
# loop through the original array with its position index and use
# the transliteration Hash to place them in the correct order
ary.map.with_index {|a,i| translator[[a,i]]}
end
Example: (Working Example)
sort_only([3, "foo", 2, 5, "bar", 1, "baz", 4])
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
sort_only([3, 3, "foo", 2, 5, "bar", 1, "baz",12.0,5,Object, 4,"foo", 7, -1, "qux"])
#=> [-1, 1, "foo", 2, 3, "bar", 3, "baz", 12.0, 4, Object, 5, "foo", 5, 7, "qux"]
sort_only([3, "foo", 2, 5,"qux", "bar", 1, "baz", 4], sort_klass: String)
#=> [3, "bar", 2, 5, "baz", "foo", 1, "qux", 4]
For Reference the Second Example produces the following transliteration:
{[3, 0]=>-1, [3, 1]=>1, ["foo", 2]=>"foo", [2, 3]=>2, [5, 4]=>3,
["bar", 5]=>"bar", [1, 6]=>3, ["baz", 7]=>"baz", [12.0, 8]=>12.0,
[5, 9]=>4, [Object, 10]=>Object, [4, 11]=>5, ["foo", 12]=>"foo",
[7, 13]=>5, [-1, 14]=>7, ["qux", 15]=>"qux"}
Below code simply creates two new arrays: numbers and others. numbers are Integer instances from original array, later sorted in descending order. others looks like original array but number spots replaced with nil. at the end push that sorted numbers to nil spots
arr.inject([[], []]) do |acc, el|
el.is_a?(Integer) ? (acc[0]<<el; acc[1]<<nil) : acc[1]<<el; acc
end.tap do |titself|
numbers = titself[0].sort_by {|e| -e }
titself[1].each_with_index do |u, i|
titself[1][i] = numbers.pop unless u
end
end.last
I'm going to answer my own question here with yet another approach to the problem. This solution delegates most work to Ruby's built-in methods and avoids explicit blocks / loops as far as possible.
Starting with the input array:
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
You could build a hash of position => element pairs:
hash = ary.each_index.zip(ary).to_h
#=> {0=>3, 1=>"foo", 2=>2, 3=>5, 4=>"bar", 5=>1, 6=>"baz", 7=>4}
extract the pairs having integer value: (or whatever you want to sort)
ints_hash = hash.select { |k, v| v.is_a?(Integer) }
#=> {0=>3, 2=>2, 3=>5, 5=>1, 7=>4}
sort their values: (any way you want)
sorted_ints = ints_hash.values.sort
#=> [1, 2, 3, 4, 5]
build a new mapping for the sorted values:
sorted_ints_hash = ints_hash.keys.zip(sorted_ints).to_h
#=> {0=>1, 2=>2, 3=>3, 5=>4, 7=>5}
update the position hash:
hash.merge!(sorted_ints_hash)
#=> {0=>1, 1=>"foo", 2=>2, 3=>3, 4=>"bar", 5=>4, 6=>"baz", 7=>5}
And voilà:
hash.values
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]

Creating two arrays containing an original array with different values added at the end

So I am trying to build a multidimensional array by starting with a single array and splitting it into separate arrays to account for possible added values.
Example:
Original Array: [2,3]
adding either 4 or 5
New array: [[2,3,4],[2,3,5]]
I have tried the following:
array=[2,3]
array1=array<<4
array2=array<<5
array=[2,3]
array1=array<<4
array.pop
array2=array<<5
array=[2,3]
array1=array.push 4
array.pop
array2=array.push 5
The results I get are:
[[2,3,4,5],[2,3,4,5]]
[[2,3,5],[2,3,5]]
[[2,3,5],[2,3,5]]
Is there a way to alter the original array only in the new variables so that the variables don't end up equal when I combine them?
There are a number of methods on Array that are in-place modifiers, that is they don't make copies, and << is one of them.
What you might find easier is this:
array = [ 2, 3 ]
array1 = array + [ 4 ]
array2 = array + [ 5 ]
The result in this case is two independent arrays.
Another interesting way to do this is using the splat operator:
array = [2, 3]
array1 = [*array, 4]
# => [2, 3, 4]
array2 = [*array, 5]
# => [2, 3, 5]
If you have several times to add to:
array = [1, 2, 3]
say:
b = [*(4..20)]
#=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
you could use the method Array#product:
[array].product(b).map(&:flatten)
#=> [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 3, 6], [1, 2, 3, 7],
# [1, 2, 3, 8], [1, 2, 3, 9], [1, 2, 3, 10], [1, 2, 3, 11],
# [1, 2, 3, 12], [1, 2, 3, 13], [1, 2, 3, 14], [1, 2, 3, 15],
# [1, 2, 3, 16], [1, 2, 3, 17], [1, 2, 3, 18], [1, 2, 3, 19],
# [1, 2, 3, 20]]

Conditionally inserting data into an array

Alright, can someone help me how to properly iterate a dynamic size array?
Here's what I mean:
my_array = [1, 2, 3, 4, 2, 5, 15, 2] # <= there are three 2 inside my_array
Then I would like to add "good" everytime the iteration hit integer 2, I tried several method but not find the way, here's the best method I've tried(still not resulting in what I want)
# First Method
for i in 0..(my_array.length - 1)
my_array.insert(i + 1, "good") if my_array[i] == 2
end
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2]
# Second Method
for i in 0..(my_array.length - 1)
my_array[i + 1] = "good" if my_array[i] == 2
end
p my_array # => [1, 2, "good", 4, 2, "good", 15, 2, "good"]
The first method is not good because it's not showing "good" after the last 2, I guess this because the iteration could not reach the last integer 2(in the last array) and that is expected because the array size is changed bigger everytime "good" is inserted.
The second one is also bad, because I replace the data after every 2 with "good" string.
Now can someone point it out to me how can I doing this properly so I can produce it like this:
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
All "good" is added without replacing any data.
Any help is appreciated, thank you very much.
You'd have a better time transforming this into a new array than modifying in-place:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
def add_good(a)
a.flat_map do |value|
case (value)
when 2
[ 2, 'good' ]
else
value
end
end
end
puts add_good(my_array).inspect
# => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
The flat_map method is useful for situations where you want to create zero or more entries in the resulting array. map is a 1:1 mapping, flat_map is a 1:N.
It's also possible to make this much more generic:
def fancy_insert(a, insert)
a.flat_map do |value|
if (yield(value))
[ value, insert ]
else
value
end
end
end
result = fancy_insert(my_array, 'good') do |value|
value == 2
end
puts result.inspect
That way you can pass in an arbitrary block and value to be inserted.
Why not using the .each_with_index method:
arr = [1, 2, 3, 4, 5, 2, 3, 5]
arr.each_with_index {|e, i| arr.insert(i+1, "good") if e == 2}
Fast and furious.
Here's another way you can do it, using an Enumerator:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
enum = my_array.each
#=> #<Enumerator: [1, 2, 3, 4, 2, 5, 15, 2]:each>
my_array = []
loop do
x = enum.next
my_array << x
my_array << "good" if x == 2
end
my_array
#=> [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
Enumerator#next raises a StopInteration exception when the enumerator is already on the last element. Kernel#loop handles the exception by breaking the loop. That's why you will often see loop used when stepping through an enumerator.

Built in way of doing successive Ruby evals on the same object

Edit: I just realized my prior example was bad.
Borrowing #ZachKemp's idea (and changing it):
class Object
def eval_multi(*methods)
#methods.inject(self) { |memo, m| memo.send(m) }
methods.inject(self) { |memo, m| eval("#{memo}.#{m}") }
end
end
[1,2,3].eval_multi("product([4,5,6])", :transpose)
=> [[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]
I'm wondering if there's a built-in way of doing this without having to write the eval_multi method above.
You can do this with inject:
module Enumerable
def multimap(*methods)
methods.inject(self){|result, method| result.map(&method) }
end
end
arr = ["1 a", "1 b", "2 c", "2 a"]
arr.multimap(:split, :reverse, :join)
#=> ["a1", "b1", "c2", "a2"]
(I renamed the method because eval already has another meaning in Ruby).
You could just use one map, simple and clean.
arr.map{ |o| o.split.reverse.join }
You can do it with a little change in the way you pass the arguments.
[[:product, [4,5,6]], [:transpose]].inject([1,2,3]){|m, a| m.send(*a)}
# => [[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]
Or, by modifying send a little bit, you can do it like this:
class Object
def send_splat a; send(*a) end
end
[[:product, [4,5,6]], [:transpose]].inject([1,2,3], &:send_splat)
# => [[1, 1, 1, 2, 2, 2, 3, 3, 3], [4, 5, 6, 4, 5, 6, 4, 5, 6]]

Subtracting one Array from another in Ruby

I've got two arrays of Tasks - created and assigned.
I want to remove all assigned tasks from the array of created tasks.
Here's my working, but messy, code:
#assigned_tasks = #user.assigned_tasks
#created_tasks = #user.created_tasks
#Do not show created tasks assigned to self
#created_not_doing_tasks = Array.new
#created_tasks.each do |task|
unless #assigned_tasks.include?(task)
#created_not_doing_tasks << task
end
end
I'm sure there's a better way. What is it?
Thanks :-)
You can subtract arrays in Ruby:
[1,2,3,4,5] - [1,3,4] #=> [2,5]
ary - other_ary → new_ary Array Difference
Returns a new array that is a copy of the original array, removing any
items that also appear in other_ary. The order is preserved from the
original array.
It compares elements using their hash and eql? methods for efficiency.
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
If you need
set-like behavior, see the library class Set.
See the Array documentation.
The above solution
a - b
deletes all instances of elements in array b from array a.
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
In some cases, you want the result to be [1, 2, 3, 3, 5]. That is, you don't want to delete all duplicates, but only the elements individually.
You could achieve this by
class Array
def delete_elements_in(ary)
ary.each do |x|
if index = index(x)
delete_at(index)
end
end
end
end
test
irb(main):198:0> a = [ 1, 1, 2, 2, 3, 3, 4, 5 ]
=> [1, 1, 2, 2, 3, 3, 4, 5]
irb(main):199:0> b = [ 1, 2, 4 ]
=> [1, 2, 4]
irb(main):200:0> a.delete_elements_in(b)
=> [1, 2, 4]
irb(main):201:0> a
=> [1, 2, 3, 3, 5]
The code works even when the two arrays are not sorted. In the example, the arrays are sorted, but this is not required.

Resources