Related
This question already has answers here:
What is the Ruby <=> (spaceship) operator?
(6 answers)
Closed 4 years ago.
I don't quite understand how this works. I guess a large part of it is because I'm used to C and its low-level data structures, and programming at a higher level of abstraction takes some getting used to. Anyway, I was reading The Ruby Programming Language, and I came to the section about ranges and how you can use the <=> operator as sort of a shorthand for what in C you would have to implement as a sequence of if-else statements. It returns either -1, 0, or 1 depending on the results of the comparison. I decided to try it out with the following statement:
range = 1..100
r = (100 <=> range)
print( r )
The result is an empty string. So my question is, how does this operator work; what data type does it return (I assume the return value is an integer type but I can't say for sure); and finally what is the proper way to use it? Thanks for your time, and sorry if this was already answered (I didn't see it in the listing).
The <=> operator is meant to compare two values that can be compared directly, as in strings to strings or numbers to numbers. It can't magically compare two different things, Ruby doesn't convert for you like that.
As such you need to use it in the right context:
1 <=> 2
# => -1
2 <=> 1
# => 1
1 <=> 1
# => 0
When the two things can't be compared you get nil. Note that this is different from "empty string":
1 <=> '1'
# => nil
That means "invalid comparison". The <=> operator is being nice here because in other situations you get an exception:
1 < '1'
# => ArgumentError: comparison of Integer with String failed
You can also use this operator to make your own Comparable compatible class:
class Ordered
include Comparable
attr_reader :sequence
def initialize(sequence)
#sequence = sequence
end
def <=>(other)
self.sequence <=> other.sequence
end
end
Then you can do this:
a = Ordered.new(10)
b = Ordered.new(2)
[ a, b ].sort
# => [#<Ordered:0x00007ff1c6822b60 #sequence=2>, #<Ordered:0x00007ff1c6822b88 #sequence=10>]
Where those come out in order. The <=> implementation handles how these are sorted, and you can finesse that depending on how complex your sorting rules are.
Using the return values -1, 0, and 1 only as labels describing different states, you can write a condition that depends on the order between two numbers:
case a <=> b
when -1 then # a is smaller than b. Do something accordingly
when 0 then # a equals b. Do something accordingly
when 1 then # a is larger than b. Do something accordingly
end
Or, a use case where you can make use of the values -1, 0, and 1, is when you want to get the (non-negative) difference between two numbers a and b without using the abs method. The following:
(a - b) * (a <=> b)
will give the difference.
Add to the other answers this snippet: The "spaceship operator" returns -1, 0, or 1 so you can use it when comparing items in a .sort call:
events.sort {|x, y| y.event_datetime <=> x.event_datetime}
0 means the two items are the same, 1 means they are different but in the correct sort order, and -1 means they are out of order. The above example reverses x and y to sort into descending order.
In C, the function strcmp() has roughly the same behavior, to fit with qsort(), with the same semantics.
Using pseudo-code, if I have an array of integer, how can I make a single big integer that represents the same array in bits?
Example of input (using bits):
[10101, 10001, 00010, 01100]
The integer should be:
10101100010001001100
or
01100000101000110101
number = 0
for each element e
number *= 1 + maximumRepresentableNumber
number += e
For your example, maximumRepresentableNumber will be 11111, as that is the maximum number we can represent using the allowed number of bits (5). Adding 1 to that gives us 100000, and, if we multiply by that, it will be equivalent to a bit-shift by 5 to the left.
This would work for decimal representation as well, i.e. [123, 55, 29] will return 123055029. In this case maximumRepresentableNumber will be 999, so we'd just be multiplying by 1000.
What you are looking for is well known in functional programming land as fold or reduce. The basic idea is, that in a list
a,b,c,d, ..., x
we replace the commas with an operation we want (the operation beig symbolaized by $ here):
a $ (b $ (c $ (d $ ...(x $ Z))) // right fold
and the empty list with some default value Z
A bit different is the left fold, where we start out with Z:
((((Z $ a) $ b) $ c ).... $ x)))
The genral imperative algorithm for left fold would be:
result = Z
for each e in list do result = result $ e
Now, the only problem left is to identify $ and Z, that is the function we want to apply subsequently to all list elements to reach the goal and the starting value. In your case, what you want is either:
append the stringified element to the result string. Z is the empty string.
or: add the element to the result so far multiplied with 2^5. Z would be 0.
In ruby:
answer = 0
[0b10101, 0b10001, 0b00010, 0b01100].each do |x|
answer <<= 5
answer |= x
end
puts answer.to_s(2) # 10101100010001001100
In Python this would be:
a = [0b10101, 0b10001, 0b00010, 0b01100]
b = 0
for elem in a:
b <<= elem.bit_length()
b += elem
print(bin(b))
I tried to see how Array#[]= works, and played around:
enum[int] = obj → obj
enum[start, length] = obj → obj
enum[range] = obj → obj
Question 1
I have one array b holding nil at its 0 index.
b = []
b[0] # => nil
I tried to replace nil with integer 10 in the code below.
b[-1] = 10 # => IndexError: index -1 too small for array; minimum: 0
Why doesn't the code above work, but the ones below do? In case of an array with size 1, why are the indices 0 and -1 treated differently?
b[0] = 5 # => 5
b[-1] = 10 # => 10
Question 2
I created an array of size 2, and did the following:
a = [1,2]
a[-3] = 3 # => IndexError: index -3 too small for array; minimum: -2
a[-3] = [3] # => IndexError: index -3 too small for array; minimum: -2
a[-3..-4] = [3] # => RangeError: -3..-4 out of range
I believe that negative index never increases the size of an array, but I don't know why. Why did the code below succeed?
a[-2..-3] = [3,4] #=> [3, 4]
I would suggest you to take a look at the first para in Array documentation. It surprisingly says: “A negative index is assumed to be relative to the end of the array—that is, an index of -1 indicates the last element of the array, -2 is the next to last element in the array, and so on.”
That means, that you may set a[-N]th element if and only |N| <= a.size. That’s why a = [1,2] ; a[-3] = 3 fails (3 > 2).
On the other hand, there is [likely] not documented feature for ruby arrays: a[INBOUNDS_IDX..NONSENSE_IDX]=SMTH will insert SMTH before INBOUNDS_IDX index:
a=[1,2]
a[2..0]='a'
a[2..1]='b'
a[2..-100]='c'
# ⇒ [1, 2, "c", "b", "a"]
a[2..-1]='q'
# ⇒ [1, 2, "q"]
Nonsense here means “less than INBOUNDS_IDX, and not treatable as index in an negative notation” (that’s why a[2..-1] in the example above is treated as a[2..(a.size - 1)].)
Q1:
An empty array has 0 elements, so when you try to set its element 0, with negative index -1, it will give an error.
Because negative index cycles through the array from the end.
So
a = []; a[-1] = 3 makes it impossible to
a) get the element at last position, since its null
b) set its value. since it was never captured.
a[0] = 5 will work because you are telling the compiler to
a) grab the first element,
b) create one if not present, and then assign that to the value you requested.
See official api doc specifically mentioning positive index can grow size, negative index past the beginning of the array raises an error.
Q2:
The above explanation almost answers the second question as well.
Given a = [1,2]
a[-3] = 3 causes the first point of break. You are trying to access the 3rd element from the end, which does not exist. By design it breaks down.
While a[-2..-3] is within the capture range of the defined array.
You ask the interpreter to capture the second element from the last (1st from the front in this case), and try to invoke a range which is asking it to increase the array's size, and populate it with whatever you requested.
Happily, all is still well and as desired. Good to know.
Observation #1
The -1 index is related to the last element, if the array has no size, [], you can't use it until you initialize it with one or more elements.
Observation #2:
Yes, you are right, the negative index never increases size of the array, it only references a concrete existing position in the array.
Don't think the array is circular—the 0 index clued to the N-1 index—so you can't use any negative index thinking that it's valid.
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
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.