Partition array using index in ruby - 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.

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.

How to use an enumerator

In the Ruby Array Class documentation, I often find:
If no block is given, an enumerator is returned instead.
Why would I not pass a block to #map? What would be the use of my doing just:
[1,2,3,4].map
instead of doing:
[1,2,3,4].map{ |e| e * 10 } # => [10, 20, 30, 40]
Can someone show me a very practical example of using this enumerator?
Good question.
What if we want to do multiple things with the enumerator that is created? We don't want to process it now, because it means we may need to create another later?
my_enum = %w[now is the time for all good elves to get to work].map # => #<Enumerator: ["now", "is", "the", "time", "for", "all", "good", "elves", "to", "get", "to", "work"]:map>
my_enum.each(&:upcase) # => ["NOW", "IS", "THE", "TIME", "FOR", "ALL", "GOOD", "ELVES", "TO", "GET", "TO", "WORK"]
my_enum.each(&:capitalize) # => ["Now", "Is", "The", "Time", "For", "All", "Good", "Elves", "To", "Get", "To", "Work"]
The main distinction between an Enumerator and most† other data structures in the Ruby core library (Array, Hash) and standard library (Set, SortedSet) is that an Enumerator can be infinite. You cannot have an Array of all even numbers or a stream of zeroes or all prime numbers, but you can definitely have such an Enumerator:
evens = Enumerator.new do |y|
i = -2
y << i += 2 while true
end
evens.take(10)
# => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
zeroes = [0].cycle
zeroes.take(10)
# => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
So, what can you do with such an Enumerator? Well, three things, basically.
Enumerator mixes in Enumerable. Therefore, you can use all Enumerable methods such as map, inject, all?, any?, none?, select, reject and so forth. Just be aware that an Enumerator may be infinite whereas map returns an Array, so trying to map an infinite Enumerator may create an infinitely large Array and take an infinite amount of time.
There are wrapping methods which somehow "enrich" an Enumerator and return a new Enumerator. For example, Enumerator#with_index adds a "loop counter" to the block and Enumerator#with_object adds a memo object.
You can use an Enumerator just like you would use it in other languages for external iteration by using the Enumerator#next method which will give you either the next value (and move the Enumerator forward) or raise a StopIteration exception if the Enumerator is finite and you have reached the end.
† Eg., an infinite range: (1..1.0/0)
The feature of returning a enumerable when no block is given is mostly used when chaining functions from the enumerable class together. Like this:
abc = %w[a b c]
p abc.map.with_index{|item, index| [item, index]} #=> [["a", 0], ["b", 1], ["c", 2]]
edit:
I think my own understanding of this behavior is a bit too limited in order to give a proper understanding of the inner workings of Ruby. I think the most important thing to note is that arguments are passed on in the same way they are for Procs. Thus if an array is passed in, it will be automatically 'splatted' (any better word for this?). I think the best way to get an understanding is to just use some simple functions returning enumerables and start experimenting.
abc = %w[a b c d]
p abc.each_slice(2) #<Enumerator: ["a", "b", "c", "d"]:each_slice(2)>
p abc.each_slice(2).to_a #=> [["a", "b"], ["c", "d"]]
p abc.each_slice(2).map{|x| x} #=> [["a", "b"], ["c", "d"]]
p abc.each_slice(2).map{|x,y| x+y} #=> ["ab", "cd"]
p abc.each_slice(2).map{|x,| x} #=> ["a", "c"] # rest of arguments discarded because of comma.
p abc.each_slice(2).map.with_index{|array, index| [array, index]} #=> [[["a", "b"], 0], [["c", "d"], 1]]
p abc.each_slice(2).map.with_index{|(x,y), index| [x,y, index]} #=> [["a", "b", 0], ["c", "d", 1]]
In addition to hirolau's answer, there is another method lazy that you can combine to modify the enumerator.
a_very_long_array.map.lazy
If map always took a block, then there would have to be another method like map_lazy, and similarly for other iterators to do the same thing.
I guess sometimes you want to pass the Enumerator to another method, despite where this Enumerator came from: map, slice, whatever:
def report enum
if Enumerator === enum
enum.each { |e| puts "Element: #{e}" }
else
raise "report method requires enumerator as parameter"
end
end
> report %w[one two three].map
# Element: one
# Element: two
# Element: three
> report (1..10).each_slice(2)
# Element: [1, 2]
# Element: [3, 4]
# Element: [5, 6]
# Element: [7, 8]
# Element: [9, 10]
Enumerator A class which allows both internal and external iteration
=> array = [1,2,3,4,5]
=> array.map
=> #<Enumerator: [2, 4, 6, 8, 10]:map>
=> array.map.next
=> 2
=> array.map.next_values
=> [0] 2

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.

Ruby removing duplicates in enumerable lists

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]

Resources