Is there a particular function to retrieve then delete random array element? - ruby

I know I can do this in a couple of steps, but was wondering if there is a function which can achieve this.
I want to array#sample, then remove the element which was retrieved.

How about this:
array.delete_at(rand(array.length))

Another inefficient one, but super obvious what's going on:
array.shuffle.pop
What would be nice would be a destructive version of the sample method on Array itself, something like:
class Array
def sample!
delete_at rand length
end
end

Linuxios's has it perfect. Here is another example:
array = %w[A B C]
item_deleted = array.delete_at(1)
Here it is in irb:
1.9.2p0 :043 > array = %w[A B C]
=> ["A", "B", "C"]
1.9.2p0 :044 > item_deleted = array.delete_at(1)
=> "B"
1.9.2p0 :045 > array
=> ["A", "C"]
1.9.2p0 :047 > item_deleted
=> "B"

An alternative to the rand(array.length) approach already mentioned, could be this one
element = array.delete array.sample
Eksample:
>> array = (1..10).to_a
>> element = array.delete array.sample
>> array # => [1, 2, 4, 5, 6, 7, 8, 9, 10]
>> element # => 3
This is also a set of two operations, but at least you won't have to move away from the array itself.

If you need to sample a number of items and the remove those from the original array:
array = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
grab = array.sample(4)
=> [2, 6, 10, 5]
grab.each{ |a| array.delete a }
=> [2, 6, 10, 5]
array
=> [1, 3, 4, 7, 8, 9]

Related

Ruby hash by reference

I want to have a single target and then have that target be modifiable in a hash.
target = [1,2,3]
hash = {1 => target, 2 => target}
Now, I want to be able to either a) change target and have the hash auto update or b) change the hash's 1 and have it automatically change hash's 2. Neither work:
target = [6,7,8]
target
output:
{1=>[1, 2, 3], 2=>[1, 2, 3]}
Plan b:
hash[1] = [6,7,8]
output:
{1=>[6, 7, 8], 2=>[1, 2, 3]}
So I'm discerning that when you make a subhash an rvalue in a hash in Ruby, it's duping the subhash before setting the lvalue to equal it. What I want is the for Ruby to not to do that. Is this possible?
Thanks,
Kevin
target is just a name, [1,2,3] is the object it is referring to. Arrays are mutable, so you can change them:
target = [1,2,3]
p hash = {1 => target, 2 => target} # =>{1=>[1, 2, 3], 2=>[1, 2, 3]}
target.replace([6,7,8])
p hash # =>{1=>[6, 7, 8], 2=>[6, 7, 8]}
You should change the array itself, as already mentioned by #steenslag.
Here is just another example which also shows it wotking on a value of the Hash. You can try other methods from the Array class.
target = [1,2,3]
h = {a: target, b: target}
h #=> {:a=>[1, 2, 3], :b=>[1, 2, 3]}
[3,4,5].each do |e|
target.shift
target.push(e)
end
h #=> {:a=>[3, 4, 5], :b=>[3, 4, 5]}
[7,8,9].each do |e|
h[:a].shift
h[:a].push(e)
end
h #=> {:a=>[7, 8, 9], :b=>[7, 8, 9]}

How can I get the next n number of elements using a Ruby enumerator?

I am trying to get the next n number of elements using a Ruby enumerator, with this:
a = [1, 2, 3, 4, 5, 6]
enum = a.each
enum.next(2) # expecting [1, 2]
enum.next(2) # expecting [3, 4]
But #next does not support that. Is there another way that I can do that?
Or shall I do?
What is the correct Ruby way to do that?
You can use take method
enum.take(2)
If you need slices of two elements, you could do:
e = enum.each_slice(2)
p e.next
#=> [1, 2]
p e.next
#=> [3, 4]
a = [1, 2, 3, 4, 5, 6]
enum = a.dup
enum.shift(2) # => [1, 2]
enum.shift(2) # => [3, 4]

Get items from Ruby Array that occur 2 or more times

Let's say I have a Ruby array.
[1,2,3,4,4,5,6,6,7,7]
I want to find the values that occur 2 or more times.
[4,6,7]
It will help my process to first determine which items occur only once then remove those. So I'd like to solve this by first finding the items that occur once.
There are probably better ways, but this is one:
> [1,2,3,4,4,5,6,6,7,7].group_by{|i| i}.reject{|k,v| v.size == 1}.keys
=> [4, 6, 7]
Breaking it down:
> a = [1,2,3,4,4,5,6,6,7,7]
=> [1, 2, 3, 4, 4, 5, 6, 6, 7, 7]
> a1 = a.group_by{|i| i}
=> {1=>[1], 2=>[2], 3=>[3], 4=>[4, 4], 5=>[5], 6=>[6, 6], 7=>[7, 7]}
> a2 = a1.reject{|k,v| v.size == 1}
=> {4=>[4, 4], 6=>[6, 6], 7=>[7, 7]}
> a2.keys
=> [4, 6, 7]
Everyone loves a really difficult to follow one liner :)
[1,2,3,4,4,5,6,6,7,7].each_with_object(Hash.new(0)) { |o, h| h[o] += 1 }.select { |_, v| v > 1 }.keys
Add some white space and some comments
[1,2,3,4,4,5,6,6,7,7].each_with_object(Hash.new(0)) { |o, h|
h[o] += 1
}.select { |_, v|
v > 1
}.keys
Enumerate and pass in our memo hash to each iteration the Hash defaults to having 0 for any key
Increment counter for the object
Select only key value pairs where the value is greater than 1
Grab just the keys
This looks quite similar to Phillip's neat answer - in theory this should use slightly less memory as it will not have to build the intermediate arrays to perform counting
Another way:
a = [1,2,3,4,4,5,6,6,7,7]
au = a.uniq
a.reject { |i| au.delete(i) }
#=> [4, 6, 7]
If efficiency is important, you could use a set:
require 'set'
s = Set.new
a.reject { |e| s.add?(e) }
#=> [4, 6, 7]
You can use Array#select to return the elements where Array#count is greater than 1:
2.1.2 :005 > arr = [1,2,3,4,4,5,6,6,7,7]
=> [1, 2, 3, 4, 4, 5, 6, 6, 7, 7]
2.1.2 :006 > arr.select { |e| arr.count(e) > 1 }.uniq
=> [4, 6, 7]
Hope this helps

