Cartesian product of array with itself without duplicates in Ruby - ruby

How can I do a cartesian product of an array with itself, without including both [object i, object j] and [object j, object i]?
Currently, I've got
array = %w{a b c}
unique_combinations = array.each_with_index.to_a.product(array.each_with_index.to_a).
find_all{|(first_object, i), (second_object, j)| i < j}.
map{|(first_object, i), (second_object, j)| [first_object, second_object]}
unique_combinations # => [["a", "b"], ["a", "c"], ["b", "c"]]
which works, but feels a little verbose.
I could do
array = %w{a b c}
combinations = array.product(array)
unique_combinations = combinations.find_all{|first_item, second_item| array.index(first_item) < array.index(second_item)}
but that feels like I'm throwing information away, and would only work if the array only had unique items in it.
Another approach is
unique_combinations = []
array.each_with_index do |first_item, i|
array.each_with_index do |second_item, j|
next unless i < j
unique_combinations << [first_item, second_item]
end
end
but that feels too imperative rather than functional.

It's called a combination?
a = %w{a b c}
a.combination(2).to_a
=> [["a", "b"], ["a", "c"], ["b", "c"]]

Related

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.

Sort_by Ruby, One Descending, One Ascending

I have searched for an answer on this to no avail, there is a similar question but the answer did not work in this situation, it sorts on a numeric item. Similar Question -That did not work I am trying to use ruby's sort_by to sort one item descending with the other ascending. All I can find is one or the other.
Here is the code:
# Primary sort Last Name Descending, with ties broken by sorting Area of interest.
people = people.sort_by { |a| [ a.last_name, a.area_interest]}
Any guidance would certainly assist.
Sample data:
input
Russell, Logic
Euler, Graph Theory
Galois, Abstract Algebra
Gauss, Number Theory
Turing, Algorithms
Galois, Logic
output
Turing , Algorithms
Russell, Logic
Gauss, Number Theory
Galois, Abstract Algebra
Galois, Logic
Euler, Graph Theory
This is a straightforward way:
a = [ ['Russell', 'Logic'], ['Euler', 'Graph Theory'],
['Galois', 'Abstract Algebra'], ['Gauss', 'Number Theory'],
['Turing', 'Algorithms'], ['Galois', 'Logic'] ]
a.sort { |(name1,field1),(name2,field2)|
(name1 == name2) ? field1 <=> field2 : name2 <=> name1 }
#=> [ ["Turing", "Algorithms"], ["Russell", "Logic"],
# ["Gauss", "Number Theory"], ["Galois", "Abstract Algebra"],
# ["Galois", "Logic"], ["Euler", "Graph Theory"] ]
For multiple fields, sorting on the first in descending order, then on each of the others, in sequence, in ascending order:
a = [ %w{a b c}, %w{b a d}, %w{a b d}, %w{b c a}, %w{a b c}, %w{b c b}]
#=> [["a", "b", "c"], ["b", "a", "d"], ["a", "b", "d"],
# ["b", "c", "a"], ["a", "b", "c"], ["b", "c", "b"]]
a.sort { |e,f| e.first == f.first ? e[1..-1] <=> f[1..-1] : f <=> e }
#=> [["b", "a", "d"], ["b", "c", "a"], ["b", "c", "b"],
# ["a", "b", "c"], ["a", "b", "c"], ["a", "b", "d"]]
Make a custom class that invert the result of <=> (including Comparable).
Wrap the object you want sort descending with the custom class.
Example:
class Descending
include Comparable
attr :obj
def initialize(obj)
#obj = obj
end
def <=>(other)
return -(self.obj <=> other.obj)
end
end
people = [
{last_name: 'Russell', area_interest: 'Logic'},
{last_name: 'Euler', area_interest: 'Graph Theory'},
{last_name: 'Galois', area_interest: 'Abstract Algebra'},
{last_name: 'Gauss', area_interest: 'Number Theory'},
{last_name: 'Turing', area_interest: 'Algorithms'},
{last_name: 'Galois', area_interest: 'Logic'},
]
puts people.sort_by {|person| [
Descending.new(person[:last_name]), # <---------
person[:area_interest],
]}
output:
{:last_name=>"Turing", :area_interest=>"Algorithms"}
{:last_name=>"Russell", :area_interest=>"Logic"}
{:last_name=>"Gauss", :area_interest=>"Number Theory"}
{:last_name=>"Galois", :area_interest=>"Abstract Algebra"}
{:last_name=>"Galois", :area_interest=>"Logic"}
{:last_name=>"Euler", :area_interest=>"Graph Theory"}
BTW, if the object you want sort descending is a numeric value, you can simply use unary operator -:
people.sort_by {|person| [-person.age, person.name] }

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

