What is the easiest way to rearrange the elements of an array by referencing their indice? Something like this:
[:a, :b, :c, :d].rearrange(3, 1, 0, 2) # => [:d, :b, :a, :c]
[:a, :b, :c, :d].rearrange!(3, 1, 0, 2) # => [:d, :b, :a, :c]
Well, Ruby already has this method, only that it's called values_at:
>> [:a, :b, :c, :d].values_at(3, 1, 0, 2)
=> [:d, :b, :a, :c]
I'd turn it around a bit. A single permutation is just a function which maps an Array to another Array so this seems natural:
a = [:a, :b, :c, :d]
permuted = [3, 1, 0, 2].map { |i| a[i] }
That's easy to monkey patch into Array if necessary:
class Array
def permute(*p)
p.map { |i| self[i] }
end
end
Error checking, permute!, and further niceties are left as an exercise.
Related
Currently I can get all permutations of a two item array as such:
[[:a, :b], [:c, :d]].reduce(&:product)
# => [[:a, :c], [:a, :d], [:b, :c], [:b, :d]]
However when I try and do the same for a three item array I do not get the desired result:
[[:a, :b], [:c, :d], [:e, :f]].reduce(&:product)
# => [[[:a, :c], :e], [[:a, :c], :f], [[:a, :d], :e], [[:a, :d], :f]]
The expected result is:
[[:a, :c, :e], [:a, :c, :f], [:a, :d, :e], [:a, :d, :f] ...]
data = [[:a, :b], [:c, :d], [:e, :f]]
data[0].product(*data[1..-1])
# => [[:a, :c, :e], [:a, :c, :f], [:a, :d, :e], [:a, :d, :f], [:b, :c, :e], [:b, :c, :f], [:b, :d, :e], [:b, :d, :f]]
and you can extend your data with more arrays
Since each item, such as [[:a, :c], :e] is almost correct it can be flattened:
[[:a, :b], [:c, :d], [:e, :f]].reduce(&:product).map(&:flatten)
# => [[:a, :c, :e], [:a, :c, :f], [:a, :d, :e], [:a, :d, :f] ...]
Consider the following input:
input = [:a, :b, :c]
# output = input.join_array(:x)
What is a readable and concise way to get the following output (in Ruby):
[:a, :x, :b, :x, :c]
A naive approach:
input = [:a, :b, :c]
input.flat_map{|elem| [elem, :x]}[0...-1] # => [:a, :x, :b, :x, :c]
Without cutting last element:
res = input.reduce([]) do |memo, elem|
memo << :x unless memo.empty?
memo << elem
end
res # => [:a, :x, :b, :x, :c]
Flatten a Product
You can use Array#product to distribute :x throughout your array, and then flatten the result. For example:
input = [:a, :b, :c]
input.product([:x]).flatten
#=> [:a, :x, :b, :x, :c, :x]
Trimming an Array
Assuming your desired result wasn't just a typo that accidentally excluded the last element, you can use Array#pop, Array#slice, or other similar methods to trim the last element from the array. Some examples include:
input.product([:x]).flatten[0...-1]
#=> [:a, :x, :b, :x, :c]
output = input.product([:x]).flatten
output.pop
output
#=> [:a, :x, :b, :x, :c]
What about:
input = [:a, :b, :c]
p input.zip([:x].cycle).flatten[0..-2] #=> [:a, :x, :b, :x, :c]
For fun, we could use join. Not necessarily readable or concise though!
[:a, :b, :c].join('x').chars.map(&:to_sym) # => [:a, :x, :b, :x, :c]
# Or, broken down:
input = [:a, :b, :c]
output = input.join('x') # => "axbxc"
output = output.chars # => ["a", "x", "b", "x", "c"]
output = output.map(&:to_sym) # => [:a, :x, :b, :x, :c]
How is this ?
input = [:a, :b, :c]
p input.each_with_object(:x).to_a.flatten[0..-2]
# >> [:a, :x, :b, :x, :c]
I have multiple arrays with unknown element count like
a = []
a << [:a, :c, :e]
a << [:b, :f, :g, :h, :i, :j]
a << [:d]
result should be something like ~ (I don't really care details due rounding etc)
r = [:b, :a, :f, :g, :d, :c, :h, :i, :e, :j]
This is how I think it could be done
First we need to expand/distribute equally elements in each array to same length, so we get something like
a << [nil, :a, nil, :c, nil, :e]
a << [:b, :f, :g, :h, :i, :j]
a << [nil, nil, :d, nil, nil]
Next we interleave them as typically would do
r = a.shift
a.each { |e| r = r.zip(e) }
r = r.flatten.compact
My current problem is how to equally (as much as it's possible) distribute those elements across array? There could be one array with 4 elements and other with 5, but probably biggest should go first.
Of course would be nice to see if there's any other way to achieve this :)
I would use a sort to do this, based on element index postion, divided by size of array, plus some offset based on array id, to keep things consistent (if you don't need consistency, you could use a small random offset instead).
a = [:a,:b]
b = [:c]
c = [:d,:e,:f]
d = [:g:,:h,:i,:j]
def sort_pos array, id
(1..array.size).map { |i| (i - 0.5 + id/1000.0)/(array.size + 1e-6) }
end
# Combine all the arrays with their sort index, assigning ids to each array for consistency.
# Depending on how you receive these arrays, this structure can be built up programatically,
# as long as you add an array plus its sort index numbers at the same time
combined = (a + b + c + d).zip( sort_pos(a, 1) + sort_pos(b, 2) + sort_pos(c, 3) + sort_pos(d, 4) )
# Extract the values from the original arrays in their new order
combined.sort_by { |zipped| zipped[1] }.map { |zipped| zipped[0] }
=> [:g, :d, :a, :h, :e, :i, :b, :f, :j, :c]
There might be a cleaner way of doing this in Ruby . . . but I think the end result is what you are after - an "even" mix of multiple arrays.
If you only care about even-ness of mix from a statistical perspective (i.e. over time it is "fair"), you could just do this:
(a+b+c+d).shuffle
=> [:g, :b, :i, :c, :a, :h, :e, :j, :f, :d]
I always see replace in the Array and Hash documentation and I always think that it's odd.
I'm sure I've done something like this many times:
a = [:a, :b, :c, :d]
...
if some_condition
a = [:e, :f]
end
But I never thought to use this instead:
a = [:a, :b, :c, :d]
...
if some_condition
a.replace [:e, :f]
end
Which I assume is the intended use. Does this really save memory, or have some other benefit, or is it just a style thing?
a = [:e, :f] and a.replace [:e, :f],
the two statements generated instructions as follows:
1.
a = [:a, :b, :c, :d]
a = [:e, :f]
instructions:
ruby --dump=insns test.rb
== disasm: <RubyVM::InstructionSequence:<main>#test.rb>=================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 duparray [:a, :b, :c, :d]
0004 setdynamic a, 0
0007 trace 1 ( 2)
0009 duparray [:e, :f]
0011 dup
0012 setdynamic a, 0
0015 leave
2.
a = [:a, :b, :c, :d]
a.replace([:e, :f])
instructions:
ruby --dump=insns test.rb
== disasm: <RubyVM::InstructionSequence:<main>#test.rb>=================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 duparray [:a, :b, :c, :d]
0004 setdynamic a, 0
0007 trace 1 ( 2)
0009 getdynamic a, 0
0012 duparray [:e, :f]
0014 send :replace, 1, nil, 0, <ic:0>
0020 leave
The replace method is not faster than assignment operator, but replace can modify receiver array in-place, and, replace method really save memory, this can be seen from rb_ary_replace's source.
VALUE
rb_ary_replace(VALUE copy, VALUE orig)
{
rb_ary_modify_check(copy);
orig = to_ary(orig);
if (copy == orig) return copy;
if (RARRAY_LEN(orig) <= RARRAY_EMBED_LEN_MAX)
{
VALUE *ptr;
VALUE shared = 0;
if (ARY_OWNS_HEAP_P(copy))
{
xfree(RARRAY_PTR(copy));
}
else if (ARY_SHARED_P(copy))
{
shared = ARY_SHARED(copy);
FL_UNSET_SHARED(copy);
}
FL_SET_EMBED(copy);
ptr = RARRAY_PTR(orig);
MEMCPY(RARRAY_PTR(copy), ptr, VALUE, RARRAY_LEN(orig));
if (shared)
{
rb_ary_decrement_share(shared);
}
ARY_SET_LEN(copy, RARRAY_LEN(orig));
}
else
{
VALUE shared = ary_make_shared(orig);
if (ARY_OWNS_HEAP_P(copy))
{
xfree(RARRAY_PTR(copy));
}
else
{
rb_ary_unshare_safe(copy);
}
FL_UNSET_EMBED(copy);
ARY_SET_PTR(copy, RARRAY_PTR(orig));
ARY_SET_LEN(copy, RARRAY_LEN(orig));
rb_ary_set_shared(copy, shared);
}
return copy; }
I think the intended use is to modify an array in-place that has been passed to a method. For example:
def m(a)
a.replace(%w[a b])
end
a = %w[x y z]
m(a)
# a is now ['a', 'b']
Without replace, you'd have to do something like this:
def m(a)
a.clear
a << 'a' # or use .push of course
a << 'b'
end
Using replace lets you do it all at once should bypass the auto-shrinking and auto-growing (which probably involves copying some memory) behavior that would be a side effect of replacing the array's content (not the array itself!) element by element. The performance benefit (if any) is probably just an extra, the primary intent is probably to get pointer-to-pointer behavior without having to introduce pointers or wrap the array in an extra object.
a = [:a, :b, :c, :d]
b = [:x, :y, :z]
a.replace(b)
a.object_id == b.object_id
=> false
a = [:a, :b, :c, :d]
b = [:x, :y, :z]
a = b
a.object_id == b.object_id
=> true
Also
a = [:a, :b, :c, :d]
c = a
b = [:x, :y, :z]
a.replace(b)
p c # => [:x, :y, :z]
vs
a = [:a, :b, :c, :d]
c = a
b = [:x, :y, :z]
a = b
p c # => [:a, :b, :c, :d]
This does not answer your question exactly.
I have a factorised array of n dimensions and I would like to develop it.
Here is an example:
develop([:a, :aa]) #=> [[:a, :aa]]
...which is the same as: [:a].product([:aa]).
Or, more complicated:
develop([:a, [:aa, :bb]]) #=> [[:a, :aa],
[:a, :bb]]
I'm working with Ruby 1.9. Thank you for any idea.
Edit:
Another example, with 3 levels of embedded arrays:
develop([:a, [[:b, [:ba, :bb]],
[:c, [:ca, :cb]],
[:d, [:da, :db]]]]) #=> [[:a, :b, :ba],
[:a, :b, :bb],
[:a, :c, :ca],
[:a, :c, :cb],
[:a, :d, :da],
[:a, :d, :db]]
I wonder if we could use Array's product method (http://ruby-doc.org/core-1.9.3/Array.html#method-i-product), even if we have some embedded arrays.
I'm not sure I fully understand what you are trying to do to these poor arrays, but I managed to make a function that gives the correct output for both the cases you specified. Here is the complete code:
def develop(x)
return x unless x.is_a? Array
y = []
x[1].each do |s|
d = develop(s)
d = [d] unless d.is_a? Array
d.each do |t|
t = [t] unless t.is_a? Array
y << [x.first] + t
end
end
return y
end
x = [:a,
[
[:b, [:ba, :bb]],
[:c, [:ca, :cb]],
[:d, [:da, :db]]
]
]
p develop(x)
p develop [:a, [:aa, :bb]]
The output is:
C:\Users\David\Documents\scraps\test_ruby>ruby test.rb
[[:a, :b, :ba], [:a, :b, :bb], [:a, :c, :ca], [:a, :c, :cb], [:a, :d, :da], [:a, :d, :db]]
[[:a, :aa], [:a, :bb]]
EDIT 1: Here's a shorter version that also gives the right output:
def develop(x)
return [x] unless x.is_a? Array
Array(x.last).collect do |s|
develop(s).collect do |t|
[x.first] + Array(t)
end
end.flatten 1
end