How to custom sort with ruby - ruby

I've got an array of objects which I pull from the database. But I can sort them only in ascending or descending order from database, however I need them in custom order.
Let's say I have an array of objects from db :
arr = [obj1,obj2,obj3]
where obj1 has id 1, obj2 has id 2 and obj3 has id 3
but my sort order would be 3,1,2 or I'd have some array of ids which would dictate the order i.e [3,1,2]
So the order of custom sorting would be :
arr = [obj3,obj1,obj2]
I've tried :
arr.sort_by{|a,b| [3,1,2]}
I've been reading some tutorials and links about sorting and it's mostly simple sorting. So how would one achieve the custom sorting described above?

You're close. [3,1,2] specifies an ordering, but it doesn't tell the block how to relate it to your objects. You want something like:
arr.sort_by {|obj| [3,1,2].index(obj.id) }
So the comparison will order your objects sequentially by the position of their id in the array.
Or, to use the more explicit sort (which you seem to have sort_by slightly confused with):
arr.sort do |a,b|
ordering = [3,1,2]
ordering.index(a.id) <=> ordering.index(b.id)
end

This is like #Chuck's answer, but with O(n log n) performance.
# the fixed ordering
ordering = [3, 1, 2]
# a map from the object to its position in the ordering
ordering_index = Hash[ordering.map(&:id).each_with_index.to_a]
# a fast version of the block
arr.sort_by{|obj| ordering_index[obj.id]}

Related

How can I return hash pairs of keys that sum up to less than a maximum value?

Given this hash:
numsHash = {5=>10, 3=>9, 4=>7, 2=>5, 20=>4}
How can I return the key-value pair of this hash if and when the sum of its keys would be under or equal to a maximum value such as 10?
The expected result would be something like:
newHash = { 5=>10, 3=>9, 2=>5 }
because the sum of these keys equals 10.
I've been obsessing with this for hours now and can't find anything that leads up to a solution.
Summary
In the first section, I provide some context and a well-commented working example of how to solve the defined knapsack problem in a matter of microseconds using a little brute force and some Ruby core classes.
In the second section, I refactor and expand on the code to demonstrate the conversion of the knapsack solution into output similar to what you want, although (as explained and demonstrated in the answer below) the correct output when there are multiple results must be a collection of Hash objects rather than a single Hash unless there are additional selection criteria not included in your original post.
Please note that this answer uses syntax and classes from Ruby 3.0, and was specifically tested against Ruby 3.0.3. While it should work on Ruby 2.7.3+ without changes, and with most currently-supported Ruby 2.x versions with some minor refactoring, your mileage may vary.
Solving the Knapsack Problem with Ruby Core Methods
This seems to be a variant of the knapsack problem, where you're trying to optimize filling a container of a given size. This is actually a complex problem that is NP-complete, so a real-world application of this type will have many different solutions and possible algorithmic approaches.
I do not claim that the following solution is optimal or suitable for general purpose solutions to this class of problem. However, it works very quickly given the provided input data from your original post.
Its suitability is primarily based on the fact that you have a fairly small number of Hash keys, and the built-in Ruby 3.0.3 core methods of Hash#permutation and Enumerable#sum are fast enough to solve this particular problem in anywhere from 44-189 microseconds on my particular machine. That seems more than sufficiently fast for the problem as currently defined, but your mileage and real objectives may vary.
# This is the size of your knapsack.
MAX_VALUE = 10
# It's unclear why you need a Hash or what you plan to do with the values of the
# Hash, but that's irrelevant to the problem. For now, just grab the keys.
#
# NB: You have to use hash rockets or the parser complains about using an
# Integer as a Symbol using the colon notation and raises SyntaxError.
nums_hash = {5 => 10, 3 => 9, 4 => 7, 2 => 5, 20 => 4}
keys = nums_hash.keys
# Any individual element above MAX_VALUE won't fit in the knapsack anyway, so
# discard it before permutation.
keys.reject! { _1 > MAX_VALUE }
# Brute force it by evaluating all possible permutations of your array, dropping
# elements from the end of each sub-array until all remaining elements fit.
keys.permutation.map do |permuted_array|
loop { permuted_array.sum > MAX_VALUE ? permuted_array.pop : break }
permuted_array
end
Returning an Array of Matching Hashes
The code above just returns the list of keys that will fit into your knapsack, but per your original post you then want to return a Hash of matching key/value pairs. The problem here is that you actually have more than one set of Hash objects that will fit the criteria, so your collection should actually be an Array rather than a single Hash. Returning only a single Hash would basically return the original Hash minus any keys that exceed your MAX_VALUE, and that's unlikely to be what's intended.
Instead, now that you have a list of keys that fit into your knapsack, you can iterate through your original Hash and use Hash#select to return an Array of unique Hash objects with the appropriate key/value pairs. One way to do this is to use Enumerable#reduce to call Hash#merge on each Hash element in the subarrays to convert the final result to an Array of Hash objects. Next, you should call Enumerable#unique to remove any Hash that is equivalent except for its internal ordering.
For example, consider this redesigned code:
MAX_VALUE = 10
def possible_knapsack_contents hash
hash.keys.reject! { _1 > MAX_VALUE }.permutation.map do |a|
loop { a.sum > MAX_VALUE ? a.pop : break }; a
end.sort
end
def matching_elements_from hash
possible_knapsack_contents(hash).map do |subarray|
subarray.map { |i| hash.select { |k, _| k == i } }.
reduce({}) { _1.merge _2 }
end.uniq
end
hash = {5 => 10, 3 => 9, 4 => 7, 2 => 5, 20 => 4}
matching_elements_from hash
Given the defined input, this would yield 24 hashes if you didn't address the uniqueness issue. However, by calling #uniq on the final Array of Hash objects, this will correctly yield the 7 unique hashes that fit your defined criteria if not necessarily the single Hash you seem to expect:
[{2=>5, 3=>9, 4=>7},
{2=>5, 3=>9, 5=>10},
{2=>5, 4=>7},
{2=>5, 5=>10},
{3=>9, 4=>7},
{3=>9, 5=>10},
{4=>7, 5=>10}]

