Suppose there is an array like this one:
list = ["a", "a", "a", "b", "b", "c", "d", "e", "e"]
We want to create a cycle where every next element is different from the previous element and the first element is different from the last element.
required = ["a", "b", "a", "b", "a", "c", "e", "d", "e"]
How is this done in ruby?
def create_cycle
temp = Array.new($input)
i, j, counter = 0
while i == 0
while (counter != $input.length)
j = rand(1..$input.length-1).floor
unless !($input[i][0].to_s.eql?$input[j][0])
$solution.push($input[i])
$solution.push($input[j])
puts input[i], input[j]
$input.delete_at(i)
$input.delete_at(j)
counter = counter + 1
end
end
end
end
I'm trying to learn this. Thank you for your help.
Additional notes:
The elements a, b, c, d, e represent special format strings, where
a certain property is common among them, thus the first element "a"
shares a property with the next element "a" but is not equivalent to
the first.
In the case it isn't possible to create a cycle, then, it is enough to raise a flag in command line.
I might do it like this:
>> list = [a, a, a, b, b, c, d, e, e]
>> list.sort.each_slice((list.size/2.0).round).reduce(:zip).flatten.compact
=> [a, c, a, d, a, e, b, e, b]
The general method is to:
sort the list, so all identical members are adjacent
divide the list in half from the middle
interleave the two halves together
Assuming you do not care about the order being the same as in the original array, and it is ok to have duplicates if there is no way, and also assuming the list is presorted, here is one approach - it just keeps adding elements from the beginning and end of the list till there are no elements left:
def interleaver list
result = []
el = list.first
while(el)
el = list.shift
if el
result << el
else
return result
end
el = list.pop
if el
result << el
else
return result
end
end
result
end
> a = 'a'
> b = 'b'
> c = 'c'
> d = 'd'
> e = 'e'
> list = [a, a, a, b, b, c, d, e, e]
> interleaver(list)
=> ["a", "e", "a", "e", "a", "d", "b", "c", "b"]
But if such interleaving is not possible, you will get duplicates:
> list = [a, a, a, b]
> interleaver(list)
#=> ["a","b","a","a"]
You can obtain such a string, or demonstrate that no such string exists, with the following recursive method.
Code
def doit(remaining, partial=[])
first_partial, last_partial = partial.first, partial.last
if remaining.size == 1
return ([first_partial, last_partial] & remaining).empty? ?
partial + remaining : nil
end
remaining.uniq.each_with_index do |s,i|
next if s == last_partial
rem = remaining.dup
rem.delete_at(i)
rv = doit(rem, partial + [s])
return rv if rv
end
nil
end
Examples
list = %w| a a b |
#=> ["a", "a", "b"]
doit list
#=> nil
The above demonstrates that the three elements of list cannot be permuted to satisfy the two ordering requirements.
list = %w| a a a b b c d e e |
#=> ["a", "a", "a", "b", "b", "c", "d", "e", "e"]
doit list
#=> ["a", "b", "a", "b", "c", "b", "e", "d", "e"]
This took 0.0042 second to solve on a newish MacBook Pro.
list = %w| a a a a a a a b b c d e e f f f g g g g h i i i i j j |
#=> ["a", "a", "a", "a", "a", "a", "a", "b", "b", "c", "d", "e", "e",
# "f", "f", "f", "g", "g", "g", "g", "h", "i", "i", "i", "i", "j", "j"]
doit list
#=> ["a", "b", "a", "b", "a", "b", "a", "b", "c", "b", "d", "e", "f",
# "e", "f", "g", "f", "g", "h", "g", "h", "i", "j", "i", "j", "i", "j"]
This took 0.0059 seconds to solve.
Out of curiosity, I then tried
list = (%w| a a a a a a a b b c d e e f f f g g g g h i i i i j j |).shuffle
#=> ["a", "c", "f", "b", "d", "i", "a", "a", "i", "a", "a", "g", "g",
# "a", "g", "i", "j", "b", "h", "j", "e", "e", "a", "g", "f", "i", "f"]
doit list
#=> ["a", "c", "f", "b", "d", "i", "a", "i", "a", "g", "a", "g", "a",
# "g", "i", "g", "j", "b", "h", "j", "e", "a", "e", "g", "f", "i", "f"]
This took a whooping 1.16 seconds to solve, suggesting that it may be desirable to pre-sort list (doit(list.sort)) if, of course, list is sortable.
Related
How to change something like this
array = ["a, b, c, d"]
to
array = ["a", "b", "c", "d"]
and vice versa
Use String#split and Array#join:
["a, b, c, d"].first.split(', ')
# => ["a", "b", "c", "d"]
[["a", "b", "c", "d"].join(', ')]
# => ["a, b, c, d"]
I'm working on a problem around Instagram hashtags. Users often have "bundles" of hashtags that they copy and paste when they are posting images. Different bundles for different topics.
So I might have my "Things from the garden" bundle, which would be ["garden", "beautifullawns", "treesoutside", "greenlondon"] and so on. They're often twenty to thirty items long.
Sometimes they might have several of these to keep things varied.
What I want to do is by looking at past images that they have posted, to recommend a bundle of tags to use.
To do that I would have several arrays of tags that they have used previously:
x = ["a", "b", "c", "d", "e"]
y = ["a", "b", "d", "e", "f", "g"]
z = ["a", "c", "d", "e", "f", "h"]
...
I'd like to find largest common subsets of entries for these arrays.
So in this case, the largest subset would be ["a", "d", "e"] within those three. That's simple enough to achieve naively by using something like x & y & z.
However, I'd like to create a ranking of these subsets based on their size and frequency within all of the arrays under consideration, so that I can display the most commonly used bundles of tags:
[
{bundle: ["a","d","e"], frequency: 3, size: 3},
{bundle: ["e","f"], frequency: 2, size: 2},
{bundle: ["a","b"], frequency: 2, size: 2},
{bundle: ["b","d"], frequency: 2, size: 2},
...
]
Presumably, with a limitation on the minimum size of these bundles, say two items.
I'm using Elasticsearch for indexing, but I've found that attempting to do this with aggregations is challenging, so I'm pulling out the images into Ruby and then working there to create the listing.
As a first pass, I've looped over all of these arrays, then find all subsets of the other arrays, using an MD5 hash key as a unique identifier. But this limits the results. Adding further passes makes this approach quite inefficient, I suspect.
require 'digest'
x = ["a", "b", "c", "d", "e"]
y = ["a", "b", "d", "e", "f", "g"]
z = ["a", "c", "d", "e", "f", "h"]
def bundle_report arrays
arrays = arrays.collect(&:sort)
working = {}
arrays.each do |array|
arrays.each do |comparison|
next if array == comparison
subset = array & comparison
key = Digest::MD5.hexdigest(subset.join(""))
working[key] ||= {subset: subset, frequency: 0}
working[key][:frequency] += 1
working[key][:size] = subset.length
end
end
working
end
puts bundle_report([x, y, z])
=> {"bb4a3fb7097e63a27a649769248433f1"=>{:subset=>["a", "b", "d", "e"], :frequency=>2, :size=>4}, "b6fdd30ed956762a88ef4f7e8dcc1cae"=>{:subset=>["a", "c", "d", "e"], :frequency=>2, :size=>4}, "ddf4a04e121344a6e7ee2acf71145a99"=>{:subset=>["a", "d", "e", "f"], :frequency=>2, :size=>4}}
Adding a second pass gets this to a better result:
def bundle_report arrays
arrays = arrays.collect(&:sort)
working = {}
arrays.each do |array|
arrays.each do |comparison|
next if array == comparison
subset = array & comparison
key = Digest::MD5.hexdigest(subset.join(""))
working[key] ||= {subset: subset, frequency: 0}
working[key][:frequency] += 1
working[key][:size] = subset.length
end
end
original_working = working.dup
original_working.each do |key, item|
original_working.each do |comparison_key, comparison|
next if item == comparison
subset = item[:subset] & comparison[:subset]
key = Digest::MD5.hexdigest(subset.join(""))
working[key] ||= {subset: subset, frequency: 0}
working[key][:frequency] += 1
working[key][:size] = subset.length
end
end
working
end
puts bundle_report([x, y, z])
=> {"bb4a3fb7097e63a27a649769248433f1"=>{:subset=>["a", "b", "d", "e"], :frequency=>2, :size=>4}, "b6fdd30ed956762a88ef4f7e8dcc1cae"=>{:subset=>["a", "c", "d", "e"], :frequency=>2, :size=>4}, "ddf4a04e121344a6e7ee2acf71145a99"=>{:subset=>["a", "d", "e", "f"], :frequency=>2, :size=>4}, "a562cfa07c2b1213b3a5c99b756fc206"=>{:subset=>["a", "d", "e"], :frequency=>6, :size=>3}}
Can you suggest an efficient way to establish this ranking of large subsets?
Rather than do an intersection of every array with every other array, which might quickly get out of hand, I'd be tempted to keep a persistent index (in Elasticsearch?) of all the possible combinations seen so far, along with a count of their frequency. Then for every new set of tags, increment the frequency counts by 1 for all the sub-combinations from that tag.
Here's a quick sketch:
require 'digest'
def bundle_report(arrays, min_size = 2, max_size = 10)
combination_index = {}
arrays.each do |array|
(min_size..[max_size,array.length].min).each do |length|
array.combination(length).each do |combination|
key = Digest::MD5.hexdigest(combination.join(''))
combination_index[key] ||= {bundle: combination, frequency: 0, size: length}
combination_index[key][:frequency] += 1
end
end
end
combination_index.to_a.sort_by {|x| [x[1][:frequency], x[1][:size]] }.reverse
end
input_arrays = [
["a", "b", "c", "d", "e"],
["a", "b", "d", "e", "f", "g"],
["a", "c", "d", "e", "f", "h"]
]
bundle_report(input_arrays)[0..5].each do |x|
puts x[1]
end
Which results in:
{:bundle=>["a", "d", "e"], :frequency=>3, :size=>3}
{:bundle=>["d", "e"], :frequency=>3, :size=>2}
{:bundle=>["a", "d"], :frequency=>3, :size=>2}
{:bundle=>["a", "e"], :frequency=>3, :size=>2}
{:bundle=>["a", "d", "e", "f"], :frequency=>2, :size=>4}
{:bundle=>["a", "b", "d", "e"], :frequency=>2, :size=>4}
This might not scale very well either though.
Ruby 2.4. I have an array of strings
2.4.0 :007 > arr = ["a", "b", "g", "e", "f", "i"]
=> ["a", "b", "g", "e", "f", "h", "i"]
How do I split my array into smaller arrays based on a condition? I have a function -- "contains_vowel," which returns true if a string contains "a", "e", "i", "o", or "u". How would I take an array of strings and split it into smaller arrays, using a divider function of "contains_vowel"? That is, for the above, the resulting array of smaller arrays would be
[["a"], ["b", "g"], ["e"], ["f", "h"], ["i"]]
If an element of the larger array satisfies the condition, it would become an array of one element.
arr = ["a", "b", "g", "e", "f", "i"]
r = /[aeiou]/
arr.slice_when { |a,b| a.match?(r) ^ b.match?(r) }.to_a
=> [["a"], ["b", "g"], ["e"], ["f"], ["i"]]
String#match? made its debut in Ruby v2.4. For earlier versions you could use (for example) !!(b =~ r), where !! converts a truthy/falsy value to true/false. That converstion is needed because the XOR operator ^ serves double-duty: it's a logical XOR when a and b in a^b are true, false or nil, and a bit-wise XOR when the operands are integers, such as 2^6 #=> 4 (2.to_s(2) #=> "10"; 6.to_s(2) #=> "110"; 4.to_s(2) #=> "100").
One more way to skin a cat
def contains_vowel(v)
v.count("aeiou") > 0
end
def split_by_substring_with_vowels(arr)
arr.chunk_while do |before,after|
!contains_vowel(before) & !contains_vowel(after)
end.to_a
end
split_by_substring_with_vowels(arr)
#=> [["a"], ["b", "g"], ["e"], ["f", "h"], ["i"]]
What it does:
passes each consecutive 2 elements
splits when either of them contain vowels
Example with your other Array
arr = ["1)", "dwr", "lyn,", "18,", "bbe"]
split_by_substring_with_vowels(arr)
#=> [["1)", "dwr", "lyn,", "18,"], ["bbe"]]
Further example: (if you want vowel containing elements in succession to stay in the same group)
def split_by_substring_with_vowels(arr)
arr.chunk_while do |before,after|
v_before,v_after = contains_vowel(before),contains_vowel(after)
(!v_before & !v_after) ^ (v_before & v_after)
end.to_a
end
arr = ["1)", "dwr", "lyn,", "18,", "bbe", "re", "rr", "aa", "ee"]
split_by_substring_with_vowels(arr)
#=> [["1)", "dwr", "lyn,", "18,"], ["bbe", "re"], ["rr"], ["aa", "ee"]]
This checks if before and after are both not vowels Or if they both are vowels
I might use chunk which splits an array everytime the value of its block changes. Chunk returns a list of [block_value, [elements]] pairs, I used .map(&:last) to only get the sub-lists of elements.
arr = ["a", "b", "g", "e", "f", "h", "i"]
def vowel?(x); %w(a e i o u).include?(x); end
arr.chunk{|x| vowel?(x)}.map(&:last)
=> [["a"], ["b", "g"], ["e"], ["f", "h"], ["i"]]
contains_vowel = ->(str) { !(str.split('') & %w|a e i o u|).empty? }
_, result = ["a", "b", "g", "e", "f", "h", "i"].
each_with_object([false, []]) do |e, acc|
cv, acc[0] = acc[0], contains_vowel.(e)
cv ^ acc.first ? acc.last << [e] : (acc.last[-1] ||= []) << e
end
result
#⇒ [["a"], ["b", "g"], ["e"], ["f", "h"], ["i"]]
What we do here:
contains_vowel is a lambda to check whether the string contains a vowel or not.
we reduce the input array, collecting the last value (contained the previously handled string the vowel or not,) and the result.
cv ^ acc.first checks whether it was a flip-flop of vowel on the last step.
whether is was, we append a new array to the result
whether is was not, we append the string to the last array in the result.
I'm using Ruby 2.4. If I have a group of array indexes, and I want to delete all the elements at those indexes, how do I do that? I tried the below, but its leaving stuff out
2.4.0 :005 > indexes_to_delete = [7, 8, 9]
=> [7, 8, 9]
2.4.0 :008 > a = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
2.4.0 :009 > indexes_to_delete.each do |index| a.delete_at(index) end
=> [7, 8, 9]
2.4.0 :010 > a
=> ["a", "b", "c", "d", "e", "f", "g", "i"]
Notice the ending array. I should have only seven elements in teh array since I started with ten and then specified three indexes of elements to delete in the array. Yet I have eight elements. How do I adjust my statement to delete the elements at the indexes I specified?
Every time you delete an item from an array the indexes change.
So you could do this:
3.times { a.delete_at(7) }
Which has the same effect of deleting at 7,8,9
Or use slice! as recommended here: How to delete a range of values from an array?
a.slice!(7..9)
to work with arbitrary arrays, i think the obvious choice would be reject with index:
a.reject.with_index { |item, idx| indexes_to_delete.include? idx }
This is non-mutating so you'd set a variable equal to the result.
Here are a couple of ways to do that.
indexes_to_delete = [3, 8, 9]
a = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
#1
indexes_to_delete.sort.reverse_each { |i| a.delete_at(i) }
a #=> ["a", "b", "c", "e", "f", "g", "h"]
This mutates a. If that's not desired, operate on a copy of a (a.dup).
Just remember that you must delete the elements in the reverse order of their indexes.
#2
a.values_at(*(0..a.size-1).to_a - indexes_to_delete)
#=> ["a", "b", "c", "e", "f", "g", "h"]
First we calculate the "keepers"
(0..a.size-1).to_a - indexes_to_delete
#=> [0, 1, 2, 4, 5, 6]
This does not mutate a. If a is to be mutated write
a.replace(a.values_at(*(0..a.size-1).to_a - indexes_to_delete))
#=> ["a", "b", "c", "e", "f", "g", "h"]
a #=> ["a", "b", "c", "e", "f", "g", "h"]
#maxple gives a third way (reject.with_index), which reads best to me. I doubt there are significant efficiency differences among the three.
Your problem is that the indexing changes when you do the delete. So don't delete at first.
a = ["a","b","c","d","e","f","g","h","i","j"]
to_delete = [7,8,9]
to_delete.each { |i| a[i] = nil }
a.compact!
Set the elements you want to delete to nil, and then compact the array to get rid of those elements.
If you have nil elements you want to keep, but you have a value that you know would never have in the array, you could do
MARK = special value
to_delete.each { |i| a[i] = MARK }
a.delete(MARK)
I am having a problem understanding how array.sort{ |x,y| block } works exactly, hence how to use it?
An example from Ruby documentation:
a = [ "d", "a", "e", "c", "b" ]
a.sort #=> ["a", "b", "c", "d", "e"]
a.sort { |x,y| y <=> x } #=> ["e", "d", "c", "b", "a"]
In your example
a.sort
is equivalent to
a.sort { |x, y| x <=> y }
As you know, to sort an array, you need to be able to compare its elements (if you doubt that, just try to implement any sort algorithm without using any comparison, no <, >, <= or >=).
The block you provide is really a function which will be called by the sort algorithm to compare two items. That is x and y will always be some elements of the input array chosen by the sort algorithm during its execution.
The sort algorithm will assume that this comparison function/block will meet the requirements for method <=>:
return -1 if x < y
return 0 if x = y
return 1 if x > y
Failure to provide an adequate comparison function/block will result in array whose order is undefined.
You should now understand why
a.sort { |x, y| x <=> y }
and
a.sort { |x, y| y <=> x }
return the same array in opposite orders.
To elaborate on what Tate Johnson added, if you implement the comparison function <=> on any of your classes, you gain the following
You may include the module Comparable in your class which will automatically define for you the following methods: between?, ==, >=, <, <= and >.
Instances of your class can now be sorted using the default (ie without argument) invocation to sort.
Note that the <=> method is already provided wherever it makes sense in ruby's standard library (Bignum, Array, File::Stat, Fixnum, String, Time, etc...).
When you have an array of, let's say, integers to sort, it's pretty straightforward for sort method to order the elements properly - smaller numbers first, bigger at the end. That's when you use ordinary sort, with no block.
But when you are sorting other objects, it may be needed to provide a way to compare (each) two of them. Let's say you have an array of objects of class Person. You probably can't tell if object bob is greater than object mike (i.e. class Person doesn't have method <=> implemented). In that case you'd need to provide some code to explain in which order you want these objects sorted to sort method. That's where the block form kicks in.
people.sort{|p1,p2| p1.age <=> p2.age}
people.sort{|p1,p2| p1.children.count <=> p2.children.count}
etc. In all these cases, sort method sorts them the same way - the same algorithm is used. What is different is comparison logic.
#OscarRyz reply cleared up a lot for me on the question on how the sort works, esp
{ |x, y| y <=> x }
Based on my understanding I am providing here what the state of the array would be after each comparison for above block results.
Note: Got the reference of printing the values of block paramaters e1, e2 from
ruby-forum
1.9.3dev :001 > a = %w(d e a w f k)
1.9.3dev :003 > a.sort { |e1, e2| p [e2, e1]; e2 <=> e1 }
["w", "d"]
["k", "w"]
["k", "d"]
["k", "e"]
["k", "f"]
["k", "a"]
["f", "a"]
["d", "f"]
["d", "a"]
["d", "e"]
["e", "f"]
=> ["w", "k", "f", "e", "d", "a"]
A guessed array state at runtime after each comparison:
[e2, e1] Comparsion Result Array State
["w", "d"] 1 ["w", "e", "a", "d", "f", "k"]
["k", "w"] -1 ["w", "e", "a", "d", "f", "k"]
["k", "d"] 1 ["w", "e", "a", "k", "f", "d"]
["k", "e"] 1 ["w", "k", "a", "e", "f", "d"]
["k", "f"] 1 ["w", "k", "a", "e", "f", "d"]
["k", "a"] 1 ["w", "k", "a", "e", "f", "d"]
["f", "a"] 1 ["w", "k", "f", "e", "a", "d"]
["d", "f"] -1 ["w", "k", "f", "e", "a", "d"]
["d", "a"] 1 ["w", "k", "f", "e", "d", "a"]
["d", "e"] -1 ["w", "k", "f", "e", "d", "a"]
["e", "f"] -1 ["w", "k", "f", "e", "d", "a"] (Result)
Thanks,
Jignesh
<=> is a method is ruby that returns ( self.<=>( argument ) )
-1 if self < argument
0 if self == argument
1 if self > argument
x and y are items of array. If no block is provided, the sort function uses x<=>y, otherwise the result of the block says if x should be before y.
array.sort{|x, y| some_very_complicated_method(x, y) }
Here if some_very_complicated_method(x, y) returns smth that is < 0, x is considered < than y and so on...
Some miscellaneous points:
x and y are called block parameters. The sort method basically says "I'll give you x and y, you determine whether x or y should come first, and I'll look after the boring stuff with regards to sorting"
<=> is called a spaceship operator.
In:
a.sort {|x,y| y <=> x } #=> ["e", "d", "c", "b", "a"]
what is x and y?
x and y are the elements being compared by the sorting algorithm.
This is useful to define for custom classes which element should be before the other.
For basic data ( numbers, strings , date, etc ) the natural order is predefined, but for customer element ( ie Employee ) you define who goes before who in a comparison. This block give you the chance to define that.
and what happens at y<=>x?
There, they are comparing the elements in descending order ( those with "higher" value will go first ) rather than the natural order ( x<=>y )
The <=> method stands for "compareTo" and return 0 if the elements are equivalent, or < 0 if x goes before than y or > 0 if x goes after y
I believe |x,y| y<=>x is comparing two elements at a time in descending order, as seen in:
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-3C-3D-3E
Say with [ "d", "a", "e", "c", "b" ], "d" and "a" appear to be compared first. Then since it is descending, both remain in the same order because d evaluates to less than a. Then d and e are evaluated. "e" is moved to "d"'s position. Without knowing the internal workings of the c code it is not possible to know where is d moved to but I figure this process continues until all elements are sorted. The c functions:
VALUE
rb_ary_cmp(VALUE ary1, VALUE ary2)
{
long len;
VALUE v;
ary2 = rb_check_array_type(ary2);
if (NIL_P(ary2)) return Qnil;
if (ary1 == ary2) return INT2FIX(0);
v = rb_exec_recursive_paired(recursive_cmp, ary1, ary2, ary2);
if (v != Qundef) return v;
len = RARRAY_LEN(ary1) - RARRAY_LEN(ary2);
if (len == 0) return INT2FIX(0);
if (len > 0) return INT2FIX(1);
return INT2FIX(-1);
}