ruby uniq on the first two columns of two dimensional array - ruby-on-rails

I have a two dimensional array with three fields in the second dimension.
Is it possible to use uniq on the first two fields of the second dimension?
I have seen array.uniq! {|c| c.first}. When I am correct, this apply uniq on the first field of the array.
Is it possible to use something like array.uniq! {|c| c.first c.second}?
#array = Array.new()
#array << Array.new([journal.from_account_number, journal.from_account,
journal.buchungsart])
There are several entries in #array.
The question was how to get unique values from array not considering journal.buchungsart.
The answer was: #array = #array.uniq! {|c| [c.first, c.second]}

Yes, put the values in an array:
array.uniq! {|c| [c.first, c.second]}

Just return an array of two elements in the block.
Since Array#second is not defined in the standard library, do
array.uniq! { |c| [c[0], c[1]] }
instead, which can be further simplified to array.uniq! { |c| c[0..1] }

You could also make use of the fact that hash keys are unique.
arr = [[1, 2, 3],
[2, 1, 4],
[1, 2, 5]]
a = arr.each_with_object({}) { |row, h| h.update(row.first(2)=>row) }.values
#=> [[1, 2, 5], [2, 1, 4]]
See Hash#update (aka merge!).
Before extracting the hash values with Hash#value, we have computed the following hash.
arr.each_with_object({}) { |row, h| h.update(row.first(2)=>row) }
#=> {[1, 2]=>[1, 2, 5], [2, 1]=>[2, 1, 4]}
Notice that, for given values of the first two elements in a row, it is the last row of arr with those values that is to be "kept". The first such row in arr is to be kept, use the following.
arr.each_with_object({}) { |row, h| h.update(row.first(2)=>row) { |_,o,_| o } }.values
#=> [[1, 2, 3], [2, 1, 4]]
This does not mutate arr. If arr is to be modified, write
arr.replace(a)
where a is defined above.

Array#first accepts a parameter :
%w(a b c d e f).first(2)
# => ["a", "b"]
so you could just use :
array.uniq!{ |c| c.first(2) }

Related

Create list of lists from list of json objects ruby

I have a list of json objects something like this:
test = [{"a": 1, "b": 2, "c": 3}, {"a": 4, "b": 5, "c":6}]
I want to fetch the fields 'a' and 'c' from the above test list to create a list of list something like this:
[[1, 4], [3, 6]]
The idea is to make a list of all the values of a, then the values of c.
When I am using pluck:
test.pluck(:a, :c)
I am getting output like this:
[[1, 3], [4, 6]]
One approach I tried which is working fine.
res = []
res << test.pluck(:a)
res << test.pluck(:c)
But I am thinking it would be better if I get one or two liner solution,
with or without inbuilt function because the number of fields in the future may increase.
You were looking for following,
%i(a c).map { |x| test.map { |e| e[x] } }
You could use Array#transpose on the pluck result. Which assumes the array represents rows or columns and swaps the representation around.
test.pluck(:a, :c).transpose
#=> [[1, 4], [3, 6]]

remove array from multidimensional array if first 3 values aren't unique in ruby on rails