Understanding Ruby array sorting syntax

I really don't understand the following sorting method:
books = ["Charlie and the Chocolate Factory", "War and Peace", "Utopia", "A Brief History of Time", "A Wrinkle in Time"]
books.sort! { |firstBook, secondBook| firstBook <=> secondBook }
How does the this work? In the ruby books, they had one parameter for example |x| represent each of the values in the array. If there is more than one parameter (firstBook and secondBook in this example) what does it represent??
Thank you!
The <=> operator returns the result of a comparison.
So "a" <=> "b" returns -1, "b" <=> "a" returns 1, and "a" <=> "a" returns 0.
That's how sort is able to determine the order of elements.
Array#sort (and sort!) called without a block will do comparisons with <=>, so the block is redundant. These all accomplish the same thing:
books.sort!
books.sort_by!{|x| x}
books.sort!{|firstBook, secondBook| firstBook <=> secondBook}
Since you are not overriding the default behavior, the second and third forms are needlessly complicated.
So how does this all work?
The first form sorts the array by using some sorting algorithm -- it's not relevant which one -- which needs to be able to compare two elements to decide which comes first. (More on this below.) It automatically, behind the scenes, follows the same logic as the third line above.
The middle form lets you choose what to sort on. For example: instead of, for each item, just sorting on that item (which is the default), you can sort on that item's length:
books.sort_by!{|title| title.length}
Then books is sorted from shortest title to longest title. If all you are doing is calling a method on each item, there's another shortcut available. This does the same thing:
books.sort_by!(&:length)
In the final form, you have control over the comparison itself. For example, you could sort backwards:
books.sort!{|first, second| second <=> first}
Why does sort need two items passed into the block, and what do they represent?
Array#sort (and sort!) with a block is how you override the comparison step of sorting. Comparison has to happen at some point during a sort in order to figure out what order to put things in. You don't need to override the comparison in most cases, but if you do, this is the form that allows that, so it needs two items passed into the block: the two items that need to be compared right now. Let's look at an example in action:
[4, 3, 2, 1].sort{|x, y| puts "#{x}, #{y}"; x <=> y}
This outputs:
4, 2
2, 1
3, 2
3, 4
This shows us that in this case, sort compared 4 and 2, then 2 and 1, then 3 and 2, and then finally 3 and 4, in order to sort the array. The precise details are irrelevant to this discussion and depend on the sorting algorithm being used, but again, all sorting algorithms need to be able to compare items in order to sort.
The block given inside {} is passed as a comparing function for method sort. |a, b| tells us that this comparing function takes 2 parameters (which is expected number of arguments since we need to compare).
This block is executed for each element in array but if we need one more argument we take next element after this.
See http://ruby-doc.org/core-2.0/Array.html#method-i-sort for an explanation. As for a single-parameter method referred to in your books, I can only guess you were looking at sort_by. Can you give an example?

