Related
Given a set of items [z,a,b,c] I want to find the "cartesian power" (cartesian product with itself n times) but only those results that have a z in them. For example:
normal_values = ["a","b","c"]
p limited_cartesian( normal_values, "z", 2 )
#=> [
#=> ["z", "z"]
#=> ["z", "a"]
#=> ["z", "b"]
#=> ["z", "c"]
#=> ["a", "z"]
#=> ["b", "z"]
#=> ["c", "z"]
#=> ]
I can do this by spinning through the full set and skipping entries that don't have the special value, but I'm wondering if there's a simpler way. Preferably one that allows me to only evaluate the desired entries lazily, without wasting time calculating unwanted entries.
def limited_cartesian( values, special, power )
[special, *values].repeated_permutation(power)
.select{ |prod| prod.include?( special ) }
end
Edit: With v3.0 I finally have something respectable. As is oft the case, the key was looking at the problem the right way. It occurred to me that I could repeatedly permute normal_values << special, power - 1 times, then for each of those permutations, there would be one more element to add. If the permutation contained at least one special, any element of normal_values << special could be added; otherwise, special must be added.
def limited_cartesian( values, special, power )
all_vals = values + [special]
all_vals.repeated_permutation(power-1).map do |p|
if p.include?(special)
*all_vals.each_with_object([]) { |v,a| a << (p + [v]) }
else
p + [special]
end
end
end
limited_cartesian( values, 'z', 1 )
# [["z"]]
limited_cartesian( values, 'z', 2 )
# => [["a", "z"], ["b", "z"], ["c", "z"],
# ["z", "a"], ["z", "b"], ["z", "c"],
# ["z", "z"]]
limited_cartesian( values, 'z', 3 )
# => [["a", "a", "z"], ["a", "b", "z"], ["a", "c", "z"],
# ["a", "z", "a"], ["a", "z", "b"], ["a", "z", "c"],
# ["a", "z", "z"], ["b", "a", "z"], ["b", "b", "z"],
# ["b", "c", "z"], ["b", "z", "a"], ["b", "z", "b"],
# ["b", "z", "c"], ["b", "z", "z"], ["c", "a", "z"],
# ["c", "b", "z"], ["c", "c", "z"], ["c", "z", "a"],
# ["c", "z", "b"], ["c", "z", "c"], ["c", "z", "z"],
# ["z", "a", "a"], ["z", "a", "b"], ["z", "a", "c"],
# ["z", "a", "z"], ["z", "b", "a"], ["z", "b", "b"],
# ["z", "b", "c"], ["z", "b", "z"], ["z", "c", "a"],
# ["z", "c", "b"], ["z", "c", "c"], ["z", "c", "z"],
# ["z", "z", "a"], ["z", "z", "b"], ["z", "z", "c"],
# ["z", "z", "z"]]
This is my v2.1, which works, but is not pretty. I'll leave it for the record.
def limited_cartesian( values, special, power )
ndx = Array(0...power)
ndx[1..-1].each_with_object( [[special]*power] ) do |i,a|
ndx.combination(i).to_a.product(values.repeated_permutation(power-i).to_a)
.each { |pos, val| a << stuff_special(special, pos, val.dup) }
end
end
def stuff_special( special, pos, vals )
pos.each_with_object(Array.new(pos.size + vals.size)) {|j,r|
r[j] = special }.map {|e| e.nil? ? vals.shift : e }
end
# e.g., stuff_special( 'z', [1,4], ["a","b","c"]) => ["a","z","b","c","z"]
I want to convert this array
[[["b", "c"], ["c", "d"]], [["v", "e"], ["r", "g"]]]
into
[["b", "c"], ["c", "d"], ["v", "e"], ["r", "g"]]
How can I convert this ?
Array#flatten takes an optional level:
The optional level argument determines the level of recursion to flatten
Example:
[[["b", "c"], ["c", "d"]], [["v", "e"], ["r", "g"]]].flatten(1)
#=> => [["b", "c"], ["c", "d"], ["v", "e"], ["r", "g"]]
arr = []
a = [[["b", "c"], ["c", "d"]], [["v", "e"], ["r", "g"]]]
a.map{|x| x.map{|y| arr << y}}
puts arr
I have the following array
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"]]
I want to get the following output [["a","b"], ["a","c"], ["b","c"]] and delete ["b","a"] and ["c","b"]
I have the following code
a3.each do |ary3|
x = ary3[0]
y = ary3[1]
x = ary3[1]
y = ary3[0]
if a3.include?([x,y])
a3 - [y,x]
end
end
print a3
I tried using the swap, but no luck!
Thanks for the help.
Two arrays are considered to be equal if they contain the same elements and these elements are in the same order:
["a", "b"] == ["b", "a"]
#=> false
["a", "b"] == ["a", "b"]
#=> true
So you need to sort the inner arrays first and then you can use Array#uniq to ensure that each element in the outer array will only appear once:
arr = [["a", "b"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
arr.map(&:sort).uniq
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
This will leave arr untouched, however:
arr
#=> [["a", "b"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
You will need to use mutator methods (with a !) to edit the array in place:
arr = [["a", "b"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
arr.map!(&:sort).uniq!
arr
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
Edit
As a follow-up to #sawa's comment, who was concerned that it may not be desirable to change the ordering of the inner arrays, i looked a bit deeper into Array#uniq. Consider the following array:
arr = [["b", "a"], ["a", "c"], ["b", "c"], ["b", "a"], ["c", "b"]]
I figured out that Array#uniq actually takes a block that lets you specify how the elements should be be compared:
arr.uniq!{|x| x.sort }
arr
#=> [["b", "a"], ["a", "c"], ["b", "c"]]
Cool thing is, this also works with Symbol#to_proc (the &: notation) and actually looks even more elegant than my original answer:
arr.uniq!(&:sort)
arr
#=> [["b", "a"], ["a", "c"], ["b", "c"]]
You can still use Array#sort! if you want the inner arrays to be sorted afterwards:
arr.uniq!(&:sort!)
arr
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
My last observation on this is though, that the order probably isn't important or else two arrays with different order would not be considered equal. This got me thinking (again) and i posed myself the question: Why not use a Set? It would work like this:
require 'set'
sets = [Set["a", "b"], Set["a", "c"], Set["b", "c"], Set["b", "a"], Set["c", "b"]]
sets.uniq!
sets
#=> [#<Set: {"a", "b"}>, #<Set: {"a", "c"}>, #<Set: {"b", "c"}>]
Just keep in mind that a Set will not allow you to add the same element multiple times, whereas an array does:
[%w[a b b b c], %w[a b b b c], %w[a b c]].uniq(&:sort)
#=> [["a", "b", "b", "b", "c"], ["a", "b", "c"]]
[Set.new(%w[a b b b c]), Set.new(%w[a b b b c]), Set.new(%w[a b c])].uniq
#=> [#<Set: {"a", "b", "c"}>]
check this one: #delete_if as below:
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"]]
p a3.delete_if{|x| [["b", "a"], ["c","b"]].include? x}
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
As per your comment and description post :
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"],["b", "a"]]
p a3.each {|x| a3.delete(x.reverse) if a3.include? x.reverse}
#=> [["a", "b"], ["a", "c"], ["b", "c"]]
BenchMark:
require 'benchmark'
N = 10000
Benchmark.bm(20) do | x |
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"],["b", "a"]]
x.report('Mine') do
N.times { a3.each {|x| a3.delete(x.reverse) if a3.include? x.reverse} }
end
a3 = [["a", "b"], ["a","c"], ["b","c"], ["b", "a"], ["c","b"],["b", "a"]]
x.report('padde') do
N.times { a3.uniq!(&:sort!) }
end
end
Output:
user system total real
Mine 0.172000 0.000000 0.172000 ( 0.361021)
padde 0.203000 0.000000 0.203000 ( 0.460026)
I have an array structure that looks like:
a=[
[['a','A'],['b','B'],['c','C']],
[['d','D'],['e','E'],['f','F']]
]
How to merge inner two arrays so the new structure will be Array of arrays
[
['a','A'],['b','B'],['c','C'],['d','D'],['e','E'],['f','F']
]
Tried
a.inject([]){|k,v| v | k} # but order gets changed
=> [["d", "D"], ["e", "E"], ["f", "F"], ["a", "A"], ["b", "B"], ["c", "C"]]
How can i get desired result without loosing the order.
Tips, comments, suggestions, please?
Thnx.
array.flatten takes a parameter:
a.flatten(1) #[["a", "A"], ["b", "B"], ["c", "C"], ["d", "D"], ["e", "E"], ["f", "F"]]
Try this:
a.inject([]){|k,v| k|v}
>> "aaaaaafbfbfsjjseew".scan(/(.)/)
=> [["a"], ["a"], ["a"], ["a"], ["a"], ["a"], ["f"], ["b"], ["f"], ["b"], ["f"], ["s"], ["j"], ["j"], ["s"], ["e"], ["e"], ["w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.))/)
=> [["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["j", "j"], ["j", "j"], ["s", "s"], ["e", "e"], ["e", "e"], ["w", "w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.)\2*)/)
=> [["aaaaaa", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["jj", "j"], ["s", "s"], ["ee", "e"], ["w", "w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.)\1*)/)
=> [["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["j", "j"], ["j", "j"], ["s", "s"], ["e", "e"], ["e", "e"], ["w", "w"]]
>> "aaaaaafbfbfsjjseew".scan(/((.)\3*)/)
=> [["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["a", "a"], ["f", "f"], ["b", "b"], ["f", "f"], ["b", "b"], ["f", "f"], ["s", "s"], ["j", "j"], ["j", "j"], ["s", "s"], ["e", "e"], ["e", "e"], ["w", "w"]]
From the fine manual:
str.scan(pattern) → array
[...]
If the pattern contains groups, each individual result is itself an array containing one entry per group.
This one:
"aaaaaafbfbfsjjseew".scan(/(.)/)
has a group so you get an array of arrays: each individual result is a single element array.
The next one:
"aaaaaafbfbfsjjseew".scan(/((.))/)
has two groups which happen to have the same value so you get two identical elements in your individual result arrays.
The third one:
"aaaaaafbfbfsjjseew".scan(/((.)\2*)/)
again contains two groups but also contains a back-reference to the inner group so the outer group (AKA the first group) gobbles up duplicates and you get ["aaaaaa", "a"], ["jj", "j"], and ["ee", "e"].
The fourth one:
"aaaaaafbfbfsjjseew".scan(/((.)\1*)/)
just tries to switch the back-reference to the outer group but \1 isn't defined inside group 1 so it is equivalent to /((.))/.
The fifth one:
"aaaaaafbfbfsjjseew".scan(/((.)\3*)/)
tries to refer to a non-existant group (group 3 when there are only two groups) so it behaves the same as /((.))/.
"aaaaaafbfbfsjjseew".scan(/(.)/) means the string can be splitted into individual array of strings.
Here parenthesis tells that it's an array, and the .-symbol in parenthesis represents number of characters in that individual string of array.
If we write for suppose "hellovenkat".scan(/(...)/) , this results
[["hel"],["lov"],["enk"]]. It doesn't give the last index, because it can not contain three characters.
If we give "hello venkat".scan(/(...)/), this results as following.
Ans: [["hel"], ["lo "], ["ven"], ["kat"]].