Why do we need spaceship operator? [duplicate] - ruby

This question already has answers here:
What is the Ruby <=> (spaceship) operator?
(6 answers)
Closed 7 years ago.
If we have <, >, and ==, the total order is determined by those. Why do we need <=>?

we Don't need <=>.
a<=>b
is equivalent to:
if a<b
return -1
elsif a>b
return 1
else
return 0
end
It is there for convenience and it was taken from perl.

<=> is the basis of Comparable, so you don't have to implement all of the compare functions yourself. It's easier and less error-prone to just implement one function instead of three.

The "spaceship" operator is for comparison, not equality. It's similar in concept to C's strcmp function.
From the String class:
string <=> other_string → -1, 0, +1 or nil
Comparison — Returns -1, 0, +1 or nil depending on whether string is less than, equal to, or greater than other_string.
In short, == returns a boolean expressing equality, while <=> returns a number expressing comparative value. If the first object is of greater value than the second, <=> returns +1. If it's of lesser value, -1 is returned. If the two have the same value, 0 is returned.
The "value" of an object can be defined to be just about anything. For String, however, <=> checks the lexicographic ordering of the two arguments.
Therefore:
"abc" == "abc" # true
("abc" <=> "abc") == 0 # true

You are correct that there is redundancy between <, ==, > and <=>. In fact, when <, ==, > are defined, <=> is automatically defined.

This operator is sometimes called a "signum" function. It provides the most concise way to customize sort order. For example:
require "ostruct"
# Fake "rows" with OpenStructs
my_data = [
OpenStruct.new({ :name => "Ben", :age => 50 }),
OpenStruct.new({ :name => "Abe", :age => 50 }),
OpenStruct.new({ :name => "Cab", :age => 51 })
]
# Sort by age descending, then name ascending
puts my_data.sort { |a, b| 2 * (b.age <=> a.age) + (a.name <=> b.name) }
This works because the value from <=> is always -1, 0, or 1. I don't know of a more efficient way to do general-purpose sorting.

Related

Ruby - How to write a method that returns an array of strings?

I've tried different ways and this is probably the closest that I got to it. I am trying to write a method that takes in an array of strings and returns it containing the strings that are at least 5 characters long and end with "y".
I'm a beginner and this is my second problem I've come across with, and I've tried multiple if statements and using a while loop, however I could not get to it and now this is where I am at. Thank you!
def phrases(arr1, arr2)
arr1 = ["funny", "tidy", "fish", "foogiliously"]
arr2 = ["happily", "lovely", "hello", "multivitaminly"]
if (arr1.length > 5 && arr1.length == "y")
return arr1
elsif (arr2.length > 5 && arr2.length == "y")
return arr2
end
end
puts phrases(["funny", "tidy", "fish", "foogiliously"])
puts phrases(["happily", "lovely", "hello", "multivitaminly"])
If I'm understanding your question correctly, you want to return a subset of the passed in array matching your conditions (length ≥ 5 and last character = 'y'). In that case:
def phrases(words)
words.grep(/.{4}y\z/)
end
What that regex does:
.{4} means 4 of any character
y is the letter y
\z is the end of the string, so we don't match in the middle of a long word
The docs for Enumerable#select are here (an Array is an Enumerable).
Output:
> phrases(["funny", "tidy", "fish", "foogiliously"])
=> ["funny", "foogiliously"]
> phrases(["happily", "lovely", "hello", "multivitaminly"])
=> ["happily", "lovely", "multivitaminly"]
If you only want word characters, rather than any character, you'd use this regex instead: /\A.{4,}y\z/. In that case, \A means the start of the string, and \w{4,} means at least 4 word characters.
If, when given an array and inclusion criterion, one wishes to construct an array that contains those elements of the first array that satisfy the inclusion criterion, one generally uses the method Array#select or Array#reject, whichever is more more convenient.
Suppose arr is a variable that holds the given array and include_element? is a method that takes one argument, an element of arr, and returns true or false, depending on whether the inclusion criterion is satisified for that element. For example, say the array comprises the integers 1 through 6 and the inclusion criterion is that the number is even (2, 4 and 6). We could write:
arr = [1,2,3,4,5,6]
def include_element?(e)
e.even?
end
include_element?(2)
#=> true
include_element?(3)
#=> false
arr.select { |e| include_element?(e) }
#=> [2, 4, 6]
The method include_element? is so short we probably would substitute it out and just write:
arr.select { |e| e.even? }
Array#select passes each element of its receiver, arr, to select's block, assigns the block variable e to that value and evaluates the expression in the block (which could be many lines, of course). Here that expresssion is just e.even?, which returns true or false. (See Integer#even? and Integer#odd?.)
If that expression evaluates as a truthy value, the element e is to be included in the array that is returned; if it evaluates as a falsy value, e is not to be included. Falsy values (logical false) are nil and false; truthy values (logical true) are all other Ruby objects, which of course includes true.
Notice that we could instead write:
arr.reject { |e| e.odd? }
Sometimes the inclusion criterion consists of a compound expression. For example, suppose the inclusion criterion were to keep elements of arr that are both even numbers and are at least 4. We would write:
arr.select { |e| e.even? && e >= 4 }
#=> [4, 6]
With other criteria we might write:
arr.select { |e| e.even? || e >= 4 }
#=> [2, 4, 5, 6]
or
arr.select { |e| e < 2 || (e > 3 && e < 6) }
#=> [1, 4, 5]
&& (logical 'and') and || (logical 'or') are operators (search "operator expressions"). As explained at the link, most Ruby operators are actually methods, but these two are among a few that are not.
Your problem now reduces to the following:
arr.select { |str| <length of str is at least 5> && <last character of str is 'y'> }
You should be able to supply code for the <...> bits.
You are trying to write a function that should work on a single array at a time I think. Also, you are taking in an array, and retaining only those elements that satisfy your conditions: at least 5 characters long, and ends with y. This is a filtering operation. Read about the methods available for ruby's Array class here
def phrases(array)
...
filtered_array
end
Now the condition you are using is this arr1.length > 5 && arr1.length == "y".
The first half should check if the string length is greater than 5, not the array length itself. The second half is an indexing operation, and your code for that is incorrect. basically you are checking if the last character in the string is y.
Usually strings are indexed in this manner: string[index]. In your case you can use string[-1]=='y' or string[string.length - 1]=='y'. This because arrays and strings are zero indexed in ruby. The first element has index of 0, the second has an index of 1, and the last one, therefore, will have an index of length-1. If you use negative indexes then the array is indexed from the end, so string[-1] is a quick way to get to the last element.
Considering this, the function will take the following structure:
def phrases(array)
filtered_array = [] # an empty array
loop through the input array
for each element check for the condition element.length > 5 && element[-1]=='y'
if true: push the element into the filtered_array
once the loop is done, return the filtered array
end
Read about ruby arrays, the methods push, filter and select in the above linked documentation to get a better idea. I'd also recommend the codeacademy ruby tutorial.
Edit: Both halves of the condition are incorrect. I had overlooked a mistake in my earlier answer. arr1.length refers to the length of the array. You want to check the length of each string in the array. So in your for loop you should check the length of the loop variable, if that is greater than 5.
You may want to spend some time reading about the methods in the core library, especially String#end_with? and Enumerable#select. You could then write a method that'd contain something like this:
['abc', 'qwerty', 'asdfghjk', 'y'].select{|s| s.length >= 5}.select{|s| s.end_with? 'y'}
#=> ["qwerty"]

