Ruby removing duplicates in enumerable lists - ruby

Is there a good way in ruby to remove duplicates in enumerable lists (i.e. reject, etc.)

For array you can use uniq() method
a = [ "a", "a", "b", "b", "c" ]
a.uniq #=> ["a", "b", "c"]
so if you just
(1..10).to_a.uniq
or
%w{ant bat cat ant}.to_a.uniq
because anyway almost every methods you do implement will return as an Array class.

Well the strategy would be to convert them to arrays and remove the duplicates from the arrays. By the way lists are arrays in ruby in any case so I'm not sure what you mean by "enumerable lists"

You can do a conversion to a Set, if element order is not important.
http://www.ruby-doc.org/core/classes/Set.html

I like using the set logic operators, if the object doesn't have a .uniq method.
a = [2,3,3,5,5,5,6] # => [2, 3, 3, 5, 5, 5, 6]
a | a # => [2, 3, 5, 6]

Related

Shuffle array with exceptions

Is there a way to shuffle all elements in an array with the exception of a specified index using the shuffle function?
Without having to manually write a method, does Ruby support anything similar?
For example, say I have an array of integers:
array = [1,2,3,4,5]
and I want to shuffle the elements in any random order but leave the first int in its place. The final result could be something like:
=> [1,4,3,2,5]
Just as long as that first element remains in its place. I've obviously found workarounds by creating my own methods to do this, but I wanted to see if there was some sort of built in function that could help cut down on time and space.
The short answer is no. Based on the latest Ruby documentation of Array.shuffle the only argument it accepts is random number generator. So you will need to write your own method - here's my take on it:
module ArrayExtender
def shuffle_except(index)
clone = self.clone
clone.delete_at(index)
clone.shuffle.insert(index, self[index])
end
end
array = %w(a b c d e f)
array.extend(ArrayExtender)
print array.shuffle_except(1) # => ["e", "b", "f", "a", "d", "c"]
print array.shuffle_except(2) # => ["e", "a", "c", "b", "f", "d"]
There is no built in function. It's still pretty easy to do that:
first element
arr = [1, 2, 3, 4, 5]
hold = arr.shift
# => 1
arr.shuffle.unshift(hold)
# => [1, 4, 5, 2, 3]
specific index
arr = [1, 2, 3, 4, 5]
index = 2
hold = arr.delete_at(index)
# => 3
arr.shuffle.insert(index, hold)
# => [5, 1, 3, 2, 4]

Ruby array of two dimensional arrays, search/lookup?

This may be very simple, but I don't know all of Ruby's array functions.
If I have a given array like:
values = [["a", 1], ["b", 3], ["c", 7], ... etc ]
I would like two functions:
A function that, when I give it "b", gives me 3.
The other way around, a function that when I give it 3, gives me "b".
There must be an easy way?
Hash[values]["b"] # => 3
Hash[values.map(&:reverse)][3] # => "b"
My first question is: Does this have to be an array? Hash is designed for this and has key / value lookup built-in.
You can create a Hash from an array by doing:
hash = Hash[values]
Then use hash["a"] # => 1
For the reverse, do: hash.key(1) # => "a"
The first is easy to achieve, by converting your array to a Hash with:
value_hash = Hash[values]
And access this with:
value_hash['b'] # => 3
For the other way around I would first like to know if you are sure that is is a unique request? So are both 'a','b','c',... and 1,3,7... etc. unique?
hash = array.to_h => Converts your array to a hash
hash[key] = value => Get the value associated with the key
hash.invert[key] = value => This method inverts your hash and you can select values
Yeah a hash is the answer, if you don't have duplicate keys of course. Otherwise you can use Array#assoc#rassoc which searches an array of arrays matching the first and last elements respectively:
ary = [["A", 1], ["B", 2], ["C", 3], ["D", 4], ["E", 5], ["F", 6], ["G", 6]]
ary.assoc('A') => ["A", 1]
ary.rassoc('3') => ["C", 3]
Note: these methods return the first matching array, not all of them.
See more at http://www.ruby-doc.org/core-2.1.2/Array.html
I see no point in creating a hash to locate a single value. Why not the simple, direct approach?
values = [["a", 1], ["b", 3], ["c", 7]]
values.find { |l,n| l=='b' }.last #=> 3
values.find { |l,n| n==3 }.first #=> "b"
Of course, neither of these deal with multiple values.

