Count sequential occurrences of element in ruby array - ruby

Given some array such as the following:
x = ['a', 'b', 'b', 'c', 'a', 'a', 'a']
I want to end up with something that shows how many times each element repeats sequentially. So maybe I end up with the following:
[['a', 1], ['b', 2], ['c', 1], ['a', 3]]
The structure of the results isn't that important... could be some other data types of needed.

1.9 has Enumerable#chunk for just this purpose:
x.chunk{|y| y}.map{|y, ys| [y, ys.length]}

This is not a general solution, but if you only need to match single characters, it can be done like this:
x.join.scan(/(\w)(\1*)/).map{|x| [x[0], x.join.length]}

Here's one line solution. The logic same as Matt suggested, though, works fine with nil's in front of x:
x.each_with_object([]) { |e, r| r[-1] && r[-1][0] == e ? r[-1][-1] +=1 : r << [e, 1] }

Here's my approach:
# Starting array
arr = [nil, nil, "a", "b", "b", "c", "a", "a", "a"]
# Array to hold final values as requested
counts = []
# Array of previous `count` element
previous = nil
arr.each do |letter|
# If this letter matches the last one we checked, increment count
if previous and previous[0] == letter
previous[1] += 1
# Otherwise push a new array for letter/count
else
previous = [letter, 1]
counts.push previous
end
end
I should note that this doesn't suffer from the same problem that Matt Sanders describes, since we're mindful of our first time through the iteration.

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"]

Sort array a by value in array b in Ruby

I have an array
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
and another array:
b = [0, 3, 6, 3, 4, 0, 1]
Is it possible to sort array a according to values in array b?
The result should be:
a = ['c', 'e', 'b', 'd', 'g', 'a', 'f']
Something like this doesn't seem to exist in Ruby/Rails:
a.sort_with_index{ |elem, index| b[index] }
Edit: in response to the duplicate marking: the question being referred to has an array with elements having an ID, and the other array references the ID's directly. This is not the same, and the same solution will not apply...
a.sort_by.with_index { |_,i| [-b[i], i] }
#=> ["c", "e", "b", "d", "g", "a", "f"]
This uses the indices of elements in a to break ties. I see from a comment on #tadman's answer that that is desired, though it is not a requirement given in the statement of the question. See the third paragraph of the doc for Array#<=> for an explanation of how arrays are ordered in sorting operations.
You can just combine the two, sort, and strip out the original a values:
a.zip(b).sort_by { |_a, _b| -_b }.map { |_a,_| _a }

processing array with duplicates

I have an array
a = ['A', 'B', 'B', 'C', 'D', 'D']
and I have to go thru all the elements, do something depending on whether the is the last occurance or not, and remove the element after processing it.
The elements are already sorted if that matters.
I'm looking for something efficient. Any suggestions?
Her what I have until now. THIS WORKS AS EXPECTED but not sure it is very efficient.
a = ['A', 'B', 'B', 'C', 'D', 'D']
while !a.empty?
b = a.shift
unless a.count(b) > 0
p "unique #{b}"
else
p "duplicate #{b}"
end
end
and it produces
"unique A"
"duplicate B"
"unique B"
"unique C"
"duplicate D"
"unique D"
Thanks
Simple way:
array = ["A", "B", "B", "C", "D", "D"]
array.group_by{|e| e}.each do |key,value|
*duplicate, uniq = value
duplicate.map do |e|
puts "Duplicate #{e}"
end
puts "Unique #{uniq}"
end
As per Stefan's comment and suggestion, shorter way is:
array.chunk_while(&:==).each do |*duplicate, uniq|
duplicate.map do |e|
puts "Duplicate #{e}"
end
puts "Unique #{uniq}"
end
# Above both will give the same Output:
---------------------------------------
Unique A
Duplicate B
Unique B
Unique C
Duplicate D
Unique D
Based on your code and expected output, I think this is an efficient way to do what you're looking for:
a = ['A', 'B', 'B', 'C', 'D', 'D']
a.each_index do |i|
if i < a.length - 1 && a[i+1] == a[i]
puts "This is not the last occurrence of #{a[i]}"
else
puts "This is the last occurrence of #{a[i]}"
end
end
# Output:
# This is the last occurrence of A
# This is not the last occurrence of B
# This is the last occurrence of B
# This is the last occurrence of C
# This is not the last occurrence of D
# This is the last occurrence of D
But I want to reiterate the importance of the wording in my output versus yours. This is not about whether the value is unique or not in the input. It seems to be about whether the value is the last occurrence within the input or not.
Quite similar to the answer of #GaganGami but using chunk_while.
a.chunk_while { |a,b| a == b }
.each do |*list,last|
list.each { |e| puts "duplicate #{e}" }
puts "unique #{last}"
end
chunk_whilesplits the array into sub arrays when the element changes.
['A', 'B', 'B', 'C', 'D', 'D'].chunk_while { |a,b| a == b }.to_a
# => [["A"], ["B", "B"], ["C"], ["D", "D"]]
The OP stated that the elements of a are sorted, but that is not required by the method I propose. It also maintains array-order, which could be important for the "do something" code performed for each element to be removed. It does so with no performance penalty over the case where the array is already sorted.
For the array
['A', 'B', 'D', 'C', 'B', 'D']
I assume that some code is to be executed for 'A', 'C' the second 'B' and the second 'D', in that order, after which a new array
['B', 'D']
is returned.
Code
def do_something(e) end
def process_last_dup(a)
a.dup.
tap do |b|
b.each_with_index.
reverse_each.
uniq(&:first).
reverse_each { |_,i| do_something(a[i]) }.
each { |_,i| b.delete_at(i) }
end
end
Example
a = ['A', 'B', 'B', 'C', 'D', 'D']
process_last_dup(a)
#=> ["B", "D"]
Explanation
The steps are as follows.
b = a.dup
#=> ["A", "B", "B", "C", "D", "D"]
c = b.each_with_index
#=> #<Enumerator: ["A", "B", "B", "C", "D", "D"]:each_with_index>
d = c.reverse_each
#=> #<Enumerator: #<Enumerator: ["A",..., "D"]:each_with_index>:reverse_each>
Notice that d can be thought of as a "compound" enumerator. We can convert it to an array to see the elements it will generate and pass to uniq.
d.to_a
#=> [["D", 5], ["D", 4], ["C", 3], ["B", 2], ["B", 1], ["A", 0]]
Continuing,
e = d.uniq(&:first)
#=> [["D", 5], ["C", 3], ["B", 2], ["A", 0]]
e.reverse_each { |_,i| do_something(a[i]) }
reverse_each is used so that do_something is first executed for 'A', then for the second 'B', and so on.
e.each { |_,i| b.delete_at(i) }
b #=> ["B", "D"]
If a is to be modified in place replace a.dup. with a..
Readers may have noticed that the code I gave at the beginning used Object#tap so that tap's block variable b, which initially equals a.dup, will be returned after it has been modified within tap's block, rather than explicitly setting b = a.sup at the beginning and b at the end, as I've done in my step-by-step explanation. Both approaches yield the same result, of course.
The doc for Enumerable#uniq does not specify whether the first element is kept, but it does reference Array.uniq, which does keep the first. If there is any uneasiness about that one could always replace reverse_each with reverse so that Array.uniq would be used.