Checking through Array subset

I have a challenge, im trying to write a method that takes in an array and returns the subset and permutation of twos, including the initial array. How do I check for particular patterns in the array. For example, given this array:
[a,b,c]
subset return will be:
[a,b,c,], [a,b], [b,c], [c,a]
and I also need to check if each subset contains a particular letter. Here's my code:
def conflict_free?(a)
return a.permutation(2).to_a
end
Here's how to get the subsets you're looking for:
def subsets(a)
2.upto(a.length).flat_map {|n| a.combination(n).to_a}
end
irb(main):023:0> subsets(["a", "b", "c"])
=> [["a", "b"], ["a", "c"], ["b", "c"], ["a", "b", "c"]]
Anything else you want, you'll have to edit your question and provide more detail.
Here is a very compact and fast solution :
def conflict(a)
a.combination(2).to_a << a
end
>> [["a", "b"], ["a", "c"], ["b", "c"], ["a", "b", "c"]]
If you did want the initial array at the beginning you sacrificing a fair bit of speed. Nevertheless the best way to do it :
def conflict(a)
temp = [a]
a.combination(2).each { |com| temp << com}
temp
end
>> [["a", "b", "c"], ["a", "b"], ["a", "c"], ["b", "c"]]
If the input is not 3 then this will work :
def conflict(a)
temp = []
2.upto(a.size-1) {|i| temp += a.combination(i).to_a}
temp << a
end
The initial array can be added at the beginning or end. Above it's at the end.

Ruby Combinations with array elements

Ok, i've searched the internet for answers and also searched for hours in my ruby programmer but i cant sort this out. I'm writing a script for making all sorts of combinations from elements in an array.
ar = ["a","b","c","d"]
At this point I am able to make these combinations:
["a"],["a","b"],["a","b","c"],["a","b","c","d"],["b"],["b","c"],["b","c","d"],["c"],["c","d"],["d"]
This is OK, but I can't find a way for searching these combinations, for example ["a","c"] or ["a","c","d"] or ["a","d"], etc...
For now my code looks like:
def combinaties(array)
combinaties = []
i=0
while i <= array.length-1
combinaties << array[i]
unless i == array.length-1
array[(i+1)..(array.length-1)].each{|volgend_element|
combinaties<<(combinaties.last.dup<<volgend_element)
}
end
i+=1
end
end
Functional approach (needs Ruby >= 1.9) to create the powerset of an array (except for the empty element you don't seem to need):
xs = ["a", "b", "c", "d"]
yss = 1.upto(xs.size).flat_map do |n|
xs.combination(n).to_a
end
#[
# ["a"], ["b"], ["c"], ["d"],
# ["a", "b"], ["a", "c"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"],
# ["a", "b", "c"], ["a", "b", "d"], ["a", "c", "d"], ["b", "c", "d"],
# ["a", "b", "c", "d"],
#]
There is a trivial correspondence (bijection) between such combinations and the numbers in [1..(2^m - 1)] (m being the array length).
Consider such a number n. It's binary representation has m digits (including leading zeros). The positions of the digits that are 1 are the indices of the elements in the corresponding combination.
The code would be:
def combinations(array)
m = array.length
(1...2**m).map do | n |
(0...m).select { | i | n[i] == 1 }.map { | i | array[i] }
end
end
Or in ruby 1.9
%w(a b c d e).combination(3).to_a
will give you all the combinations of size 3.

Resources