Most efficient way to compile unique values in a massive text file?

I have a set of large text files that in total contain about 3 million rows.
What I want to do is pluck a value from a given column from each row and add it to an array in memory. If the value already exists in the array, then ignore it.
I'm assuming the fastest way is NOT:
Read a value
if exists (using array's native index or what-have-you method), then push it to the array
Should I be inserting the value in alphabetical order to speed up the match/search?
OR should I keep multiple arrays...for example, one for each letter of the alphabet?
Use Set:
Set implements a collection of unordered values with no duplicates. This is a hybrid of Array's intuitive inter-operation facilities and Hash's fast lookup.
Example usage:
require 'set'
set = Set.new
set << 1 << 2 << 3 # => #<Set: {1, 2, 3}>
set << 2 # => #<Set: {1, 2, 3}>
You could add the values as keys to a hash map, that would take care of removing duplicates automatically. You could even count the number of times each value occurs this way (with the hash value).

Combine array of array into all possible combinations, forward only, in Ruby

I have an array of arrays, like so:
[['1','2'],['a','b'],['x','y']]
I need to combine those arrays into a string containing all possible combinations of all three sets, forward only. I have seen lots of examples of all possible combinations of the sets in any order, that is not what I want. For example, I do not want any of the elements in the first set to come after the second set, or any in the third set to come before the first, or second, and so on. So, for the above example, the output would be:
['1ax', '1ay', '1bx', '1by', '2ax', '2ay', '2bx', '2by']
The number of arrays, and length of each set is dynamic.
Does anybody know how to solve this in Ruby?
Know your Array#product:
a = [['1','2'],['a','b'],['x','y']]
a.first.product(*a[1..-1]).map(&:join)
Solved using a recursive, so-called "Dynamic Programming" approach:
For n-arrays, combine the entries of the first array with each result on the remaining (n-1) arrays
For a single array, the answer is just that array
In code:
def variations(a)
first = a.first
if a.length==1 then
first
else
rest = variations(a[1..-1])
first.map{ |x| rest.map{ |y| "#{x}#{y}" } }.flatten
end
end
p variations([['1','2'],['a','b'],['x','y']])
#=> ["1ax", "1ay", "1bx", "1by", "2ax", "2ay", "2bx", "2by"]
puts variations([%w[a b],%w[M N],['-'],%w[x y z],%w[0 1 2]]).join(' ')
#=> aM-x0 aM-x1 aM-x2 aM-y0 aM-y1 aM-y2 aM-z0 aM-z1 aM-z2 aN-x0 aN-x1 aN-x2
#=> aN-y0 aN-y1 aN-y2 aN-z0 aN-z1 aN-z2 bM-x0 bM-x1 bM-x2 bM-y0 bM-y1 bM-y2
#=> bM-z0 bM-z1 bM-z2 bN-x0 bN-x1 bN-x2 bN-y0 bN-y1 bN-y2 bN-z0 bN-z1 bN-z2
You could also reverse the logic, and with care you should be able to implement this non-recursively. But the recursive answer is rather straightforward. :)
Pure, reduce with product:
a = [['1','2'],['a','b'],['x','y']]
a.reduce() { |acc, n| acc.product(n).map(&:flatten) }.map(&:join)
# => ["1ax", "1ay", "1bx", "1by", "2ax", "2ay", "2bx", "2by"]

Associatively sorting a table by value in Lua

