how to find best matching element in array of numbers? - ruby

I need help with something that seems simple but confuses me. Trying to write some fuzzy matching method that copes with differences in format between what value is computed as needed, and which are actually available from a selection list.
The value (option strike price) is always a computed Float like 85.0 or Int.
The array contains numbers in string form, unpredictable in either increment or whether they will be shown rounded to some decimal (including extra zeros like 5.50) or no decimal (like 85), eg.:
select_list = ["77.5", "80", "82.5", "85", "87.5", "90", "95", "100", "105"]
I am unsure how to write a simple line or two of code that will return the closest matching element (by number value) as it appears in the array. For example, if select_list.contains? 85.0 returned "85"
Actually, the selection choices come from a Watir::Webdriver browser.select_list(:id, "lstStrike0_1") HTML object whose visible text (not HTML value) are those numbers; maybe there is a more direct way to just call browser.select_list(:id, "lstStrike0_1").select X without having to figure out in Watir how to convert all those choices into a Ruby array?

xs = ["77.5", "80", "82.5", "85", "87.5", "90", "95", "100", "105"]
xs.min_by { |x| (x.to_f - 82.4).abs }
#=> "82.5"

I'm not a ruby coder so this might not be the best way to do it
def select_closest(list, target)
return (list.map {|x| [(x.to_f - target).abs, x]}).min[1]
end
select_list = ["77.5", "80", "82.5", "85", "87.5", "90", "95", "100", "105"]
puts select_closest(select_list, 81) # yields 80

Related

JSONata prevent array flattening

Q: How do I prevent JSONata from "auto-flattening" arrays in an array constructor?
Given JSON data:
{
"w" : true,
"x":["a", "b"],
"y":[1, 2, 3],
"z": 9
}
the JSONata query seems to select 4 values:
[$.w, $.x, $.y, $.z]
The nested arrays at $.x and $.y are getting flattened/inlined into my outer wrapper, resulting in more than 4 values:
[ true, "a", "b", 1, 2, 3, 9 ]
The results I would like to achieve are
[ true, ["a", "b"], [1, 2, 3], 9 ]
I can achieve this by using
[$.w, [$.x], [$.y], $.z]
But this requires me to know a priori that $.x and $.y are arrays.
I would like to select 4 values and have the resulting array contain exactly 4 values, independent of the types of values that are selected.
There are clearly some things about the interactions between JSONata sequences and arrays that I can't get my head around.
In common with XPath/XQuery sequences, it will flatten the results of a path expression into the output array. It is possible to avoid this in your example by using the $each higher-order function to iterate over an object's key/value pairs. The following expression will get what you want without any flattening of results:
$each($, function($v) {
$v
})
This just returns the value for each property in the object.
UPDATE: Extending this answer for your updated question:
I think this is related to a previous github question on how to combine several independent queries into the same question. This uses an object to hold all the queries in a similar manner to the one you arrived at. Perhaps a slightly clearer expression would be this:
{
"1": t,
"2": u.i,
"3": u.j,
"4": u.k,
"5": u.l,
"6": v
} ~> $each(λ($v){$v})
The λ is just a shorthand for function, if you can find it on your keyboard (F12 in the JSONata Exerciser).
I am struggling to rephrase my question in such as way as to describe the difficulties I am having with JSONata's sequence-like treatment of arrays.
I need to run several queries to extract several values from the same JSON tree. I would like to construct one JSONata query expression which extracts n data items (or runs n subqueries) and returns exactly n values in an ordered array.
This example seems to query request 6 values, but because of array flattening the result array does not have 6 values.
This example explicitly wraps each query in an array constructor so that the result has 6 values. However, the values which are not arrays are wrapped in an extraneous & undesirable array. In addition one cannot determine what the original type was ...
This example shows the result that I am trying to accomplish ... I asked for 6 things and I got 6 values back. However, I must know the datatypes of the values I am fetching and explicitly wrap the arrays in an array constructor to work-around the sequence flattening.
This example shows what I want. I queried 6 things and got back 6 answers without knowing the datatypes. But I have to introduce an object as a temporary container in order to work around the array flattening behavior.
I have not found any predicates that allow me to test the type of a value in a query ... which might have let me use the ?: operator to dynamically decide whether or not to wrap arrays in an array constructor. e.g. $isArray($.foo) ? [$.foo] : $.foo
Q: Is there an easier way for me to (effectively) submit 6 "path" queries and get back 6 values in an ordered array without knowing the data types of the values I am querying?
Building on the example from Acoleman, here is a way to pass in n "query" strings (that represent paths):
(['t', 'u.i', 'u.j', 'u.k', 'u.l', 'v'] {
$: $eval('$$.' & $)
}).$each(function($o) {$o})
and get back an array ofn results with their original data format:
[
12345,
[
"i",
"ii",
"iii"
],
[],
"K",
{
"L": "LL"
},
null
]
It seems that using $each is the only way to avoid any flattening...
Granted, probably not the most efficient of expressions, since each has to be evaluated from a path string starting at the root of the data structure -- but there ya go.

