modify a different index of list inside loop - arrays

For modifying same index we can do
mylist = [1,2,3]
Enum.map(mylist, fn x-> -x end) // [-1,-2,-3]
How would we modify a different index like
for (i = 0; i < mylist.length; i++) {
mylist[i+1] = -mylist[i];
}

The most important thing is to remember you have to create an entirely new list, so you should think about how best to get what you're looking for, not how to replicate how you would tackle it in another language.
If, for example, you're wanting to compare items with nearby items, you could zip a list with its offset:
iex> enum = 1..5
1..5
iex> stream = Stream.drop(enum, 1) # Enum.drop/2 would also work
#Stream<[enum: 1..5, funs: [#Function<48.15162342/1 in Stream.drop/2>]]>
iex> Enum.zip(enum, stream) # Stream.zip/2 works if you're going to iterate
[{1, 2}, {2, 3}, {3, 4}, {4, 5}]
For your specific example, where the next value is the previous value negated, you could create an infinite stream and take what you need:
iex> 1 |> Stream.iterate(&-/1) |> Enum.take(3)
[1, -1, 1]
If you're only looking to modify one value, you'll have to create a new list with only the new value changed (NOTE this is not the way you want to do several updates):
iex> {previous, [current | next]} = Enum.split(1..5, 3)
{[1, 2, 3], [4, 5]}
iex> Enum.concat([previous, [current * 12], next])
[1, 2, 3, 48, 5]

Both answers here so far are using Elixir core library helpers like Enum.with_index/2, or Stream.iterate/2, or Enum.map/2.
I am to show the barebone functional approach for modifying the neighbour index as in your pseudocode example. I will be replicating the snippet that does not crash the VM.
> mylist = [ 1, 2, 3 ]
> for (i = 1; i < mylist.length; i++) { mylist[i-1] = -mylist[i] }
> mylist
//⇒ [ -2, -3, 3 ]
In Elixir, using recursion, that would be:
defmodule Neighbour do
def map(list, acc \\ [])
def map([_, next | rest], acc), do: map(rest, [-next | acc])
def map([next], acc), do: Enum.reverse([next, -next | acc])
end
Neighbour.map([1, 2, 3])
#⇒ [-2, -3, 3]

Remembering that data is immutable in Erlang and barring that it's not a good idea to map imperative programming to functional programming, we could do something like the following:
new_list = Enum.map(Enum.with_index(list), fn {v,i} ->
((Enum.at(list, i + 1) || 0) - v)
end)
Which is would result in [1, 1, -3] or in other words:
2 - 1 = 1
3 - 2 = 1
0 - 3 = -3
As others have pointed this would be an inefficient way to do such a thing per the O(n) complexity of Enum.at/2.
A more performant and idiomatic solution would be:
iex(1)> list0 = [1,2,3]
[1, 2, 3]
iex(2)> sliced = Enum.slice(list, 1, 2)
[2,3]
iex(3) list2 = List.duplicate(0, length(list0) - length(sliced))
[2, 3, 0]
iex(21)> Enum.map(Enum.zip(list, list2), fn({v1, v2}) -> v2 - v1 end)
[1, 1, -3]

Related

Ruby array: getting all the elements that have the max value while chaining methods (namely, without having a handle on the sorted array)

Suppose that you need to get all the elements that have the max value in an array.
A possible method would be to sort the array then use Enumerable#take_while:
array = [ 1, 3, 2, 3, 2, 3 ].sort {|a,b| b - a}
array.take_while { |e| e == array[0] }
#=> [3, 3, 3]
Now, when you are beautifully chaining methods and don't want to stop the chain just for storing the sorted array (which you'll need for referencing its first element in the take_while block), how would you do it?
I posted the question and an answer below for reference, but I probably missed better ways, so feel free to post your own method
Another way:
arr = [ 1, 3, 2, 3, 2, 3 ]
arr.sort {|a,b| b - a}.tap { |a| a.select! { |e| e == a.first } }
#=> [3, 3, 3]
Note that arr is not mutated.
ruby < 2.5
My original response to the question: sort.slice_when.first
[ 1, 3, 2, 3, 2, 3 ].sort {|a,b| b - a}.slice_when {|a,b| b != a}.first
#=> [3, 3, 3]
note: As slice_when returns an Enumerator, this solution won't walk through all the sorted array when chaining it with first. There is a more performant solution below tough.
ruby >= 2.5
Combining #engineersmnky and #Cary methods: then and max+select
[ 1, 3, 2, 3, 2, 3 ].then { |arr| mx = arr.max; arr.select { |elm| elm == mx } }
#=> [3, 3, 3]
You can try this
pry(main)> [ 1, 2, 2, 3, 3, 3 ].sort.slice_when {|a,b| b > a}.to_a.last
=> [3, 3, 3]
A bit similar of the last solution but also different.
Source https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-slice_when

