Get a fixed size slice from array - ruby

I need to dynamically retrieve a slice of size 3 from an array (as part of a more complex method):
a = %w(a b c d e)
i = 0 # i is an argument, I need [nil, "a", "b"]
a[(i-1)..(i+1)]
=> [] # no luck
a[(i-1), 3]
=> ["e"]
I know that when code gets messed up, it's not ruby's fault, is mine. What I'm missing? Is there a better way to achieve this?
I need to clarify. What I want is a slice of a given size, around a given index, and maping to nil if the slice goes beyond offset.

Here's a compact way you can do this. It relies on the fact that indexing beyond an array returns nil.
>> i = 0; 3.times.map{|j| a[j+i]}
=> ["a", "b", "c"]
>> i = 4; 3.times.map{|j| a[j+i]}
=> ["e", nil, nil]

Is each_slice the method you are looking for? Otherwise try to provide a better example for what you actually want to achieve.

Enumerable#each_cons will do the trick:
a.each_cons(3){|slice| p slice }
output:
["a", "b", "c"]
["b", "c", "d"]
["c", "d", "e"]

This is how I made it (having to go around ruby indexing negative values):
a = %w(a b c d e)
def slice_around(ary, index)
slice_3 = ->(x){ x == 0 ? [ary.size+1,0,1] : ((x-1)..(x+1)).to_a }
idxs = slice_3.call(index).map { |i| ary[i] }
end
> slice_around a, 0
=> [nil, "a", "b"]
> slice_around a, 2
=> ["b", "c", "d"]
It wont work if a negative index is passed though.

Related

How do I move an element of an array one place up/down with Ruby

Let's say I have this array
array = ['a', 'b', 'c', 'd']
What is a good way to target an element (for example 'b') and switch it with the next element in line (in this case 'c') so the outcome becomes:
=> ['a', 'c', 'b', 'd']
array[1], array[2] = array[2], array[1]
array #=> ["a", "c", "b", "d"]
or
array[1, 2] = array.values_at(2, 1)
array #=> ["a", "c", "b", "d"]
There is no build in function to do this. You can swap the values like so:
array = %w[a b c d]
array[1..2] = array[1..2].reverse
array #=> ["a", "c", "b", "d"]
You could add some helper methods to the core array class.
class Array
def move_up(index)
self[index, 2] = self[index, 2].reverse
self
end
def move_down(index)
move_up(index - 1)
end
end
Note: Keep in mind that this solution mutates the original array. You could also opt for a version that creates a new array. For this version you can call #dup (result = dup) than work with result instead of self.
References:
Array#[]
Array#[]=
Array#reverse
Object#dup
Try this for swapping
array[0],array[1] = array[1],array[0]
or in general
array[i],array[i+1] = array[i+1],array[i]
Assuming that you want to target the elements by their indices, a combination of insert and delete_at would work:
array = %w[a b c d]
array.insert(2, array.delete_at(1))
array
#=> ["a", "c", "b", "d"]

Ruby: How to create a letter counting function