Ruby custom sorting of first n elements

I would like to sort an array of string based on my custom ordering. Problem is I dont know all the elements in array but Im sure that it has 3 strings (high/med/low). So I would like those 3 to be first 3 values . Rest at last
Eg:
Incoming arrays
array1 = ["high", "not impt" , "med" , "kind of impt" , "low" ]
array2 = ["low", "rand priority", "med", "high"]
Only high med and low are fixed, rest all keep changing or might not be present at all
required output
["high", "med", "low", rest.(order doesn't matter)]]
I know I can delete and merge, But it will be confusing in code as to why Im doing delete and merge. Any better way?
You can use sort_by method and implement something like this:
["high", "not impt" , "med" , "kind of impt" , "low" ].sort_by do |a|
["high", "med", "low"].index(a) || Float::INFINITY
end
index method returns 0, 1 and 2 for "high", "med" and "low" correspondingly and nil for other values. Thus, "high", "med" and "low" is going to be at the beginning and others at the end since every value is less than Float::INFINITY

use ruby to identify if array of terms contains one and ONLY one repeated term

In categorical logic, categorical syllogism requires four terms, but can only have three unique terms; that is one and ONLY one term can and must be repeated.
I trying to write a Ruby test for this but having a little bit of trouble.
Consider the following three arrays of terms, the first of which is a valid list, while the other two are invalid (termarray2 contains 4 unique terms and termarray3 contains only 2 unique terms).
termarray1 = ["Dogs", "Mortal Things", "Mortal Things", "Living Things"]
termarray2 = ["Dogs", "Mortal Things", "Cats", "Living Things"]
termarray3 = ["Dogs", "Mortal Things", "Mortal Things", "Dogs"]
I want to write a test called three_terms?
It should return true for termarray1 and false for termarray2 and termarray3
Any ideas how I could to this?
The uniq methods returns the unique elements in an array.
This should work:
array.uniq.count == 3
But the test you mention also checks that the original array has four elements. Thus the entire check should be:
(array.count == 4) && (array.uniq.count == 3)
Check to see if the size of unique elements is 3 (using Array#uniq):
array.uniq.size == 3
You could also monkey-patch Array with three_terms?:
class Array
def three_terms?
uniq.size == 3
end
end

ruby return fixed number of elements in a certain order

This might be a dumb question.
I know sample returns random number of elements from an array.
For example,
[1,2,3].sample.times do
Is there a way to return a fixed number of elements in a certain order always?
I dont know how to do this in ruby.
EDIT:
Lets say I always want to return penalty_name, severity and name only from the second and last array here always.:
offenses = PERSON_SUMMARY[:offenses].map do |offense|
offense[:penalties].map do |penalty|
penalty.merge(name: offense[:offense_name])
end
end.flatten
=> [{:penalty_name=>"Prison", :severity=>"Medium", :name=>"Speeding"}, {:penalty_name=>"Ticket", :severity=>"Low", :name=>"Speeding"}, {:penalty_name=>"Prison", :severity=>"Medium", :name=>"Shoplifting"}, {:penalty_name=>"Fine", :severity=>"Low", :name=>"Shoplifting"}]
right now I am doing:
offenses.each do |hash|
hash.sample
I think you want something like:
[1,2,3,4,5].sample(4).sort
It will take 4 random number from the array and order it...
edit - after your comment:
[5,4,3,2,1].values_at(1,-1).sort #second element(1) and last one(-1)
=>[1, 4]
You can specify which ones you want with values_at (negative numbers count from the back.)
ar=[{:penalty_name=>"Prison", :severity=>"Medium", :name=>"Speeding"}, {:penalty_name=>"Ticket", :severity=>"Low", :name=>"Speeding"}, {:penalty_name=>"Prison", :severity=>"Medium", :name=>"Shoplifting"}, {:penalty_name=>"Fine", :severity=>"Low", :name=>"Shoplifting"}]
p ar.values_at(1,-1).map{|h|h.values}
#=> [["Ticket", "Low", "Speeding"], ["Fine", "Low", "Shoplifting"]]

How to implement argmax in ruby?

Given a json array:
[{ "x":"5", "y":"20" },{ "x":"6", "y":"10" },{ "x":"50", "y":"5" }]
I'd like to find argmax(x), such that I can do puts argmax(arr, :arg => "x").y and get 5. How can I elegantly implement this in Ruby?
Edit: Clarified a bit. The idea is that you can specify the field of an element in a list that you want to maximize and the method will return the maximizing element.
I think you want Enumerable#max_by. To get y like you're saying, it would be:
arr.max_by {|hash| hash['x']}['y']
(Well, actually, you'll want the numbers to be numbers instead of strings, since '50' sorts lower than '6'. But I think you get the idea. You can to_i or do whatever processing you need in the block to get the "real" value to sort by.)

Resources