better multiple array sort, based on first array

I'm working to update the SVG::Graph gem, and have made many improvements to my version, but have found a bottleneck with multiple array sorting.
There is a "sort_multiple" function built in, which keeps an array of arrays (all of equal size) sorted by the first array in the group.
The issue I have is that this sort works well on truly random data, and really badly on sorted, or almost sorted data:
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
if lo < hi
p = partition(arrys,lo,hi)
sort_multiple(arrys, lo, p-1)
sort_multiple(arrys, p+1, hi)
end
arrys
end
def partition( arrys, lo, hi )
p = arrys[0][lo]
l = lo
z = lo+1
while z <= hi
if arrys[0][z] < p
l += 1
arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
end
z += 1
end
arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
l
end
this routine appears to use a variant of the Lomuto partition scheme from wikipedia: https://en.wikipedia.org/wiki/Quicksort#Lomuto_partition_scheme
I have an array of 5000+ numbers, which is previously sorted, and this function adds about 1/2 second per chart.
I have modified the "sort_multiple" routine with the following:
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
first = arrys.first
return arrys if first == first.sort
if lo < hi
...
which has "fixed" the problem with sorted data, but I was wondering if there is any way to utilise the better sort functions built into ruby to get this sort to work much quicker. e.g. do you think I could utilise a Tsort to speed this up? https://ruby-doc.org/stdlib-2.6.1/libdoc/tsort/rdoc/TSort.html
looking at my benchmarking, the completely random first group appears to be very fast.
Current benchmarking:
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
if lo < hi
p = partition(arrys,lo,hi)
sort_multiple(arrys, lo, p-1)
sort_multiple(arrys, p+1, hi)
end
arrys
end
def partition( arrys, lo, hi )
p = arrys[0][lo]
l = lo
z = lo+1
while z <= hi
if arrys[0][z] < p
l += 1
arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
end
z += 1
end
arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
l
end
first = (1..5400).map { rand }
second = (1..5400).map { rand }
unsorted_arrys = [first.dup, second.dup, Array.new(5400), Array.new(5400), Array.new(5400)]
sorted_arrys = [first.sort, second.dup, Array.new(5400), Array.new(5400), Array.new(5400)]
require 'benchmark'
Benchmark.bmbm do |x|
x.report("unsorted") { sort_multiple( unsorted_arrys.map(&:dup) ) }
x.report("sorted") { sort_multiple( sorted_arrys.map(&:dup) ) }
end
results:
Rehearsal --------------------------------------------
unsorted 0.070699 0.000008 0.070707 ( 0.070710)
sorted 0.731734 0.000000 0.731734 ( 0.731742)
----------------------------------- total: 0.802441sec
user system total real
unsorted 0.051636 0.000000 0.051636 ( 0.051636)
sorted 0.715730 0.000000 0.715730 ( 0.715733)
#EDIT#
Final accepted solution:
def sort( *arrys )
new_arrys = arrys.transpose.sort_by(&:first).transpose
new_arrys.each_index { |k| arrys[k].replace(new_arrys[k]) }
end
I have an array of 5000+ numbers, which is previously sorted, and this function adds about 1/2 second per chart.
Unfortunately, algorithms implemented in Ruby can become quite slow. It's often much faster to delegate the work to the built-in methods that are implemented in C, even if it comes with an overhead.
To sort a nested array, you could transpose it, then sort_by its first element, and transpose again afterwards:
arrays.transpose.sort_by(&:first).transpose
It works like this:
arrays #=> [[3, 1, 2], [:c, :a, :b]]
.transpose #=> [[3, :c], [1, :a], [2, :b]]
.sort_by(&:first) #=> [[1, :a], [2, :b], [3, :c]]
.transpose #=> [[1, 2, 3], [:a, :b, :c]]
And although it creates several temporary arrays along the way, the result seems to be an order of magnitude faster than the "unsorted" variant:
unsorted 0.035297 0.000106 0.035403 ( 0.035458)
sorted 0.474134 0.003065 0.477199 ( 0.480667)
transpose 0.001572 0.000082 0.001654 ( 0.001655)
In the long run, you could try to implement your algorithm as a C extension.
I confess I don't fully understand the question and don't have the time to study the code at the link, but it seems that you have one sorted array that you are repeatedly mutating only slightly, and with each change you may mutate several other arrays, each a little or a lot. After each set of mutations you re-sort the first array and then rearrage each of the other arrays consistent with the changes in indices of elements in the first array.
If, for example, the first array were
arr = [2,4,6,8,10]
and the change to arr were to replace the element at index 1 (4) with 9 and the element at index 3 (8) with 3, arr would become [2,9,6,3,10], which, after re-sorting, would be [2,3,6,9,10]. We could do that as follows:
new_arr, indices = [2,9,6,3,10].each_with_index.sort.transpose
#=> [[2, 3, 6, 9, 10], [0, 3, 2, 1, 4]]
Therefore,
new_arr
#=> [2, 3, 6, 9, 10]
indices
#=> [0, 3, 2, 1, 4]
the intermediate calculation being
[2,9,6,3,10].each_with_index.sort
#=> [[2, 0], [3, 3], [6, 2], [9, 1], [10, 4]]
Considering that
new_array == [2,9,6,3,10].values_at(*indices)
#=> true
we see that each of the other arrays, after having been mutated, can be sorted to conform with the sorting of indices in the first array with the following method, which is quite fast.
def sort_like_first(a, indices)
a.values_at(*indices)
end
For example,
a = [5,4,3,1,2]
a.replace(sort_like_first a, indices)
a #=> [5, 1, 3, 4, 2]
a = %w|dog cat cow pig owl|
a.replace(sort_like_first a, indices)
a #=> ["dog", "pig", "cow", "cat", "owl"]
In fact, it's not necessary to sort each of the other arrays until they are required in the calculations.
I would now like to consider a special case, namely, when only a single element in the first array is to be changed.
Suppose (as before)
arr = [2,4,6,8,10]
and the element at index 3 (8) is to be replaced with 5, resulting in [2,4,6,5,10]. A fast sort can be done with the following method, which employs a binary search.
def new_indices(arr, replace_idx, replace_val)
new_loc = arr.bsearch_index { |n| n >= replace_val } || arr.size
indices = (0..arr.size-1).to_a
index_removed = indices.delete_at(replace_idx)
new_loc -= 1 if new_loc > replace_idx
indices.insert(new_loc, index_removed)
end
arr.bsearch_index { |n| n >= replace_val } returns nil if n >= replace_val #=> false for all n. It is for that reason I have tacked on || arr.size.
See Array#bsearch_index, Array#delete_at and Array#insert.
Let's try it. If
arr = [2,4,6,8,10]
replace_idx = 3
replace_val = 5
then
indices = new_indices(arr, replace_idx, replace_val)
#=> [0, 1, 3, 2, 4]
Only now can we replace the element of arr at index replace_idx.
arr[replace_idx] = replace_val
arr
#=> [2, 4, 6, 5, 10]
We see that the re-sorted array is as follows.
arr.values_at(*indices)
#=> [2, 4, 5, 6, 10]
The other arrays are sorted as before, using sort_like_first:
a = [5,4,3,1,2]
a.replace(sort_like_first(a, indices))
#=> [5, 4, 1, 3, 2]
a = %w|dog cat cow pig owl|
a.replace(sort_like_first(a, indices))
#=> ["dog", "cat", "pig", "cow", "owl"]
Here's a second example.
arr = [2,4,6,8,10]
replace_idx = 3
replace_val = 12
indices = new_indices(arr, replace_idx, replace_val)
#=> [0, 1, 2, 4, 3]
arr[replace_idx] = replace_val
arr
#=> [2, 4, 6, 12, 10]
The first array sorted is therefore
arr.values_at(*indices)
#=> [2, 4, 6, 10, 12]
The other arrays are sorted as follows.
a = [5,4,3,1,2]
a.replace(sort_like_first a, indices)
a #=> [5, 4, 3, 2, 1]
a = %w|dog cat cow pig owl|
a.replace(sort_like_first a, indices)
a #=> ["dog", "cat", "cow", "owl", "pig"]