OK, so I have this array of arrays. Each array within the larger array is very much the same, ten specific values. If my value at location 3 is a specific value, then I want to iterate through the rest of the remaining arrays within the larger array and see if the first 3 values at locations 0, 1, and 2 match. if they then match, I'd like to delete the original array. I'm having a hard time with it, maybe there is an easy way? I'm sure there is, I'm fairly new to this whole coding stuff =) So much appreciation in advance for your help....
here's where I'm at:
#projectsandtrials.each do |removed|
if removed[3] == ["Not Harvested"]
#arraysforloop = #projectsandtrials.clone
#arraysforloop1 = #arraysforloop.clone.delete(removed)
#arraysforloop1.each do |m|
if (m & [removed[0], removed[1], removed[2]]).any?
#projectsandtrials.delete(removed)
end
end
end
end
Lets look at your situation:
#projectsandtrials.each do |removed|
// some logic, yada yada
#projectsandtrials.delete(removed)
end
You can't just delete stuff out of an array you're iterating through. At least not until you finish iterating through it. What you should be using instead is a filtering method like reject instead of just an each.
So instead of deleting right there, you should just return true when using reject.
I think about it like this when iterating through arrays.
Do I want the array to stay the same size and have the same content?
Use each.
Do I want the array to be the same size, but have different content?
Use map.
Do I want the array to be less than or equal to the current size?
Use select or reject.
Do I want it to end up being a single value?
Use reduce.
Code
def prune(arr, val)
arr.values_at(*(0..arr.size-4).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }.
concat((arr.size-3..arr.size-1).to_a))
end
Example
arr = [ [1,2,3,4,0],
[3,4,5,6,1],
[3,4,5,4,2],
[3,4,5,6,3],
[3,4,5,6,4],
[3,4,0,6,5],
[2,3,5,4,6],
[2,3,5,5,7],
[2,3,5,7,8],
[2,3,5,8,9],
[2,3,5,7,0]
]
Notice that the last values of the elements (arrays) of arr are consecutive. This is to help you identify the elements of prune(arr, 4) (below) that have been dropped.
prune(arr, 4)
# => [[3, 4, 5, 6, 1],
# [3, 4, 5, 4, 2],
# [3, 4, 5, 6, 3],
# [3, 4, 5, 6, 4],
# [3, 4, 0, 6, 5],
# [2, 3, 5, 5, 7],
# [2, 3, 5, 7, 8],
# [2, 3, 5, 8, 9],
# [2, 3, 5, 7, 0]]
Explanation
The arrays at indices 0 and 6 have not been included in array returned.
arr[0] ([1,2,3,4,0]) has not been included because arr[0][3] = val = 4 and arr[1], arr[2] and arr[3] all begin [3,4,5].
arr[6] ([2,3,5,4,6]) has not been included because arr[6][3] = 4 and arr[7], arr[8] and arr[9] all begin [2,3,5].
arr[2] ([3,4,5,5,2]) has been included because, while arr[2][3] = 4, arr[3][0,3], arr[4][0,3] and arr[5][0,3] all not all equal (i.e., arr[5][2] = 0).
Notice that the last three elements of arr will always be included in the array returned.
Now let's examine the calculations. First consider the following.
arr.size
#=> 11
a = (0..arr.size-4).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }
#=> (0..7).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }
#=> [1, 2, 3, 4, 5, 7]
Consider reject's block calculation for i=0 (recall val=4).
arr[i][3] == val && arr[i+1..i+3].transpose[0,3].map(&:uniq).all? {|a| a.size==1 }}
#=> 4 == 4 && arr[1..3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3,4,5,6,1],
# [3,4,5,4,2],
# [3,4,5,6,3]].transpose[0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3, 3, 3],
# [4, 4, 4],
# [5, 5, 5],
# [6, 4, 6],
# [1, 2, 3]][0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3, 3, 3],
# [4, 4, 4],
# [5, 5, 5]].map(&:uniq).all? { |a| a.size==1 }
#=> [[3], [4], [5]].all? { |a| a.size==1 }
#=> true
meaning arr[0] is to be rejected; i.e., not included in the returned array.
The remaining block calculations (for i=1,...,10) are similar.
We have computed
a #=> [1, 2, 3, 4, 5, 7]
which are the indices of all elements of arr except the last 3 that are to be retained. To a we add the indices of the last three elements of arr.
b = a.concat((arr.size-3..arr.size-1).to_a)
#=> a.concat((8..10).to_a)
#=> a.concat([8,9,10])
#=> [1, 2, 3, 4, 5, 7, 8, 9, 10]
Lastly,
arr.values_at(*b)
returns the array given in the example.
Your code snippet seems fine, although there are couple of things to note:
#arraysforloop.clone.delete(removed) removes all the occurences of removed array (not only the first one). E.g. [1,2,3,1].delete(1) would leave you with [2,3]. You could fix it with using an iterator for #projectsandtrials and delete_at method.
delete method returns the same argument you pass to it (or nil if no matches found). So #arraysforloop1 = #arraysforloop.clone.delete(removed) makes your #arraysforloop1 to contain the removed array's elements only! Removing an assignment could save you.
I see no reason to have two cloned arrays, #arraysforloop and #arraysforloop1, as the former one is not used anyhow later. May be we could omit one of them?
#projectsandtrials.delete(removed) leaves you in a strange state, as long as you're iterating the same array right now. This could end up with you missing the right next element after the removed one. Here is a simple snippet to illustrate the behaviour:
> a = [1,2,3]
> a.each{|e, index| puts("element is: #{e}"); a.delete(1);}
element is: 1
element is: 3
As you see, after deleting element 1 the loop moved to element 3 directly, omitting the 2 (as it became the first element in array and algorithm thinks it's been handled already).
One of the possibilities to make it less messy is to split it to a bundle of methods. Here is an option:
def has_searched_element? row
# I leave this method implementation to you
end
def next_rows_contain_three_duplicates?(elements, index)
# I leave this method implementation to you
end
def find_row_ids_to_remove elements
[].tap do |result|
elements.each_with_index do |row, index|
condition = has_searched_element?(row) && next_rows_contain_three_duplicates?(elements, index)
result << index if condition
end
end
end
row_ids_to_remove = find_row_ids_to_remove(#projectsandtrials)
# now remove all the elements at those ids out of #projectsandtrials

make sure two fields in array are unique

I need to make sure the follower_id and followed_id are unique in an array which also includes a third number, called value. All are integers. It is the combination of follower_id and followed_id that needs to be unique not the individual numbers themselves. Here is what I have
Relationship.populate 1..20 do |relationship|
relationship.follower_id = (1..20)
relationship.followed_id = (1..20)
relationship.value = rand(1..5)
end
this would ensure that
1,3,5
1,3,5
2,3,5
1,2,5
would be
1,3,5
2,3,5
1,2,5
Assuming, that the order in pairs is not to be taken into account, and you want to eliminate triples, even having different values, here you go:
a = [[1,3,5], [3,1,5], [2,3,5], [2,3,6], [1,2,5]]
# to count [1,3,5] and [3,1,5] as similar
a.group_by { |(fd,fr,_)| [fd,fr].sort }.values.map &:first
# to count [1,3,5] and [3,1,5] as different
a.group_by { |(fd,fr,_)| [fd,fr] }.values.map &:first
#⇒ [[1,3,5], [3,1,5], [2,3,5], [1,2,5]]
a = [[1,3,5], [1,3,4], [3,1,2], [2,3,2], [2,3,1], [1,2,3]]
If the order of the first two elements of each element of a is important:
a.uniq { |e| e[0,2] }
#=> [[1, 3, 5], [3, 1, 2], [2, 3, 2], [1, 2, 3]]
If the order of the first two elements of each element of a is not important:
require 'set'
a.uniq { |e| Set.new e[0,2] }
#=> [[1, 3, 5], [2, 3, 2], [1, 2, 3]]
Perfect Uniqueness
Here's a solution assuming the order within the triples matters and you want each triple to be perfectly unique. Just use the uniq method on the Array class. It works like this:
[
[1,3,5],
[1,3,5],
[2,3,5],
[1,2,5]
].uniq
#=> [[1, 3, 5], [2, 3, 5], [1, 2, 5]]
Partial Uniqueness
If instead, you only care about the first two being unique, pass the uniq method a block that returns whatever subset you want to be unique. If you only want the first two elements of the triple and can discard duplicates even when the third element is unique, you can just pass it the range 0..1.
[
[1,3,5],
[1,3,5],
[2,3,5],
[1,2,5],
[1,2,6]
].uniq { |triple| triple[0..1] }
#=> [[1, 3, 5], [2, 3, 5], [1, 2, 5]]
Note that the last element, [1,2,6] was discarded even though it ended in 6 because it was considered a duplicate of [1,2,5]. This is because [1,2,5][0..1] #=> [1,2] and [1,2,6][0..1] #=> [1,2].

Pushing max hash key-values into an array in Ruby?

I have a hash called count, defined as count = {4=>2, 5=>3, 6=>3, 7=>1}.
I want to take the max value, and then push the key that corresponds to that value into an array, so I do this:
array = []
array.push(count.max_by{|k,v| v}[0])
=>> [5]
However, 6 also has the value 3, which is another maximum value. How do I push this value into the array so I get [5,6] instead of just [5]?
This is the way to choose the max values of the hash:
count.values.max
=> 3
Use the select method on the hash:
count.select{ |k, v| v == count.values.max }
=> {5=>3, 6=>3}
Get the keys:
count.select{ |k, v| v == count.values.max }.keys
=> [5, 6]
And finally assign to an array:
array = count.select{ |k, v| v == count.values.max }.keys
This could probably be improved dramatically, but off the top of my head:
count.group_by{|k,v| v}.max_by{|k,v| k}.last.map(&:first)
First, group the key/value pairs of the hash so that the ones with the same value are in the same groups:
count.group_by{|k,v| v} #=> {2=>[[4, 2]], 3=>[[5, 3], [6, 3]], 1=>[[7, 1]]}
Then get the group with the maximum value:
.max_by{|k,v| k} #=> [3, [[5, 3], [6, 3]]]
Now, we just want the original keys out of that, so first we take the last element of the pair:
.last #=> [[5, 3], [6, 3]]
And we want just the first element of each of those nested pairs:
.map(&:first) #=> [5, 6]
This approach avoids one pass through the map as compared to the select-based solutions. It's probably not a significant performance win unless the data set is really huge, in which case the intermediate data structures being built by my solution will be more of a problem anyway.
max = count.values.max
array = count.keys.select{|k| count[k] == max}

ruby - Permutation between elements of an array

I'm coding a plugin in Google Sketchup with ruby and I faced a real problem while trying to permute two arrays that are present in an array all this depending on a user combination.
I have an array of arrays like [["1"],["lol"], ["so"]]
When we have a combination like this <[1,
2, 3] it's fine, it should stay the same : [["1"],["lol"], ["so"]]
But when we have a combination like this [2, 3, 1], the output should be : [["lol"], ["so"], ["1"]]
For [3,1,2] => [["so"], ["1"], ["lol"]]
...etc
EDIT
Sorry guys I forgot for the array I have a bit like : [["1, 2, 3"], ["lol1, lol2, lol3"], ["so1, so2, so3"]] so for the combination [2, 3, 1] the output should be : [["2, 3, 1"], ["lol2, lol3, lol1"], ["so2, so3, so1"]]
Thanks for helping me out.
You could use collect:
array = [["1"],["lol"], ["so"]]
indexes = [2, 1, 3]
indexes.collect {|i| array[i-1]} #=> [["lol"], ["1"], ["so"]]
If you set the indexes to be 0-based you could drop the -1
split and map can be used to turn your strings into values:
"1, 2, 3".split(",").map { |i| i.to_i} # [1, 2, 3]
You can then also split your strings
"lol2, lol3, lol1".split(/, /) #=> ["lol2", "lol3", "lol1"]
You should be able to put that together with the above to get what you want.
indexes = [2, 1, 3]
array = [["1"],["lol"], ["so"]]
result = indexes.map{|index| array[index-1] }
You should also take a look at active_enum
https://github.com/adzap/active_enum
You could do something like:
class YourClassName < ActiveEnum::Base
value [1] => ['1']
value [2] => ['lol']
value [3] => ['so']
end
a = [["1"], ["lol"], ["so"]]
index = [2, 1, 3]
index.collect {|i| a[i - 1]}
This outputs
[["lol"], ["1"], ["so"]]

Resources