Complex subsorts on an array of arrays

I wrote a quick method that confirms that data coming from a webpage is sorted correctly:
def subsort_columns(*columns)
columns.transpose.sort
end
Which worked for basic tests. Now, complex subsorts have been introduced, and I'm pretty certain I'll need to still use an array, since hashes can't be guaranteed to return in a specific order. The order of the input in this case represents subsort priority.
# `columns_sort_preferences` is an Array in the form of:
# [[sort_ascending_bool, column_data]]
# i.e.
# subsort_columns([true, column_name], [false, column_urgency], [true, column_date])
# Will sort on name ascending, then urgency descending, and finally date ascending.
def subsort_columns(*columns_sort_preferences)
end
This is where I'm stuck. I want to do this cleanly, but can't come up with anything but rolling out a loop for each subsort that occurs on any parent sort...but it sounds wrong.
Feel free to offer better suggestions, as I'm not tied to this implementation.
Here's some test data:
a = [1,1,1,2,2,3,3,3,3]
b = %w(a b c c b b a b c)
c = %w(x z z y x z z y z)
subsort_columns([true, a], [false, b], [false, c])
=> [[1, 'c', 'z'],
[1, 'b', 'z'],
[1, 'a', 'x'],
[2, 'c', 'y'],
[2, 'b', 'x'],
[3, 'c', 'z'],
[3, 'b', 'z'],
[3, 'b', 'y'],
[3, 'a', 'z']]
Update:
Marking for reopen because I've linked to this question in a comment above the function in our codebase that I provided as my own answer. Not to mention the help I got from an answer here that clearly displays the solution to my problem, whom I'd like to give a bounty to for giving me a tip in the right direction. Please don't delete this question, it is very helpful to me. If you disagree, at least leave a comment specifying what is unclear to you.
Use sort {|a, b| block} → new_ary:
a = [1,1,1,2,2,3,3,3,3]
b = %w(a b c c b b a b c)
c = %w(x z z y x z z y z)
sorted = [a, b, c].transpose.sort do |el1, el2|
[el1[0], el2[1], el2[2]] <=> [el2[0], el1[1], el1[2]]
end
Result:
[[1, "c", "z"],
[1, "b", "z"],
[1, "a", "x"]
[2, "c", "y"],
[2, "b", "x"],
[3, "c", "z"],
[3, "b", "z"],
[3, "b", "y"],
[3, "a", "z"]]
For a descending column reverse the left and right elements of the spaceship operator.
One way to do this is to do a series of 'stable sorts' in reverse order. Start with the inner sort and work out to the outer. The stability property means that the inner sort order remains intact.
Unfortunately, Ruby's sort is not stable. But see this question for a workaround.
# Sort on each entry in `ticket_columns`, starting with the first column, then second, etc.
# Complex sorts are supported. If the first element in each `ticket_columns` is a true/false
# boolean (specifying if an ascending sort should be used), then it is sorted that way.
# If omitted, it will sort all ascending.
def _subsort_columns(*ticket_columns)
# Is the first element of every `ticket_column` a boolean?
complex_sort = ticket_columns.all? { |e| [TrueClass, FalseClass].include? e[0].class }
if complex_sort
data = ticket_columns.transpose
sort_directions = data.first
column_data = data[1..-1].flatten 1
sorted = column_data.transpose.sort do |cmp_first, cmp_last|
cmp_which = sort_directions.map { |b| b ? cmp_first : cmp_last }
cmp_these = sort_directions.map { |b| b ? cmp_last : cmp_first }
cmp_left, cmp_right = [], []
cmp_which.each_with_index { |e, i| cmp_left << e[i] }
cmp_these.each_with_index { |e, i| cmp_right << e[i] }
cmp_left <=> cmp_right
end
sorted
else
ticket_columns.transpose.sort
end
end

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.

Resources