Ruby, Array.select with number of elements

I have this array
[1, 2, 3, 4, 5, 6]
I would like to get the first 2 elements that are bigger than 3.
I can do:
elements = []
[1, 2, 3, 4, 5, 6].each do |element|
elements << element if element > 3
break if elements.size == 2
end
puts elements
Is there a more elegant way to do this?
Is there something in the Ruby core like Array.select(num_elements, &block)?
You were nearly there. Just use break with a parameter:
[1, 2, 3, 4, 5, 6].each_with_object([]) do |element, acc|
acc << element if element > 3
break acc if acc.size >= 2
end
Another way to accomplish it, would be to use Enumerator::Lazy with array.lazy.select, or an explicit Enumerator instance with Enumerable#take (here it’s a definite overkill, posting mostly for educational purposes.)
enum =
Enumerator.new do |y|
i = [1, 2, 3, 4, 5, 6].each
loop { i.next.tap { |e| y << e if e > 3 } }
end
enum.take(2)
#⇒ [4, 5]
Sidenote: both examples above would stop traversing the input as soon as two elements are found.
a = [1, 2, 3, 4, 5, 6]
p a.filter {|x| x > 3}.first(2)
Or
p a.select{|x| x > 3}.first(2)
output
[4, 5]
As Cary suggest, the given below code wouldn't be a performance hit if array is bigger, it would stop executing further if 2 elements are found
a.lazy.select{|x| x > 3}.first(2)
Just for having a couple of options more..
ary.each_with_object([]) { |e, res| res << e if e > 3 && res.size < 2 }
or
ary.partition { |e| e > 3 }.first.first(2)