Reposition an element to the front of an array in Ruby

Even coming from javascript this looks atrocious to me:
irb
>> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
>> a.unshift(a.delete('c'))
=> ["c", "a", "b"]
Is there a more legible way placing an element to the front of an array?
Edit my actual code:
if #admin_users.include?(current_user)
#admin_users.unshift(#admin_users.delete(current_user))
end
Maybe this looks better to you:
a.insert(0, a.delete('c'))
Maybe Array#rotate would work for you:
['a', 'b', 'c'].rotate(-1)
#=> ["c", "a", "b"]
This is a trickier problem than it seems. I defined the following tests:
describe Array do
describe '.promote' do
subject(:array) { [1, 2, 3] }
it { expect(array.promote(2)).to eq [2, 1, 3] }
it { expect(array.promote(3)).to eq [3, 1, 2] }
it { expect(array.promote(4)).to eq [1, 2, 3] }
it { expect((array + array).promote(2)).to eq [2, 1, 3, 1, 2, 3] }
end
end
sort_by proposed by #Duopixel is elegant but produces [3, 2, 1] for the second test.
class Array
def promote(promoted_element)
sort_by { |element| element == promoted_element ? 0 : 1 }
end
end
#tadman uses delete, but this deletes all matching elements, so the output of the fourth test is [2, 1, 3, 1, 3].
class Array
def promote(promoted_element)
if (found = delete(promoted_element))
unshift(found)
end
self
end
end
I tried using:
class Array
def promote(promoted_element)
return self unless (found = delete_at(find_index(promoted_element)))
unshift(found)
end
end
But that failed the third test because delete_at can't handle nil. Finally, I settled on:
class Array
def promote(promoted_element)
return self unless (found_index = find_index(promoted_element))
unshift(delete_at(found_index))
end
end
Who knew a simple idea like promote could be so tricky?
Adding my two cents:
array.select{ |item| <condition> } | array
Pros:
Can move multiple items to front of array
Cons:
This will remove all duplicates unless it's the desired outcome.
Example - Move all odd numbers to the front (and make array unique):
data = [1, 2, 3, 4, 3, 5, 1]
data.select{ |item| item.odd? } | data
# Short version:
data.select(&:odd?) | data
Result:
[1, 3, 5, 2, 4]
Another way:
a = [1, 2, 3, 4]
b = 3
[b] + (a - [b])
=> [3, 1, 2, 4]
If by "elegant" you mean more readable even at the expense of being non-standard, you could always write your own method that enhances Array:
class Array
def promote(value)
if (found = delete(value))
unshift(found)
end
self
end
end
a = %w[ a b c ]
a.promote('c')
# => ["c", "a", "b"]
a.promote('x')
# => ["c", "a", "b"]
Keep in mind this would only reposition a single instance of a value. If there are several in the array, subsequent ones would probably not be moved until the first is removed.
In the end I considered this the most readable alternative to moving an element to the front:
if #admin_users.include?(current_user)
#admin_users.sort_by{|admin| admin == current_user ? 0 : 1}
end
If all the elements in the array are unique you can use array arithmetic:
> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
> a -= "c"
=> ["a", "b"]
> a = ["c"] + a
=> ["c", "a", "b"]
Building on above:
class Array
def promote(*promoted)
self - (tail = self - promoted) + tail
end
end
[1,2,3,4].promote(5)
=> [1, 2, 3, 4]
[1,2,3,4].promote(4)
=> [4, 1, 2, 3]
[1,2,3,4].promote(2,4)
=> [2, 4, 1, 3]

How do I check an array for duplicates? [duplicate]

This question already has answers here:
How to find and return a duplicate value in array
(23 answers)
Closed 8 years ago.
I've got an array A. I'd like to check if it contains duplicate values. How would I do so?
Just call uniq on it (which returns a new array without duplicates) and see whether the uniqed array has less elements than the original:
if a.uniq.length == a.length
puts "a does not contain duplicates"
else
puts "a does contain duplicates"
end
Note that the objects in the array need to respond to hash and eql? in a meaningful for uniq to work properly.
In order to find the duplicated elements, I use this approach (with Ruby 1.9.3):
array = [1, 2, 1, 3, 5, 4, 5, 5]
=> [1, 2, 1, 3, 5, 4, 5, 5]
dup = array.select{|element| array.count(element) > 1 }
=> [1, 1, 5, 5, 5]
dup.uniq
=> [1, 5]
If you want to return the duplicates, you can do this:
dups = [1,1,1,2,2,3].group_by{|e| e}.keep_if{|_, e| e.length > 1}
# => {1=>[1, 1, 1], 2=>[2, 2]}
If you want just the values:
dups.keys
# => [1, 2]
If you want the number of duplicates:
dups.map{|k, v| {k => v.length}}
# => [{1=>3}, {2=>2}]
Might want to monkeypatch Array if using this more than once:
class Array
def uniq?
self.length == self.uniq.length
end
end
Then:
irb(main):018:0> [1,2].uniq?
=> true
irb(main):019:0> [2,2].uniq?
=> false

Resources