Partition array using index in ruby

I'm looking for an elegant way to partition an array by using index in ruby
eg:
["a","b",3,"c",5].partition_with_index(2)
=> [["a","b",3],["c",5]]
So far the best that I can think is using the below
["a","b",3,"c",5].partition.each_with_index{|val,index| index <= 2}
=> [["a","b",3],["c",5]]
Is there any other elegant way to accomplish this?
Thanks!
You can do:
["a","b",3,"c",5].partition.with_index { |_, index| index <= 2 }
Following #toro2k advice, I think this is a better solution because you are combining the two Enumerators to get the desired output.
If you don’t pass a block of code to partition, it returns an Enumerator object instead. Enumerators have a with_index method that will maintain the current loop index.
Why don't you use array.slice!
array#slice! Deletes the element(s) given by an index (optionally up to length elements) or by a range.
> a = ['a', 'b', 'c', 5]
> b = a.slice! 0, 2 # => ['a', 'b']
> a # => ['c', 5]
In your case,
> [a.slice!(0, index), a]
You could use Enumerable's take and drop methods:
a = ["a","b",3,"c",5]
[a.take(3), a.drop(3)] # => [["a", "b", 3], ["c", 5]]
I made an Enumerable quick reference sheet you might want to consult for questions like this.
This can be done, but not sure if it elegant or not :
a = ["a","b",3,"c",5]
index = 2
[a[0..index], a[index+1..-1]]
Thanks
You can try the below :
a = ["a","b",3,"c",5]
par = a.slice_before(sum: -2) do |elem, state|
state[:sum] += 1
state[:sum] == 2
end.to_a
par
# => [["a", "b", 3], ["c", 5]]
For your particular case, 'pyper' gem is usable:
require 'pyper' # gem install pyper if necessary
include Pyper
ary = ["a", "b", 3, "c", 5]
ary.τ3τ #=> ["a", "b", 3]
ary.τfτ #=> ["c", 5]
It only works easily on small n (number of chopped-off elements), but Pyper provides many other frequently encountered tasks on collections. It was inspired by lisp's car and cdr functions (see details by an anonymous donor), and the letters can be combined together into a control string, a bit like in APL. Greek tau (τ) is used to denote methods instead of c and r, so car, cdr become τaτ, τdτ:
ary.τaτ #=> "a"
ary.τdτ #=> ["b", 3, "c", 5]
# Instead of τfτ, one can write
ary.τdddτ #=> ["c", 5]
etc.

Consistent sorting across multiple arrays