What's the proper way to use the <=> operator in Ruby? [duplicate]

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.

Operator <=> in Ruby [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is the Ruby <=> (spaceship) operator?
I saw a code and an operator I'm unfamiliar with
#array << {:suffix=> substring, :index => i}
#array.sort! { |x,y| x[:suffix] <=> y[:suffix]}
I can't do google on it. What does <=> do?
This is the spaceship operator, it was borrowed from Perl. It is commonly used for sorting, because it returns -1 if left operand is less than right operand, 1 if right operand is greater than the left and returns 0 otherwise.
1 <=> 2 # => -1
2 <=> 1 # => 1
1 <=> 1 # => 0
It does comparison defined for the particular class. If it is the case that ... < ... is true, it returns -1, if ... == ... is true, then 0, and if ... > ... is true, then 1.
It's called the spaceship operator.
For the core numeric and string classes, it's a comparison operator that returns -1, 0, or 1.
In theory, a class can define any operator to do anything it wants, but this will be the method that is used when sorting. It may make sense to define <=> for an arbitrary application class if that class will ever need to be ordered.

Ruby Equal to vs Comparison;

I need help for understanding - What is the difference between Equal to and Comparison?
Here is the case
x == y means `Equal to`
x = 10 and y = 10
puts "X and Y are equal" if x == y
puts "X and Y are equal" if x <=> y
I know when and where can I use equal to, but when and where can I use Comparison <=>
Thanks
When you want to compare? It returns -1, 0, 1. For example, sorting users by first name:
users_by_first = users.sort { |u1, u2| u1.fname <=> u2.fname }
Here's another SO question asking about the spaceship operator.
<=> General comparison operator. Returns -1, 0, or +1, depending on whether its receiver is less than, equal to, or greater than its argument.
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html
This is useful for sorting, for one thing.
Equality operators: == and !=
The == operator, also known as equality or double equal, will return true if both objects are equal and false if they are not.
"koan" == "koan" # Output: => true
The != operator, AKA inequality or bang-tilde, is the opposite of ==. It will return true if both objects are not equal and false if they are equal.
"koan" != "discursive thought" # Output: => true
Note that two arrays with the same elements in a different order are not equal, uppercase and lowercase versions of the same letter are not equal and so on.
When comparing numbers of different types (e.g., integer and float), if their numeric value is the same, == will return true.
2 == 2.0 # Output: => true
Comparison operators
Objects such as numbers and strings, which can be compared (amongst themselves) in terms of being greater or smaller than others, provide the <=> method, also known as the spaceship method. When comparing two objects, <=> returns -1 if the first object is lesser than the second (a < b), 0 in case they are equal (a == b) and 1 when the first object is greater than the second (a > b).
5 <=> 8 # Output: => -1
5 <=> 5 # Output: => 0
8 <=> 5 # Output: => 1
Most comparable or sortable object classes, such as Integer, Float, Time and String, include a mixin called Comparable, which provides the following comparison operators: < (less than), <= (less than or equal), == (equal), > (greater than), >= (greater than or equal). These methods use the spaceship operator under the hood.
Comparison operators can be used in objects of all the above classes, as in the following examples.
# String
"a" < "b" # Output: => true
"a" > "b" # Output: => false
# Symbol
:a < :b # Output: => true
:a > :b # Output: => false
# Fixnum (subclass of Integer)
1 < 2 # Output: => true
2 >= 2 # Output: => true
# Float
1.0 < 2.0 # Output: => true
2.0 >= 2.0 # Output: => true
# Time
Time.local(2016, 5, 28) < Time.local(2016, 5, 29) # Output: => true
When comparing numbers of different classes, comparison operators will implicitly perform simple type conversions.
# Fixnum vs. Float
2 < 3.0 # Output: => true
2.0 > 3 # Output: => false
More info about Ruby operators is available at this blog post.

(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