Finding (multiset) difference between two arrays

Given arrays (say row vectors) A and B, how do I find an array C such that merging B and C will give A?
For example, given
A = [2, 4, 6, 4, 3, 3, 1, 5, 5, 5];
B = [2, 3, 5, 5];
then
C = multiset_diff(A, B) % Should be [4, 6, 4, 3, 1, 5]
(the order of the result does not matter here).
For the same A, if B = [2, 4, 5], then the result should be [6, 4, 3, 3, 1, 5, 5].
(Since there were two 4s in A and one 4 in B, the result C should have 2 - 1 = 1 4 in it. Similarly for the other values.)
PS: Note that setdiff would remove all instances of 2, 3, and 5, whereas here they need to be removed just however many times they appear in B.
Performance: I ran some quick-n-dirty benchmarks locally, here are the results for future reference:
#heigele's nested loop method performs best for small lengths of A (say upto N = 50 or so elements). It does 3x better for small (N=20) As, and 1.5x better for medium-sized (N=50) As, compared to the next best method - which is:
#obchardon's histc-based method. This is the one performs the best when A's size N starts to be 100 and above. For eg., this does 3x better than the above nested loop method when N = 200.
#matt's for+find method does comparably to the histc method for small N, but quickly degrades in performance for larger N (which makes sense since the entire C == B(x) comparison is run every iteration).
(The other methods are either several times slower or invalid at the time of writing.)
Still another approach using the histc function:
A = [2, 4, 6, 4, 3, 3, 1, 5, 5, 5];
B = [2, 3, 5, 5];
uA = unique(A);
hca = histc(A,uA);
hcb = histc(B,uA);
res = repelem(uA,hca-hcb)
We simply calculate the number of repeated elements for each vectors according to the unique value of vector A, then we use repelem to create the result.
This solution do not preserve the initial order but it don't seems to be a problem for you.
I use histc for Octave compatibility, but this function is deprecated so you can also use histcounts
Here's a vectorized way. Memory-inefficient, mostly for fun:
tA = sum(triu(bsxfun(#eq, A, A.')), 1);
tB = sum(triu(bsxfun(#eq, B, B.')), 1);
result = setdiff([A; tA].', [B; tB].', 'rows', 'stable');
result = result(:,1).';
The idea is to make each entry unique by tagging it with an occurrence number. The vectors become 2-column matrices, setdiff is applied with the 'rows' option, and then the tags are removed from the result.
You can use the second output of ismember to find the indexes where elements of B are in A, and diff to remove duplicates:
This answer assumes that B is already sorted. If that is not the case, B has to be sorted before executing above solution.
For the first example:
A = [2, 4, 6, 4, 3, 3, 1, 5, 5, 5];
B = [2, 3, 5, 5];
%B = sort(B); Sort if B is not sorted.
[~,col] = ismember(B,A);
indx = find(diff(col)==0);
col(indx+1) = col(indx)+1;
A(col) = [];
C = A;
>>C
4 6 4 3 1 5
For the second example:
A = [2, 4, 6, 4, 3, 3, 1, 5, 5, 5];
B = [2, 4, 5, 5];
%B = sort(B); Sort if B is not sorted.
[~,col] = ismember(B,A);
indx = find(diff(col)==0);
col(indx+1) = col(indx)+1;
A(col) = [];
C = A;
>>C
6 4 3 3 1 5
I'm not a fan of loops, but for random perturbations of A this was the best I came up with.
C = A;
for x = 1:numel(B)
C(find(C == B(x), 1, 'first')) = [];
end
I was curious about looking at the affect of different orders of A on a solution approach so I setup a test like this:
Ctruth = [1 3 3 4 5 5 6];
for testNumber = 1:100
Atest = A(randperm(numel(A)));
C = myFunction(Atest,B);
C = sort(C);
assert(all(C==Ctruth));
end
Strongly inspired by Matt, but on my machine 40% faster:
function A = multiDiff(A,B)
for j = 1:numel(B)
for i = 1:numel(A)
if A(i) == B(j)
A(i) = [];
break;
end
end
end
end

Iterate through array forwards then backwards

[1,2,3,4,5]
=>1,2,3,4,5,4,3,2,1
=>1,2,3,2,3,4,5,4,3 #I need to be able to reverse the iteration at certain points
I first tried something like:
a = [1,2,3,4,5]
a.each {|i|
if i % 9 == 0
a.reverse!
}
but that just reverses the entire array and starts counting from the index it left off on. I need to to shift the direction of each, so to speak.
i, counter = 0, 1 # initialize index to 0, counter to 1
while(i < a.length && i >= 0) do
puts a[i]
i+= counter # increment counter
counter*= -1 if(condition) # Multiply counter with -1 to reverse it
end
Well, here's a moving "cursor" for your array:
module Cursor
def current_index
#current_index ||= 0
end
def step
#current_index = current_index + direction
handle_boundary
end
def step_back
#current_index = current_index + (direction * -1)
handle_boundary
end
def handle_boundary
if current_index == length || current_index == 0
turn_around
end
end
def direction
#direction ||= 1
end
def turn_around
#direction = direction * -1
end
def current
self[current_index]
end
end
And here's how you use it:
array = [1,2,3,4,5]
arary.extend Cursor
array.current # returns the item in current position
array.step # moves a step forward, turns around when it reaches either end of the array
array.step_back # moves a step backward without switching the direction
array.turn_around # switch the direction
Now you can travel around as you want :D
You can make use of Enumerator class to create custom enumerable that can providing custom iteration through the array. In below code, I am monkey-patching Array class for convenience (also due to resemblance of the method to Array#cycle), though solution can be done without monkey-patching as well.
class Array
def reversible_cycle
Enumerator.new do |y|
index = 0
direction = :forward
loop do
direction = :backward if index + 1 >= size
direction = :forward if index <= 0
y << self[index]
index += (direction == :forward ? +1 : -1)
end
end
end
end
p [1,2,3,4,5].reversible_cycle.take(9)
#=> [1, 2, 3, 4, 5, 4, 3, 2, 1]
p [1,2,3,4,5].reversible_cycle.take(13)
#=> [1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5]
p [1,2,3,4,5].reversible_cycle.take(17)
#> [1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5, 4, 3, 2, 1]
p [1,2,3,4,5].reversible_cycle.take(21)
#=> [1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4, 5]
For scenarios where you are changing direction without iterating the array fully in one direction, you will have to give some examples so that one can see how to modify the above code to accommodate that
You could use Ruby's under-appreciated flip-flop operator.
arr = [1,2,3,4,5]
sz = arr.size
(2*sz-1).times { |i| puts i==0..i==arr.size-1 ? arr[i] : arr[sz-i-2] }
1
2
3
4
5
4
3
2
1

Resources