I have the following data structure in Ruby (a hash where keys are strings, and values are arrays).
X = { "id": [2, 4, 1], "name": ["a", "b", "c"], "time": [1, 0, 2]}
I would like to sort the array associated with the field "time", but I would like all other arrays to be sorted in a consistent manner. Example: after sorting, X should look like this.
X = {"id": [4, 2, 1], "name": ["b", "a", "c"], "time": [0, 1, 2]}
I solved this in a really ugly way (because I'm not sure how to do it). What I did was create a copy of time, then zip id and time, and sort it, then zip name and time_copy and sort it. Then unzip. I'm pretty sure this is an awful way to do it. Could someone else teach me a better method?
I think you should seriously consider changing your data structure from a hash of arrays to an array of hashes if the three pieces of data are supposed to belong together. Otherwise you can get into all sorts of trouble (what would happen if you accidentally made the arrays unequal lengths, for example) - indeed, as you have found, it makes sorting rather difficult.
If you are stuck with the hash as an input format, you can convert as follows
hash = {id: [2, 4, 1], name: ["a", "b", "c"], time: [1, 0, 2]}
array = hash.map{|k,v| [k].product(v)}.transpose.map{|h| Hash[h]}
# => [{id: 2, name: "a", time: 1}, ...]
In the array of hashes format you can sort on a field extremely easily
array.sort_by{|h| h[:time]}
Using #tokland's answer to another question and applying values_at to the result:
h = { id: [2, 4, 1], name: ["a", "b", "c"], time: [1, 0, 2]}
time_indices = h[:time].each_with_index.sort_by(&:first).map(&:last)
h.values.each{|ar| ar.replace(ar.values_at(*time_indices))}
#=> {:id=>[4, 2, 1], :name=>["b", "a", "c"], :time=>[0, 1, 2]}
Almost the same as steenslag's, but I think map.with_index should be used.
permutation = X["time"].map.with_index{|*xi| xi}.sort_by(&:first).map(&:last)
X.values.each{|a| a.replace(a.values_at(*permutation))}

Weird multiplicator operator behavior in a two arrays to hash combination

I was looking for a way to convert two arrays into a single hash. I found something like this :
a1 = [1,2,3]
a2 = [?A, ?B, ?C]
Hash[*a1.zip(a2).flatten]
I thought that this syntax was a bit weird, because Hash[a1.zip a2] would do exactly the same. But more than that, I don't understand the need for the * operator.
I know that it turns objects into arrays, or something alike (but not in the same way [] does, apparently).
When I execute :
a = a1.zip(a2).flatten
=> [1, "A", 2, "B", 3, "C"]
a = *a1.zip(a).flatten
=> [1, "A", 2, "B", 3, "C"]
Nothing really happens, and for what I know of the * operator, this seems to be the normal behavior.
So, why does
Hash[*a1.zip(a2).flatten]
=> {1=>"A", 2=>"B", 3=>"C"}
Hash[a1.zip(a).flatten]
=> {}
Return different values, given that the parameters seem identical ?
I guess I must be missing something about the * operator.
Thanks.
When the * operator is used with arrays like that it is called the splat operator.
Think of it as an operator that removes the first level of brackets around an array. This is quite useful because you can turn arrays into argument lists:
def stuff(x, y, z)
end
a = [1, 2, 3]
stuff(*a) # x,y,z gets assigned 1,2,3
The same thing works with Hash[]. The [] operator on Hash accepts as arguments:
An argument list of key-value pairs:
Hash["a", 1, "b", 2] #=> { "a" => 1, "b" => 2 }
An array or array pairs representing key-values:
Hash[ [["a", 1], ["b", 2]] ] #=> { "a" => 1, "b" => 2 }
Hash[] not does NOT accept a plain flat array as arguments:
Hash[ ["a", 1, "b", 2] ] #=> {}
So with this in mind, plus our understanding what the splat operator does you can now see what is happening:
paired_array = a1.zip(a2)
=> [[1, "A"], [2, "B"], [3, "C"]]
plain_array = a1.zip(a2).flatten
=> [1, "A", 2, "B", 3, "C"]
# Per rule 2 above we know this works
Hash[paired_array]
=> {1=>"A", 2=>"B", 3=>"C"}
# This won't work
Hash[plain_array]
=> {}
# But if we turn the plain_array into an argument list,
# then we know per rule 1 above that this will work
Hash[*plain_array]
=> {1=>"A", 2=>"B", 3=>"C"}
Now then you might be wondering what the hey is happening when you do:
a = *plain_array
=> [1, "A", 2, "B", 3, "C"]
Since we know the splat operator effectively strips the brackets, we get this:
a = 1, "A", 2, "B", 3, "C"
...which funnily enough is valid Ruby code and just creates an array again.
You can read more fun stuff about the splat operator in the rubyspec test case for the splat operator.
I think there's a mistake in your example, it should be like this:
Hash[a1.zip(a2).flatten] #=> {}
Hash[*a1.zip(a2).flatten] #=> {1=>"A", 2=>"B", 3=>"C"}
The splat operator in the assign mode converts an array to multiple arguments:
duck, cow, pig = *["quack","mooh","oing"] #=> ["quack","mooh","oing"]
Actually it's identical to
duck, cow, pig = ["quack","mooh","oing"] #=> ["quack","mooh","oing"]
But from the documentation you can see that Hash[...] receives multiple arguments, so the splat operator helps to assign each of those multiple arguments.
It's not that mysterious:
a1 = [1,2,3]
a2 = [?A, ?B, ?C]
p Hash[*a1.zip(a2).flatten] #{1=>"A", 2=>"B", 3=>"C"}
The * converts the array to a mere list (of arguments).
But why wasn't this syntax used?
p Hash[a1.zip(a2)]# {1=>"A", 2=>"B", 3=>"C"}
Well, it is new since Ruby 1.9.2. Your example is probably older.

Resources