I have a key => value table I'd like to sort in Lua. The keys are all integers, but aren't consecutive (and have meaning). Lua's only sort function appears to be table.sort, which treats tables as simple arrays, discarding the original keys and their association with particular items. Instead, I'd essentially like to be able to use PHP's asort() function.
What I have:
items = {
[1004] = "foo",
[1234] = "bar",
[3188] = "baz",
[7007] = "quux",
}
What I want after the sort operation:
items = {
[1234] = "bar",
[3188] = "baz",
[1004] = "foo",
[7007] = "quux",
}
Any ideas?
Edit: Based on answers, I'm going to assume that it's simply an odd quirk of the particular embedded Lua interpreter I'm working with, but in all of my tests, pairs() always returns table items in the order in which they were added to the table. (i.e. the two above declarations would iterate differently).
Unfortunately, because that isn't normal behavior, it looks like I can't get what I need; Lua doesn't have the necessary tools built-in (of course) and the embedded environment is too limited for me to work around it.
Still, thanks for your help, all!
You seem to misunderstand something. What you have here is a associative array. Associative arrays have no explicit order on them, e.g. it's only the internal representation (usually sorted) that orders them.
In short -- in Lua, both of the arrays you posted are the same.
What you would want instead, is such a representation:
items = {
{1004, "foo"},
{1234, "bar"},
{3188, "baz"},
{7007, "quux"},
}
While you can't get them by index now (they are indexed 1, 2, 3, 4, but you can create another index array), you can sort them using table.sort.
A sorting function would be then:
function compare(a,b)
return a[1] < b[1]
end
table.sort(items, compare)
As Komel said, you're dealing with associative arrays, which have no guaranteed ordering.
If you want key ordering based on its associated value while also preserving associative array functionality, you can do something like this:
function getKeysSortedByValue(tbl, sortFunction)
local keys = {}
for key in pairs(tbl) do
table.insert(keys, key)
end
table.sort(keys, function(a, b)
return sortFunction(tbl[a], tbl[b])
end)
return keys
end
items = {
[1004] = "foo",
[1234] = "bar",
[3188] = "baz",
[7007] = "quux",
}
local sortedKeys = getKeysSortedByValue(items, function(a, b) return a < b end)
sortedKeys is {1234,3188,1004,7007}, and you can access your data like so:
for _, key in ipairs(sortedKeys) do
print(key, items[key])
end
result:
1234 bar
3188 baz
1004 foo
7007 quux
hmm, missed the part about not being able to control the iteration. there
But in lua there is usually always a way.
http://lua-users.org/wiki/OrderedAssociativeTable
Thats a start. Now you would need to replace the pairs() that the library uses. That could be a simples as pairs=my_pairs. You could then use the solution in the link above
PHP arrays are different from Lua tables.
A PHP array may have an ordered list of key-value pairs.
A Lua table always contains an unordered set of key-value pairs.
A Lua table acts as an array when a programmer chooses to use integers 1, 2, 3, ... as keys. The language syntax and standard library functions, like table.sort offer special support for tables with consecutive-integer keys.
So, if you want to emulate a PHP array, you'll have to represent it using list of key-value pairs, which is really a table of tables, but it's more helpful to think of it as a list of key-value pairs. Pass a custom "less-than" function to table.sort and you'll be all set.
N.B. Lua allows you to mix consecutive-integer keys with any other kinds of keys in the same table—and the representation is efficient. I use this feature sometimes, usually to tag an array with a few pieces of metadata.
Coming to this a few months later, with the same query. The recommended answer seemed to pinpoint the gap between what was required and how this looks in LUA, but it didn't get me what I was after exactly :- which was a Hash sorted by Key.
The first three functions on this page DID however : http://lua-users.org/wiki/SortedIteration
I did a brief bit of Lua coding a couple of years ago but I'm no longer fluent in it.
When faced with a similar problem, I copied my array to another array with keys and values reversed, then used sort on the new array.
I wasn't aware of a possibility to sort the array using the method Kornel Kisielewicz recommends.
The proposed compare function works but only if the values in the first column are unique.
Here is a bit enhanced compare function to ensure, if the values of a actual column equals, it takes values from next column to evaluate...
With {1234, "baam"} < {1234, "bar"} to be true the items the array containing "baam" will be inserted before the array containing the "bar".
local items = {
{1004, "foo"},
{1234, "bar"},
{1234, "baam"},
{3188, "baz"},
{7007, "quux"},
}
local function compare(a, b)
for inx = 1, #a do
-- print("A " .. inx .. " " .. a[inx])
-- print("B " .. inx .. " " .. b[inx])
if a[inx] == b[inx] and a[inx + 1] < b[inx + 1] then
return true
elseif a[inx] ~= b[inx] and a[inx] < b[inx] == true then
return true
else
return false
end
end
return false
end
table.sort(items,compare)

Resources