Two indexes in Ruby for loop - ruby

can you have a ruby for loop that has two indexes?
ie:
for i,j in 0..100
do something
end
Can't find anything in google
EDIT: Adding in more details
I need to compare two different arrays like such
Index: Array1: Array2:
0 a a
1 a b
2 a b
3 a b
4 b b
5 c b
6 d b
7 d b
8 e c
9 e d
10 e d
11 e
12 e
But knowing that they both have the same items (abcde)
This is my logic in pseudo, lets assume this whole thing is inside a loop
#tese two if states are for handling end-of-array cases
If Array1[index_a1] == nil
Errors += Array1[index_a1-1]
break
If Array2[index_a1] == nil
Errors += Array2[index_a2-1]
break
#this is for handling mismach
If Array1[index_a1] != Array2[index_a2]
Errors += Array1[index_a1-1] #of course, first entry of array will always be same
if Array1[index_a1] != Array1[index_a1 - 1]
index_a2++ until Array1[index_a1] == Array2[index_a2]
index_a2 -=1 (these two lines are for the loop's sake in next iteration)
index_a1 -=1
if Array2[index_a2] != Array2[index_a2 - 1]
index_a1++ until Array1[index_a1] == Array2[index_a2]
index_a2 -=1 (these two lines are for the loop's sake in next iteration)
index_a1 -=1
In a nutshell, in the example above,
Errors looks like this
a,b,e
As c and d are good.

You could iterate over two arrays using Enumerators instead of numerical indices. This example iterates over a1 and a2 simultaneously, echoing the first word in a2 that starts with the corresponding letter in a1, skipping duplicates in a2:
a1 = ["a", "b", "c", "d"]
a2 = ["apple", "angst", "banana", "clipper", "crazy", "dizzy"]
e2 = a2.each
a1.each do |letter|
puts e2.next
e2.next while e2.peek.start_with?(letter) rescue nil
end
(It assumes all letters in a1 have at least one word in a2 and that both are sorted -- but you get the idea.)

The for loop is not the best way to approach iterating over an array in Ruby. With the clarification of your question, I think you have a few possibly strategies.
You have two arrays, a and b.
If both arrays are the same length:
a.each_index do |index|
if a[index] == b[index]
do something
else
do something else
end
end
This also works if A is shorter than B.
If you don't know which one is shorter, you could write something like:
controlArray = a.length < b.length ? a : b to assign the controlArray, the use controlArray.each_index. Or you could use (0..[a.length, b.length].min).each{|index| ...} to accomplish the same thing.
Looking over your edit to your question, I think I can rephrase it like this: given an array with duplicates, how can I obtain a count of each item in each array and compare the counts? In your case, I think the easiest way to do that would be like this:
a = [:a,:a,:a,:b,:b,:c,:c,:d,:e,:e,:e]
b = [:a,:a,:b,:b,:b,:c,:c,:c,:d,:e,:e,:e]
not_alike = []
a.uniq.each{|value| not_alike << value if a.count(value) != b.count(value)}
not_alike
Running that code gives me [:a,:b,:c].
If it is possible that a does not contain every symbol, then you will need to have an array which just contains the symbols and use that instead of a.uniq, and another and statement in the conditional could deal with nil or 0 counts.

the two arrays are praticatly the same except for a few elements that i have to skip in either/or every once in a while
Instead of skipping during iterating, could you pre-select the non-skippable ones?
a.select{ ... }.zip( b.select{ ... } ).each do |a1,b1|
# a1 is an entry from a's subset
# b1 is the paired entry bfrom b's subset
end

Related

How do you Compare Key Value Pairs Ruby

What is the easiest way to compare each key-value pair in a hash in Ruby, to one another?
For example,
I want to sort this code so the highest three values are first. If the third spot has values that are all the same, then I want the one greatest
key to go in that spot.
{"Aa"=>1, "DDD"=>1, "DdD"=>1, "aA"=>1, "aa"=>1, "bb"=>1, "cC"=>1, "cc"=>1, "ddd"=>3, "e"=>7}
I need the above hash to be {"e"=>7, "ddd"=>3, "aa"=>1}
One string - last string makes what u want. Add .reverse after .sort for changing sort direction.
That is a solution:
# two lines for test
m = %w(a a a a a DDD DD ddd Ddd ddd e e e cC cC cC cC b x XXX XXX XXX ZZZ ZZZ ZZZ)
m = %w(n n n KKK KKK KKK KKK KKK LLL LLL LLL kk kk kk kk kk kk kk kk)
m = m.inject(Hash.new(0)) {|h, n| h.update(n => h[n]+1)}
m = m.sort.sort_by {|k, val| -val}.to_h
I would do it in three steps:
(1) Convert your Hash h to an Array of pairs:
kv_array = h.to_a
(2) Sort the array according to your criterion:
kv_array.sort! do
|left, right|
#.... implement your comparision here
end
(3) Turn your sorted array into a Hash
h_sorted = Hash[kv_array]
as suggested by #tadman, using a group_by then sorting the relevant groups will get you what you want, although you will need to tweak to fit your actual need, as a lot of assumptions were made:
m = {"Aa"=>1, "DDD"=>1, "DdD"=>1, "aA"=>1, "aa"=>1, "bb"=>1, "cC"=>1, "cc"=>1, "ddd"=>3, "e"=>7}
m.group_by { |k,v| v }
.each_with_object([]) {|a,x| x << [ a[1].compact.map { |b| b[0] }.min, a[0] ] }
.sort{|a| a[1]}
.to_h
=> {"e"=>7, "ddd"=>3, "Aa"=>1}
explanation:
firstly we group by the value (which returns a hash with the value as the first key, and an array of the hashes that match)
then we collect the grouped hash, and find the "first" key for each grouped value (using min) ... * this is an assumption * ... returning the whole thing as an array
then we sort the new array based on the "value"
then we convert the array to a hash
I added in new-lines to aid readability (hopefully)
I am so excited to get so many great suggestions! Thank you! This entire problem was to take a string and output the top three occurring words in an array. But, no special characters were allowed unless they were an apostrophe that was within the word. If there were multiple values and they were in the top three you had to pick the one that came closest to "a". In the beginning, I used the tally method to add everything up really quickly, and my plan was then to sort the hash by the value, but then when the values were the same I couldn't put the right key-value pair where it had to be if they shared the same value.
So, I came here and asked about sorting a hash, and then realized I needed to scratch my entire approach altogether. In the end, I found that I could sort the hash to an extent, but not to the place I needed/wanted so this is what I came up with!
def top_3_words(str)
str.scan(/[\w]+'?[\w]*/).sort.slice_when{|a, b| a != b}.max(3){|a, b| a.size <=> b.size}.flatten.uniq
end
p top_3_words("a a a b c c d d d d e e e e e") == ["e", "d", "a"]
p top_3_words("e e e e DDD ddd DdD: ddd ddd aa aA Aa, bb cc cC e e e") == ["e", "ddd", "aa"]
p top_3_words(" //wont won't won't ") == ["won't", "wont"]
p top_3_words(" , e .. ") == ["e"]
p top_3_words(" ... ") == []
p top_3_words(" ' ") == []
p top_3_words(" ''' ") == []
p top_3_words("""In a village of La Mancha, the name of which I have no desire to call to
mind, there lived not long since one of those gentlemen that keep a lance
in the lance-rack, an old buckler, a lean hack, and a greyhound for
coursing. An olla of rather more beef than mutton, a salad on most
nights, scraps on Saturdays, lentils on Fridays, and a pigeon or so extra
on Sundays, made away with three-quarters of his income.""") == ["a", "of", "on"]
My thought process with this was to call scan on the str so I could get everything I didn't want in the strings out of them. Then, I called sort on that return value because the last test case was really big and I needed all the like words together. After that, I called slice_when on that return value and said when a doesn't equal b then slice, so there would be a multi d array that I then could call max on, and because I sorted earlier, the values would be alphabetical so if there was a shared value it would give me the right one. I passed 3 to max to get the top three, and then called flatten so I had one array, and uniq to take out the extra characters!!
This is all so different from my original question, but I thought I would share what I was working on in case it could ever help anyone in the future!!

Identifying Triangle with if/else

Question is a user gives 3 sides and identifies triangles, like equilateral, isosceles and scalene. Here is my coding, I don't know why gives any sides that always show up "invalid". I think it's logic wrong, but I can't figure out.
puts "please input the length of 3 sides:"
a = gets.chomp.to_i
b = gets.chomp.to_i
c = gets.chomp.to_i
if a + b <= c
puts "invalid"
elsif a <= 0 || b <= 0 || c <= 0
puts "invalid"
else
if a == b && b == c
puts"equilateral triangle"
elsif a == b
puts"isosceles triangle"
else
puts"scalene triangle"
end
end
The fact that your code always prints "invalid" makes me think that input is passed in on one line instead of being on separate lines. For example, when the input is:
50 50 50
instead of getting 50 in all three variables you would get 50 in a and 0 in b, c. This is because gets takes in an entire line instead of taking one value.
In such an event, this is what you need:
a, b, c = gets.split.map{ |value| value.to_i }
A better more effective way to do this is to store the values of the triangle sides into a hash first, the value of of each triangle side will be the keys, and the value of each key can be the repeats. This will work with strings too.
Here is an Example.
# First you get an array, you can use gets.chomp as string and split to array, whichever way you choose, but in the end we end up with an array, and we pass the array to the method.
def triangle_type(arr)
# Create new empty hash
repeated_sides = Hash.new(0)
# First make sure the array is only a length of three. (this is optional)
if arr.length == 3
# Iterate through each value in the array and store it to to a hash to find duplicates
arr.each do |x|
repeated_sides[x] += 1
end
# sort the hash by it's values in descending order, for logic to work later.
repeated_sides = repeated_sides.sort_by {|k,v| v}.reverse.to_h
# uncomment this below to see the duplicate sides hash
#puts "#{repeated_sides}"
# Iterate through the sorted hash, apply logic starting from highest and first value the iterator will find.
repeated_sides.each do |k,v|
return v == 3 ? 'Equilateral Triangle' : v == 2 ? 'Isosceles Triangle' : 'Scalene Triangle'
end
end
# Return Not a triangle if the condition fails
return 'Not a triangle'
end
# Test with integers
puts triangle_type([4,1,2,5]) # output: Not a triangle
puts triangle_type([3,3,3]) # output: Equilateral Triangle
puts triangle_type([4,3,3]) # output: Isosceles Triangle
puts triangle_type([4,2,3]) # output: Scalene Triangle
# Test with strings
puts triangle_type(['4','1','2','5']) # output: Not a triangle
puts triangle_type(['3','3','3']) # output: Equilateral Triangle
puts triangle_type(['4','3','3']) # output: Isosceles Triangle
puts triangle_type(['4','2','3']) # output: Scalene Triangle
puts triangle_type(['a','a','a']) # output: Equilateral Triangle
puts triangle_type(['a','c','c']) # output: Isosceles Triangle
puts triangle_type(['a','b','c']) # output: Scalene Triangle
Skipping user inputs, since I can not reproduce the error (even if Unihedron found a fix) there is still a problem with the logic.
When the input is a = 1000, b = 1, c = 1, the result is "scalene triangle", but it should return "invalid". Below a fix I suggest.
Let's store the input in an array (already converted into integer or float):
sides = [a, b, c]
First you need to check that all sides are positive:
sides.all? { |x| x > 0 }
Then, check that the sum of two sides is greater than the other:
sides.combination(2).map{ |x| x.sum }.zip(sides.reverse).all? { |xy, z| xy > z }
Finally (I'm missing something?), to pick the triangle denomination you can use an hash accessing it by sides.uniq result:
triangle_kinds = {1 => 'equilateral', 2 => 'isosceles', 3 => 'scalene'}
triangle_kinds[sides.uniq.size]
Used the following methods over array (enumerable):
https://ruby-doc.org/core-2.5.1/Enumerable.html#method-i-all-3F
https://ruby-doc.org/core-2.5.1/Array.html#method-i-combination
https://ruby-doc.org/core-2.5.1/Array.html#method-i-map
https://ruby-doc.org/core-2.5.1/Array.html#method-i-zip
https://ruby-doc.org/core-2.5.1/Array.html#method-i-reverse
https://ruby-doc.org/core-2.5.1/Array.html#method-i-sum
https://ruby-doc.org/core-2.5.1/Array.html#method-i-uniq

Find the combinations of a given encoded string using Ruby

I was asked this question during an interview and I couldn't come up with a satisfactory solution for it. Would appreciate if anybody could give some pointers.
Given a mapping like
mapping = {"A" => 1, "B" => 2, "C" => 3..... "Z" => 26}
encode("A") == "1"
encode("BA") == "21"
encode("ABC") == "123"
encode("") == ""
decode("1") == ["A"] -> 1
decode("21") == ["BA", "V"] -> 2
decode("123") == ["ABC", "JC", "AX"] -> 3
decode("012") == [] -> 0
decode("") == [""] -> 1
decode("102") == ["JB"] -> 1
numDecode(X) == len(decode(X))
numDecode("1") == 1
numDecode("21") == 2
numDecode("123") == 3
numDecode("") == 1
numDecode("102") == 1
numDecode("012") == 0
We need a numDecode method which gives the length of unique solution array.
Updated :
Given a mapping like :
mapping = {"A" => 1, "B" => 2, "C" => 3..... "Z" => 26}
Suppose we are given a string as "A" the it can be encoded as : "1"
encode("A") should return "1"
encode("BA") should return "21" as if mapping is a hash then B has a value of 2, A has a value of 1.
encode("ABC") should return "123" as mapping["A" is 1, mapping["B"] is 2, and mapping["C"] is 3.
encode("") should return "" as it is not in mapping.
Now if decode("1") is called then it should return an array with one element i.e. ["A"] as key matching with 1 as value in mapping is "A".
decode("") should return an array with empty string i.e. [""].
decode("21") should return an array ["BA", "U"] as 2 is "B", 1 is "A" and "U" is 21 in mapping.
decode("012") should return an empty array as string starts with "0" which is not in mapping keys.
decode("102") should return an array as ["JB"] as "10" is J and "2" is B.
And finally numDecode should return the count of unique decoded strings in array. So,
numDecode(X) == len(decode(X))
numDecode("1") == 1
numDecode("21") == 2
numDecode("123") == 3
numDecode("") == 1
numDecode("102") == 1
numDecode("012") == 0
This is an interesting question, and the interview technique that goes with it is most likely to see how far the critical thinking goes. A good interviewer would probably not expect a single canonically correct answer.
If you get as far as a recursive decode solution that you then enumerate, then you are doing well IMO (at least I'd hire most candidates who could demonstrate clearly thinking through a piece of recursive code at interview!)
Having said that, one key hint is that the question asks for a num_decode function, not necessarily for implementations of encode and decode.
There is a deeper understanding and structure accessible here, that can be gained from analysing the permutations and combinations. It allows you to write a num_decode function that can handle long strings with millions of possible answers, without filling memory or taking hours to enumerate all possibilities.
First note that any set of separate ambiguous encoding multiply the number of possibilities for the whole string:
1920 -> 19 is ambiguous 'AI' or 'S' -> 'AIT' or 'ST'
192011 -> 11 is also ambiguous 'AA' or 'K' -> 'AITAA', 'AITK', 'STAA', 'STK'
Here 19 has two possible interpretations, and 11 also has two. A string with both of these separate instances of ambiguous codings has 2 * 2 == 4 valid combinations.
Each independent section of ambiguous coding multiplies the size of the whole set of decode values by the number of possibilities that it represents.
Next how to deal with longer ambiguous sections. What happens when you add an ambiguous digit to an ambiguous sequence:
11 -> 'AA' or 'K' -> 2
111 -> 'AAA', 'AK', 'KA' -> 3
1111 -> 'AAAA', 'AAK', 'AKA', 'KAA', 'KK' -> 5
11111 -> 'AAAAA', 'AAAK', 'AAKA', 'AKAA', 'AKK', 'KAAA', 'KAK', 'KKA' -> 8
2,3,5,8 should look familiar, it is the Fibonacci sequence, what's going on? The answer is that adding one digit to the sequence allows all the previous combinations plus those of the sub-sequence before that. By adding a digit 1 to the sequence 1111 you can either interpret it as 1111(1) or 111(11) - so you can add together the number of possibilities in 1111 and 111 to get the number of possibilities in 11111. That is, N(i) = N(i-1) + N(i-2) which is how to construct the Fibonacci series.
So, if we can detect ambiguous coding sequences, and get their length, we can now calculate the number of possible decodes, without actually doing the decode:
# A caching Fibonacci sequence generator
def fib n
#fibcache ||= []
return #fibcache[n] if #fibcache[n]
a = b = 1
n.times do |i|
a, b = b, a + b
#fibcache[i+1] = a
end
#fibcache[n]
end
def num_decode encoded
# Check that we don't have invalid sequences, raising here, but you
# could technically return 0 and be correct according to question
if encoded.match(/[^0-9]/) || encoded.match(/(?<![12])0/)
raise ArgumentError, "Not a valid encoded sequence"
end
# The look-ahead assertion ensures we don't match
# a '1' or '2' that is needed by a '10' or '20'
ambiguous = encoded.scan(/[12]*1[789]|[12]+[123456](?![0])/)
ambiguous.inject(1) { |n,s| n * fib(s.length) }
end
# A few examples:
num_decode('') # => 1
num_decode('1') # => 1
num_decode('12') # => 2
num_decode('120') # => 1
num_decode('12121212') # => 34
num_decode('1212121212121212121212121211212121212121') # => 165580141
It is relatively short strings like the last one which foil attempts to enumerate
the possibilities directly by decoding.
The regex in the scan took a little experimentation to get right. Adding 7,8 or 9 is ambiguous after a 1, but not after a 2. You also want to avoid counting a 1 or 2 directly before a 0 as part of an ambiguous sequence because 10 or 20 have no other interpretations. I think I made about a dozen attempts at the regex before settling on the current version (which I believe to be correct, but I did keep finding exceptions to correct values most times I tested the first versions).
Finally, as an exercise, it should be possible to use this code as the basis from which to write a decoder that directly output the Nth possible decoding (or even one that enumerated them lazily from any starting point, without requiring excessive memory or CPU time).
Here's a recursive solution:
$mapping = Hash[(0..25).map { |i| [('A'.ord+i).chr,i+1] }]
$itoa = Hash[$mapping.to_a.map { |pair| pair.reverse.map(&:to_s) }]
def decode( str )
return [''] if str.empty?
return $itoa.key?(str) ? [$itoa[str]] : nil if str.length == 1
retval = []
0.upto(str.length-1) do |i|
word = $itoa[str[0..i]] or next
tails = decode(str[i+1..-1]) or next
retval.concat tails.map { |tail| word + tail }
end
return retval
end
Some sample output:
p decode('1') # ["A"]
p decode('21') # ["BA", "U"]
p decode('123') # ["ABC", "AW", "LC"]
p decode('012') # []
p decode('') # [""]
p decode('102') # ["JB"]
p decode('12345') # ["ABCDE", "AWDE", "LCDE"]
Note differences between this output and the question. E.g. The 21st letter of the alphabet is "U", not "V". etc.
#he = Hash[("A".."Z").to_a.zip((1..26).to_a.map(&:to_s))]
# => {"A"=>"1", "B"=>"2",...,"Z"=>"26"}
#hd = #he.invert # => {"1"=>"A", "2"=>"B",.., "26"=>"Z"}
def decode(str, comb='', arr=[])
return arr << s if str.empty?
# Return if the first character of str is not a key of #hd
return arr unless (c = #hd[str[0]])
# Recurse with str less the first char, s with c appended and arr
arr = decode(str[1..-1], s+c, arr)
# If the first two chars of str are a key of #hd (with value c),
# recurse with str less the first two chars, s with c appended and arr
arr = decode(str[2..-1], s+c, arr) if str.size > 1 && (c = #hd[str[0..1]])
arr
end
def num_decode(str) decode(str).size end
decode('1') # => ["A"]
decode('') # => [""].
decode('21') # => ["BA", "U"]
decode('012') # => [""]
decode('102') # => ["JB"]
decode('123') # => ["ABC", "AW", "LC"]
decode('12345') # => ["ABCDE", "AWDE", "LCDE"]
decode('120345') # => ["ATCDE"]
decode('12720132') # => ["ABGTACB", "ABGTMB", "LGTACB", "LGTMB"]
Any more? Yes, I see a hand back there. The gentleman with the red hat wants to see '12121212':
decode('12121212')
# => ["ABABABAB", "ABABABL", "ABABAUB", "ABABLAB", "ABABLL", "ABAUBAB",
"ABAUBL", "ABAUUB", "ABLABAB", "ABLABL", "ABLAUB", "ABLLAB",
"ABLLL", "AUBABAB", "AUBABL", "AUBAUB", "AUBLAB", "AUBLL",
"AUUBAB", "AUUBL", "AUUUB", "LABABAB", "LABABL", "LABAUB",
"LABLAB", "LABLL", "LAUBAB", "LAUBL", "LAUUB", "LLABAB",
"LLABL", "LLAUB", "LLLAB", "LLLL"]
num_decode('1') # => 1
num_decode('21') # => 2
num_decode('12121212') # => 34
num_decode('12912912') # => 8
This looks like a combinatorics problem, but it's also a parsing problem.
(You asked for pointers, so I'm doing this in English rather than dusting off my Ruby.)
I would do something like this:
If X is an empty string, return 1
If X is not a string composed of digits starting with a nonzero digit, return 0
If X contains no 1's or 2's, return 1 (there's only one possible parsing)
If X contains 1's or 2's, it gets a bit more complicated:
Every 1 that is not the last character in X matches both "A" and the first digit of one of the letters "J" through "S".
Every 2 that is not the last character in X and is followed by a digit less than 7 matches both "B" and the first digit of one of the letters.
Count up your 1's and 2's that meet those criteria. Let that number be Y. You have 2^Y combinations of those, so the answer should be 2^Y but you have to subtract 1 for every time you have a 1 and 2 next to each other.
So, if you haven't returned by Step 4 above, count up your 1's that aren't the last character in X, and the 2's that both aren't the last character in X and aren't followed by a 7,8,9, or 10. Let the sum of those counts be called Y.
Now count every instance that those 1's and 2's are neighbors; let that sum be called Z.
The number of possible parsings is (2^Y) - Z.
In the spirit of giving “some pointers”, instead of writing an actually implementation for numDecode let me say that the most logically straightforward way to tackle this problem is with recursion. If the string passed to numDecode is longer than one character then look at the beginning of the string and based on what you see use one or two (or zero) recursive calls to find the correct value.
And the risk of revealing too much, numDecode("1122") should make recursive calls to numDecode("122") and numDecode("22").
# just look for all singles and double as you go down and keep repeating this.. if you get to the end where the string would be 1 or 2 digets long you count 1
# IE
# 121
# 1 that's good 2 that's good 1 that's good if all good then count + 1
# 12 that's good 1 that's good ... no more doubles if all good then count + 1
# 1 that's good 21 that's good if all good then count + 1
# test this on other cases
$str = "2022"
$strlength = $str.length
$count = 0
def decode(str)
if str[0].to_i >= 1 and str[0].to_i <= 9
$count += 1 if str.length == 1
decode(str[1..-1])
end
if str[0..1].to_i >= 10 and str[0..1].to_i <= 26
$count += 1 if str.length == 2
p str.length
decode(str[2..-1])
end
end
decode($str)
p " count is #{$count}"

Working with arbitrary inequalities and checking which, if any, are satisfied

Given a non-negative integer n and an arbitrary set of inequalities that are user-defined (in say an external text file), I want to determine whether n satisfies any inequality, and if so, which one(s).
Here is a points list.
n = 0: 1
n < 5: 5
n = 5: 10
If you draw a number n that's equal to 5, you get 10 points.
If n less than 5, you get 5 points.
If n is 0, you get 1 point.
The stuff left of the colon is the "condition", while the stuff on the right is the "value".
All entries will be of the form:
n1 op n2: val
In this system, equality takes precedence over inequality, so the order that they appear in will not matter in the end. The inputs are non-negative integers, though intermediary and results may not be non-negative. The results may not even be numbers (eg: could be strings). I have designed it so that will only accept the most basic inequalities, to make it easier for writing a parser (and to see whether this idea is feasible)
My program has two components:
a parser that will read structured input and build a data structure to store the conditions and their associated results.
a function that will take an argument (a non-negative integer) and return the result (or, as in the example, the number of points I receive)
If the list was hardcoded, that is an easy task: just use a case-when or if-else block and I'm done. But the problem isn't as easy as that.
Recall the list at the top. It can contain an arbitrary number of (in)equalities. Perhaps there's only 3 like above. Maybe there are none, or maybe there are 10, 20, 50, or even 1000000. Essentially, you can have m inequalities, for m >= 0
Given a number n and a data structure containing an arbitrary number of conditions and results, I want to be able to determine whether it satisfies any of the conditions and return the associated value. So as with the example above, if I pass in 5, the function will return 10.
They condition/value pairs are not unique in their raw form. You may have multiple instances of the same (in)equality but with different values. eg:
n = 0: 10
n = 0: 1000
n > 0: n
Notice the last entry: if n is greater than 0, then it is just whatever you got.
If multiple inequalities are satisfied (eg: n > 5, n > 6, n > 7), all of them should be returned. If that is not possible to do efficiently, I can return just the first one that satisfied it and ignore the rest. But I would like to be able to retrieve the entire list.
I've been thinking about this for a while and I'm thinking I should use two hash tables: the first one will store the equalities, while the second will store the inequalities.
Equality is easy enough to handle: Just grab the condition as a key and have a list of values. Then I can quickly check whether n is in the hash and grab the appropriate value.
However, for inequality, I am not sure how it will work. Does anyone have any ideas how I can solve this problem in as little computational steps as possible? It's clear that I can easily accomplish this in O(n) time: just run it through each (in)equality one by one. But what happens if this checking is done in real-time? (eg: updated constantly)
For example, it is pretty clear that if I have 100 inequalities and 99 of them check for values > 100 while the other one checks for value <= 100, I shouldn't have to bother checking those 99 inequalities when I pass in 47.
You may use any data structure to store the data. The parser itself is not included in the calculation because that will be pre-processed and only needs to be done once, but if it may be problematic if it takes too long to parse the data.
Since I am using Ruby, I likely have more flexible options when it comes to "messing around" with the data and how it will be interpreted.
class RuleSet
Rule = Struct.new(:op1,:op,:op2,:result) do
def <=>(r2)
# Op of "=" sorts before others
[op=="=" ? 0 : 1, op2.to_i] <=> [r2.op=="=" ? 0 : 1, r2.op2.to_i]
end
def matches(n)
#op2i ||= op2.to_i
case op
when "=" then n == #op2i
when "<" then n < #op2i
when ">" then n > #op2i
end
end
end
def initialize(text)
#rules = text.each_line.map do |line|
Rule.new *line.split(/[\s:]+/)
end.sort
end
def value_for( n )
if rule = #rules.find{ |r| r.matches(n) }
rule.result=="n" ? n : rule.result.to_i
end
end
end
set = RuleSet.new( DATA.read )
-1.upto(8) do |n|
puts "%2i => %s" % [ n, set.value_for(n).inspect ]
end
#=> -1 => 5
#=> 0 => 1
#=> 1 => 5
#=> 2 => 5
#=> 3 => 5
#=> 4 => 5
#=> 5 => 10
#=> 6 => nil
#=> 7 => 7
#=> 8 => nil
__END__
n = 0: 1
n < 5: 5
n = 5: 10
n = 7: n
I would parse the input lines and separate them into predicate/result pairs and build a hash of callable procedures (using eval - oh noes!). The "check" function can iterate through each predicate and return the associated result when one is true:
class PointChecker
def initialize(input)
#predicates = Hash[input.split(/\r?\n/).map do |line|
parts = line.split(/\s*:\s*/)
[Proc.new {|n| eval(parts[0].sub(/=/,'=='))}, parts[1].to_i]
end]
end
def check(n)
#predicates.map { |p,r| [p.call(n) ? r : nil] }.compact
end
end
Here is sample usage:
p = PointChecker.new <<__HERE__
n = 0: 1
n = 1: 2
n < 5: 5
n = 5: 10
__HERE__
p.check(0) # => [1, 5]
p.check(1) # => [2, 5]
p.check(2) # => [5]
p.check(5) # => [10]
p.check(6) # => []
Of course, there are many issues with this implementation. I'm just offering a proof-of-concept. Depending on the scope of your application you might want to build a proper parser and runtime (instead of using eval), handle input more generally/gracefully, etc.
I'm not spending a lot of time on your problem, but here's my quick thought:
Since the points list is always in the format n1 op n2: val, I'd just model the points as an array of hashes.
So first step is to parse the input point list into the data structure, an array of hashes.
Each hash would have values n1, op, n2, value
Then, for each data input you run through all of the hashes (all of the points) and handle each (determining if it matches to the input data or not).
Some tricks of the trade
Spend time in your parser handling bad input. Eg
n < = 1000 # no colon
n < : 1000 # missing n2
x < 2 : 10 # n1, n2 and val are either number or "n"
n # too short, missing :, n2, val
n < 1 : 10x # val is not a number and is not "n"
etc
Also politely handle non-numeric input data
Added
Re: n1 doesn't matter. Be careful, this could be a trick. Why wouldn't
5 < n : 30
be a valid points list item?
Re: multiple arrays of hashes, one array per operator, one hash per point list item -- sure that's fine. Since each op is handled in a specific way, handling the operators one by one is fine. But....ordering then becomes an issue:
Since you want multiple results returned from multiple matching point list items, you need to maintain the overall order of them. Thus I think one array of all the point lists would be the easiest way to do this.

(Ruby) how to return a -1 specifically for a specific comparison

let's say when I'm comparing values in ruby, i have a value in mind that no matter what I want, using sort on that value and anything else returns a -1 (so this value is default sorted as smaller than everything).
for example, let's say i want '100' to sort smaller 100% of the time against 99. so that if i'm sorting values in an array, and a comparison comes up between 100 and 99, 100 is sorted smaller (ie, -1 is returned). but, i want all the other cases to be normal (98 is smaller than 99, 50 is bigger than 30, etc)
edit: okay this is what i want
if i have an x and a y, i do not want to use
x <=> y
i want to use (in pseudocode and hand-wavy-ness)
x > y
which means, this x is always greater than this y
Why don't you instead use a dictionary to keep values associated with their relative value? In this case, the string abc can be mapped to -1, and then just make sure no other values map to values equal to or less than -1.
Edit: If you're only concerned with one particular value breaking the norm, then this solution is not for you.
Easier to handle the specialness outside of the sort!
module Enumerable
def sort_excluding(*vals)
special,rest = partition {|x| vals.include?(x)}
rest.sort + special
end
end
One way to do it would be to implement a derivative class for your custom comparisons (http://www.ruby-doc.org/core/classes/Comparable.html)
Here's some sample code (and tests) for you:
class StrA < String
include Comparable
attr :str
def <=>(anOther)
if (str == "abc" && anOther.str == "abc")
0
elsif (str == "abc")
-1
elsif (anOther.str == "abc")
1
else
str <=> anOther.str
end
end
def initialize(str)
#str = str
end
def inspect
#str
end
end
And the tests:
a = StrA.new("Z")
b = StrA.new("B")
c = StrA.new("abc")
d = StrA.new("")
a > b # 1
a > c # 1
c > a # -1
d > c # 1
c > d # -1
c < d # 1
c > d # -1
[a, b, c, d].sort! # [ "Z", "B", "", "abc"]
I think what you want is:
[30, 50, 4, 0, 100, -22, 99].sort_by {|a| [a == 100 ? -1 : 0, a ]}.reverse
which gives:
99
50
30
4
0
-22
100
Hope I understood the question!
Array#sort or Enumerable#sort(I don't know what you are trying to sort) can take an obtional block. If the block is given, the block is used for comparison instead of <=>
For example this code will sort reversed:
foo.sort { |a,b| b <=> a }
In your case you need to call #sort something like the following:
foo.sort do |a,b|
if [a,b].sort == [99,100]
b-a # returns 1 or -1 so that 99 > 100
else
a <=> b
end
end
I am not entirely sure about the way you are trying to sort, but this should enable you to use sort in the manner you need. More inforamtion about Array#sort, and any other method can be found on your linux(and possible other OS's) via ri like this: ri Array#sort.
You could override the sort method for the object like so:
class Fixnum
alias old_sort <=>
def <=>(object)
if (self == 100 or object == 100)
return -1
else
return self.old_sort object
end
end
end
puts (101 <=> 100)
Edit 1: Above is a fully working example.
Edit 2: As stated by johannes in the comments, you really shouldn't implement this exact code. This is merely a simple example of how to override your sorting method to do domain-specific logic. Also, updated the code to reflect johannes' other comment about comparing with both object and self.

Resources