I wouldn't have asked for help without first spending a few hours trying to figure out my error but I'm at a wall. So there is my attempt but I'm getting false when I try to pass the argument though and I'm not sure why. I know there are other ways to solve this problem that are a little shorter but I'm more interested in trying to get my code to work. Any help is much appreciated.
Write a method that takes in a string. Your method should return the most common letter in the array, and a count of how many times it appears.
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
counter1 = 0
var = [string[idx1], counter2]
end
idx1 += 1
end
return var
end
puts("most_common_letter(\"abca\") == [\"a\", 2]: #{most_common_letter("abca") == ["a", 2]}")
puts("most_common_letter(\"abbab\") == [\"b\", 3]: #{most_common_letter("abbab") == ["b", 3]}")
I didn't rewrite your code because I think it's important to point out what is wrong with the existing code that you wrote (especially since you're familiar with it). That said, there are much more 'ruby-like' ways to go about this.
The issue
counter1 is only being reset if you've found a 'new highest'. You need to reset it regardless of whether or not a new highest number has been found:
def most_common_letter(string)
idx1 = 0
idx2 = 0
counter1 = 0
counter2 = 0
while idx1 < string.length
idx2 = 0
while idx2 < string.length
if string[idx1] == string[idx2]
counter1 += 1
end
idx2 += 1
end
if counter1 > counter2
counter2 = counter1
# counter1 = 0 THIS IS THE ISSUE
var = [string[idx1], counter2]
end
counter1 = 0 # this is what needs to be reset each time
idx1 += 1
end
return var
end
Here's what the output is:
stackoverflow master % ruby letter-count.rb
most_common_letter("abca") == ["a", 2]: true
most_common_letter("abbab") == ["b", 3]: true
I think you're aware there are way better ways to do this but frankly the best way to debug this is with a piece of paper. "Ok counter1 is now 1, indx2 is back to zero", etc. That will help you keep track.
Another bit of advice, counter1 and counter2 are not very good variable names. I didn't realize what you were using them for initially and that should never be the case, it should be named something like current_count highest_known_count or something like that.
Your question has been answered and #theTinMan has suggested a more Ruby-like way of doing what you want to do. There are many other ways of doing this and you might find it useful to consider a couple more.
Let's use the string:
string = "Three blind mice. Oh! See how they run."
First, you need to answer a couple of questions:
do you want the frequency of letters or characters?
do you want the frequency of lowercase and uppercase letters combined?
I assume you want the frequency of letters only, independent of case.
#1 Count each unique letter
We can deal with the case issue by converting all the letters to lower or upper case, using the method String#upcase or String#downcase:
s1 = string.downcase
#=> "three blind mice. oh! see how they run."
Next we need to get rid of all the characters that are not letters. For that, we can use String#delete1:
s2 = s1.delete('^a-z')
#=> "threeblindmiceohseehowtheyrun"
Now we are ready to convert the string s2 to an an array of individual characters2:
arr = s2.chars
#=> ["t", "h", "r", "e", "e", "b", "l", "i", "n", "d",
# "m", "i", "c", "e", "o", "h", "s", "e", "e", "h",
# "o", "w", "t", "h", "e", "y", "r", "u", "n"]
We can combine these first three steps as follows:
arr = string.downcase.gsub(/[^a-z]/, '').chars
First obtain all the distinct letters present, using Array.uniq.
arr1 = arr.uniq
#=> ["t", "h", "r", "e", "b", "l", "i", "n",
# "d", "m", "c", "o", "s", "w", "y", "u"]
Now convert each of these characters to a two-character array consisting of the letter and its count in arr. Whenever you need convert elements of a collection to something else, think Enumerable#map (a.k.a. collect). The counting is done with Array#count. We have:
arr2 = arr1.map { |c| [c, arr.count(c)] }
#=> [["t", 2], ["h", 4], ["r", 2], ["e", 6], ["b", 1], ["l", 1],
# ["i", 2], ["n", 2], ["d", 1], ["m", 1], ["c", 1], ["o", 2],
# ["s", 1], ["w", 1], ["y", 1], ["u", 1]]
Lastly, we use Enumerable#max_by to extract the element of arr2 with the largest count3:
arr2.max_by(&:last)
#=> ["e", 6]
We can combine the calculation of arr1 and arr2:
arr.uniq.map { |c| [c, arr.count(c)] }.max_by(&:last)
and further replace arr with that obtained earlier:
string.downcase.gsub(/[^a-z]/, '').chars.uniq.map { |c|
[c, arr.count(c)] }.max_by(&:last)
#=> ["e", 6]
String#chars returns a temporary array, upon which the method Array#uniq is invoked. As alternative, which avoids the creation of the temporary array, is to use String#each_char in place of String#chars, which returns an enumerator, upon which Enumerable#uniq is invoked.
The use of Array#count is quite an inefficient way to do the counting because a full pass through arr is made for each unique letter. The methods below are much more efficient.
#2 Use a hash
With this approach we wish to create a hash whose keys are the distinct elements of arr and each value is the count of the associated key. Begin by using the class method Hash::new to create hash whose values have a default value of zero:
h = Hash.new(0)
#=> {}
We now do the following:
string.each_char { |c| h[c.downcase] += 1 if c =~ /[a-z]/i }
h #=> {"t"=>2, "h"=>4, "r"=>2, "e"=>6, "b"=>1, "l"=>1, "i"=>2, "n"=>2,
# "d"=>1, "m"=>1, "c"=>1, "o"=>2, "s"=>1, "w"=>1, "y"=>1, "u"=>1}
Recall h[c] += 1 is shorthand for:
h[c] = h[c] + 1
If the hash does not already have a key c when the above expression is evaluated, h[c] on the right side is replaced by the default value of zero.
Since the Enumerable module is included in the class Hash we can invoke max_by on h just as we did on the array:
h.max_by(&:last)
#=> ["e", 6]
There is just one more step. Using Enumerable#each_with_object, we can shorten this as follows:
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end.max_by(&:last)
#=> ["e", 6]
The argument of each_with_object is an object we provide (the empty hash with default zero). This is represented by the additional block variable h. The expression
string.each_char.with_object(Hash.new(0)) do |c,h|
h[c.downcase] += 1 if c =~ /[a-z]/i
end
returns h, to which max_by(&:last) is sent.
#3 Use group_by
I will give a slightly modified version of the Tin Man's answer and show how it works with the value of string I have used. It uses the method Enumerable#group_by:
letters = string.downcase.delete('^a-z').each_char.group_by { |c| c }
#=> {"t"=>["t", "t"], "h"=>["h", "h", "h", "h"], "r"=>["r", "r"],
# "e"=>["e", "e", "e", "e", "e", "e"], "b"=>["b"], "l"=>["l"],
# "i"=>["i", "i"], "n"=>["n", "n"], "d"=>["d"], "m"=>["m"],
# "c"=>["c"], "o"=>["o", "o"], "s"=>["s"], "w"=>["w"],
# "y"=>["y"], "u"=>["u"]}
used_most = letters.max_by { |k,v| v.size }
#=> ["e", ["e", "e", "e", "e", "e", "e"]]
used_most[1] = used_most[1].size
used_most
#=> ["e", 6]
In later versions of Ruby you could simplify as follows:
string.downcase.delete('^a-z').each_char.group_by(&:itself).
transform_values(&:size).max_by(&:last)
#=> ["e", 6]
See Enumerable#max_by, Object#itself and Hash#transform_values.
1. Alternatively, use String#gsub: s1.gsub(/[^a-z]/, '').
2. s2.split('') could also be used.
3. More or less equivalent to arr2.max_by { |c, count| count }.
It's a problem you'll find asked all over Stack Overflow, a quick search should have returned a number of hits.
Here's how I'd do it:
foo = 'abacab'
letters = foo.chars.group_by{ |c| c }
used_most = letters.sort_by{ |k, v| [v.size, k] }.last
used_most # => ["a", ["a", "a", "a"]]
puts '"%s" was used %d times' % [used_most.first, used_most.last.size]
# >> "a" was used 3 times
Of course, now that this is here, and it's easily found, you can't use it because any teacher worth listening to will also know how to search Stack Overflow and will find this answer.

How to print last elements of array

how do I print all elements of an array after the second element ex:
ARR=["a","b","c","d","f"]
I want to print c d f
ARR=["cat","dog","horse"]
I want to print horse
Thanks in advance
Just as easy as
p ARR[2..-1]
where 2 and -1 are indexes of an elements.
Use Array#[] with range or Array#drop
arr = ["a","b","c","d","f"]
arr[2..-1]
# => ["c", "d", "f"]
arr.drop(2)
# => ["c", "d", "f"]
arr = ["cat","dog","horse"]
arr[2..-1]
# => ["horse"]
arr.drop(2)
# => ["horse"]
Use Array#last method
arr.last(arr.size - 2)

Create an array from a hash with each_with_index

I have an array:
arr = ["a", "b", "c"]
What I want to do is to create a Hash so that it looks like:
{1 => "a", 2 => "b", 3 => c}
I tried to do that:
Hash[arr.each_with_index.map { |item, i| [i => item] }]
but didn't get what I was looking for.
each_with_index returns the original receiver. In order to get something different from the original receiver, map is necessary anyway. So there is no need of an extra step using each or each_with_index. Also, with_index optionally takes the initial index.
Hash[arr.map.with_index(1){|item, i| [i, item]}]
# => {1 => "a", 2 => "b", 3 => c}
Hash[] takes an array of arrays as argument. So you need to use [i, item] instead of [i => item]
arr = ["a", "b", "c"]
Hash[arr.each_with_index.map{|item, i| [i+1, item] }]
#=> {1=>"a", 2=>"b", 3=>"c"}
Just for clarification: [i => item] is the same as writing [{i => item}] so you really produced an array of arrays that in turn contained a single hash each.
I also added a +1 to the index so the hash keys start at 1 as you requested. If you don't care or if you want to start at 0, just leave that off.
arr = ["a", "b", "c"]
p Hash[arr.map.with_index(1){|i,j| [j,i]}]
# >> {1=>"a", 2=>"b", 3=>"c"}

Reorder Ruby array based on the first element of each nested array

My goal is to convert a into b:
a = [["a","b"], ["d", "c"], ["a", "o"], ["d", "g"], ["c", "a"]]
b = [[["a","b"], ["a", "o"]], ["c", "a"], [["d", "c"], ["d", "g"]]
They are grouped by the first element in each nested array. So far I have:
def letter_frequency(c)
d = Hash.new(0)
c.each do |v|
d[v] += 1
end
d.each do |k, v|
end
end
def separate_arrays(arry)
arry2 = []
arry3 = []
big_arry = []
y = 0
while y < arry.length
arry2.push(arry[y][0])
arry3.push(arry[y][1])
y += 1
end
freq = letter_frequency(arry2)
front = arry.slice!(0..(freq["a"] - 1))
end
separate_arrays(a)
Not only does this seem like overkill, but there are now guarantees that "a" will be a legit Hash key, so the last part doesn't work. Thanks for any help.
You can try to do something like this:
a.group_by(&:first).values.map {|e| e.length > 1 ? e : e.flatten}
# => [[["a", "b"], ["a", "o"]], [["d", "c"], ["d", "g"]], ["c", "a"]]
I use the following methods:
Enumerable#group_by (by first element of an array, like in your question):
Returns a hash, which keys are evaluated result from the block, and
values are arrays of elements in enum corresponding to the key.
Hash#values:
Returns a new array populated with the values from hsh. See also Hash#keys.
Enumerable#map (required because you don't want to get nested array when there are only one match, like for c letter):
Returns a new array with the results of running block once for every element in enum.
Enumerable#flatten:
Returns a new array that is a one-dimensional flattening of this array
(recursively). That is, for every element that is an array, extract
its elements into the new array. If the optional level argument
determines the level of recursion to flatten

Resources