What does the Ruby sort function do, exactly? - ruby

Let me preface this by saying I'm a newbie to Ruby (pretty obvious). I'm learning Ruby on Codecademy and I'm confused by the sort function. To use as an example:
list = [3,2,1,4]
list.sort { |a,b| b <=> a }
I know that this will return the array in descending order - [4, 3, 2, 1]. What I don't understand is why, exactly. I know that when the sort function is called, the numbers from the array are passed into the function and compared, which then returns either -1, 0, or 1 - but then what? For instance, I'm guessing this is what would be compared first:
[3 <=> 2] = 1
But what does it do with the 1 that is returned? And what would the array look like after it gets the 1?
I'm confused because I don't understand how reversing the comparison (a <=> b vs. b <=> a) changes the direction in which the array is sorted. Unless I'm mistaken, doesn't "1 <=> 2" essentially return "1 comes before 2", whereas "2 <=> 1" returns "2 comes after 1"? Which is more or less the same thing, yet the results are obviously different.

The "spaceship" operator, <=> doesn't return something so English as "a comes before b". It returns what sort needs to know: where two elements are in relation to each other. Specifically, it returns the -1, 0, or 1 value you mentioned.
In a <=> b, if a is less than b (via whatever comparison method is used for the class of which a is an instance), the return is -1. If they're equal, return is 0; and if a is greater than b, the return is 1.
When you do b <=> a, the returned value is based on b rather than a, so if a is smaller, you'll get 1, whereas you got -1 when doing a <=> b.
So while the English meaning is the same, the devil is in the details: that -1, 0, or 1 return value. That value tells Ruby precisely how two elements fit into a sorted array.
The magic with those three numbers is in the quicksort algorithm used by Ruby. It's out of scope to try and explain precisely how that algorithm works, but you can basically look at it as a simple comparison on many values. For each item in an array, <=> is called with another item in the array in order to determine where the two items fall relative to each other. Once enough comparisons have been made, the positions of all those individual items is known and the sorting is done.
As a simple (and not really technically accurate, but close enough) example, consider the array [3, 2, 7, 1]. You can grab a value to compare others to in order to start the sorting. We'll pick 3. Running a comparison of 3 with all other numbers gives us:
3 <=> 2 == 1: 3 is greater than 2, so 2 must be to the left of 3. Our array might look like this now: [2, 3, 7, 1]
3 <=> 7 == -1: 3 is less than 7, so 7 must be the the right of 3. Our array continues to look as it did before, as the 7 was already on the right.
3 <=> 1 == 1: 3 is greater than 1, so the 1 must be on the left of 3. Our array looks like this now: [2, 1, 3, 7]
We know the 7 must be correct since it's the only element on the "greater than 3" side. So we just need to figure out the sort order for everything before the 3: 1 and 2. Running a similar comparison as above, we obviously swap the 1 and 2 to get [1, 2, 3, 7].
I hope this helps!

The comparison gets two arguments and returns -1 if the first argument is less than the second argument, 0 if the two arguments are equal, and 1 if the second argument is greater than the first argument. When you swap the two, it inverts the result. <=> doesn’t care about where its operands came from, so although the change doesn’t add any extra information about the relationship between a and b, it does invert the result of <=>, and that inverts the sorting order.
(1 <=> 2) == -1
(2 <=> 1) == 1
As the sorting function, you don’t get 1 <=> 2 or 2 <=> 1; you get -1 or 1. From whichever number, you decide which argument you passed to the comparison should come later in the result.

Unless I'm mistaken, doesn't "1 <=> 2" essentially return "1 comes before 2", whereas "2 <=> 1" returns "2 comes after 1"? Which is more or less the same thing, yet the results are obviously different.
No and yes. The question that is asked of the block is: "does the left element come before or after the right?" And by swapping left and right, you swap the order.
So, the answer is: you aren't reversing the comparison per se, but you are reversing the sort method's idea of which is left and which is right.
The return value of the block is interpreted by sort like this:
0: order doesn't matter
1: the elements are already in the right order
-1: the elements are in the wrong order
By swapping left and right, you swap whether the block tells sort that the elements are in the right or wrong order.
Note that Quicksort is completely irrelevant here. What matters is the contract of the comparator block. Whether that block is then used by Quicksort, Shellsort, Insertion Sort, Bubblesort, Bogosort, Timsort or whatever other comparison-based sort doesn't really matter.

Related

How do I solve this question about Pigeonhole Principle (Discrete Mathematics)?

I am not understanding the following question. I mean I want to know the sample input output for this problem question: "The pigeonhole principle states that if a function f has n distinct inputs but less than n distinct outputs,then there exist two inputs a and b such that a!=b and f(a)=f(b). Present an algorithm to find a and b such that f(a)=f(b). Assume that the function inputs are 1,2,......,and n.?"
I am unable to solve this problem as I am not understanding the question clearly. looking for your help.
The pigeonhole principle says that if you have more items than boxes, at least one of the boxes must have multiple items in it.
If you want to find which items a != b have the property f(a) == f(b), a straightforward approach is to use a hashmap data structure. Use the function value f(x) as key to store the item value x. Iterate through the items, x=1,...,n. If there is no entry at f(x), store x. If there is, the current value of x and the value stored at f(x) are a pair of the type you're seeking.
In pseudocode:
h = {} # initialize an empty hashmap
for x in 1,...,n
if h[f(x)] is empty
h[f(x)] <- x # store x in the hashmap indexed by f(x)
else
(x, h[f(x)]) qualify as a match # do what you want with them
If you want to identify all pigeons who have roommates, initialize the hashmap with empty sets. Then iterate through the values and append the current value x to the set indexed by f(x). Finally, iterate through the hashmap and pick out all sets with more than one element.
Since you didn't specify a language, for the fun of it I decided to implement the latter algorithm in Ruby:
N = 10 # number of pigeons
# Create an array of value/function pairs.
# Using N-1 for range of rand guarantees at least one duplicate random
# number, and with the nature of randomness, quite likely more.
value_and_f = Array.new(N) { |index| [index, rand(N-1)]}
h = {} # new hash
puts "Value/function pairs..."
p value_and_f # print the value/function pairs
value_and_f.each do |x, key|
h[key] = [] unless h[key] # create an array if none exists for this key
h[key] << x # append the x to the array associated with this key
end
puts "\nConfirm which values share function mapping"
h.keys.each { |key| p h[key] if h[key].length > 1 }
Which produces the following output, for example:
Value/function pairs...
[[0, 0], [1, 3], [2, 1], [3, 6], [4, 7], [5, 4], [6, 0], [7, 1], [8, 0], [9, 3]]
Confirm which values share function mapping
[0, 6, 8]
[1, 9]
[2, 7]
Since this implementation uses randomness, it will produce different results each time you run it.
Well let's go step by step.
I have 2 boxes. My father gave me 3 chocolates....
And I want to put those chocolates in 2 boxes. For our benefit let's name the chocolate a,b,c.
So how many ways we can put them?
[ab][c]
[abc][]
[a][bc]
And you see something strange? There is atleast one box with more than 1 chocolate.
So what do you think?
You can try this with any number of boxes and chocolates ( more than number of boxes) and try this. You will see that it's right.
Well let's make it more easy:
I have 5 friends 3 rooms. We are having a party. And now let's see what happens. (All my friends will sit in any of the room)
I am claiming that there will be atleast one room where there will be more than 1 friend.
My friends are quite mischievious and knowing this they tried to prove me wrong.
Friend-1 selects room-1.
Friend-2 thinks why room-1? Then I will be correct so he selects room-2
Friend-3 also thinks same...he avoids 1 and 2 room and get into room-3
Friend-4 now comes and he understands that there is no other empty room and so he has to enter some room. And thus I become correct.
So you understand the situation?
There n friends (funtions) but unfortunately or (fortunately) their rooms (output values) are less than n. So ofcourse one of the there exists 2 friend of mine a and b who shares the same room.( same value f(a)=f(b))
Continuing what https://stackoverflow.com/a/42254627/7256243 said.
Lets say that you map an array A of length N to an array B with length N-1.
Than the result could be an array B; were for 1 index you would have 2 elements.
A = {1,2,3,4,5,6}
map A -> B
Were a possible solution could be.
B= {1,2,{3,4},5,6}
The mapping of A -> could be done in any number of ways.
Here in this example both input index of 3 and 4 in Array A have the same index in array B.
I hope this usefull.

Can someone explain how recursive insertion sort works?

Assuming A is an array, and n is the number of elements in A,
recursive_insertion_sort(A, n)
IF n > 1 THEN
recursive_insertion_sort(A, n-1)
key = A[n]
i = n - 1
DOWHILE A[i] > key AND i > 0
A[i+1] = A[i]
i = i - 1
ENDDO
A[i+1] = temp
ENDIF
END
Can someone explain how recursion works in this case? There are a few things I don't understand:
I don't understand why we have to call the function again if n > 1.
Why do we input (n-1) when we call the function again? Is it so that we start the entire process from n = 2, the first 2 elements?
How does recursion in general work? Like, once we call the function again, do we ignore the code from line 4 onwards, and jump straight into the second call? Or do we run the 2nd call in conjunction with the first call?
Before discussing the implementation, let's explain what this function does: it does not sort the entire array A, but only its initial n elements. You can pass the length of the array for n to sort the whole thing, but the fact that you pass the length separately is essential to understanding the rest of the answer.
I don't understand why we have to call the function again if n > 1.
Perhaps a better way to explain the meaning of this condition would be that we do not call this function again when n is one or less. This is called the base case of recursive algorithm, i.e. the case when you don't have to do anything. In case of sorting it means that an array of only one element is already sorted.
Why do we input (n-1) when we call the function again?
Since n is the number of elements that we need to sort, We pass n-1 to sort the front of the array. Once the function returns, we know that the portion A[1..n-1] is already sorted. All we need to do is to move A[n] to its right place. We do that in the DOWHILE loop that follows: we go backward one element at a time, moving elements that are bigger than A[n] to the right. Once the loop is over, we place A[n] to its new place. Now the range A[1..n] is sorted.
How does recursion in general work?
The function has two cases - the trivial base case, when everything is done, and a reduction step, when you use recursive invocation to solve a simpler problem, and then use the results of the simpler solution to construct your final solution.
once we call the function again, do we ignore the code from line 4 onwards, and jump straight into the second call?
No, once the function returns, we continue where we left. In your case, the function waits for the A[1..n-1] range to be sorted before placing A[n] to the right place.
Small example to understand how this works :
recursive_insertion_sort([1, 7, 5, 2], 4)
| recursive_insertion_sort([1, 7, 5, 2], 3)
| | recursive_insertion_sort([1, 7, 5, 2], 2)
| | | recursive_insertion_sort([1, 7, 5, 2], 1)
| | puts 7 in the right position between it's ORDERED left values [1] -> [1,7]
| puts 5 in the right position between it's ORDERED left values [1,7] -> [1,5,7]
puts 2 in the right position between it's ORDERED left values [1,5,7] -> [1,2,5,7]

Trying to understand how array.sort works with custom comparer block

I am new to ruby and doing a RubyMonk tutorial. One of the problems is the following. Can someone please enlighten me because I am not understanding the suggested solution?
Problem Statement
Create a method named 'sort_string' which accepts a String and rearranges all the words in ascending order, by length. Let's not treat the punctuation marks any different than other characters and assume that we will always have single space to separate the words.
Example: Given a string "Sort words in a sentence", it should return "a in Sort words sentence".
Suggested Solution:
def sort_string(string)
string.split(' ').sort{|x, y| x.length <=> y.length}.join(' ')
end
My questions are;
1) Why are there two block variables being passed through? Should there only be one, because you are going through every element of the sentence one at a time?
2) I looked up the <=> operator and it states,"Combined comparison operator. Returns 0 if first operand equals second, 1 if first operand is greater than the second and -1 if first operand is less than the second." So aren't we essentially sorting by -1, 0, and 1 then, not the words?
Thank you very much in advance for your help!
1) Why are there two block variables being passed through? Should there only be one, because you are going through every element of the sentence one at a time?
Because that's how the sort method works. It compares two elements at a time, and the block tells it how to compare the two elements. There is a single-element method called sort_by which will only require one which could be used in this case:
def sort_string(string)
string.split(' ').sort_by{|x| x.length}.join(' ')
end
Or even shorter:
def sort_string(string)
string.split(' ').sort_by(&:length).join(' ')
end
2) I looked up the <=> operator and it states,"Combined comparison operator. Returns 0 if first operand equals second, 1 if first operand is greater than the second and -1 if first operand is less than the second." So aren't we essentially sorting by -1, 0, and 1 then, not the words?
Again, this is how sorting works. Sort looks at the result and, depending upon the value -1, 0, or 1 will order the original data accordingly. It's not ordering the results of <=> directly. If you've done any C programming and used strcmp, think about how you would use that function. It's based upon the same concept.
For the first question, if you look at the documentation for the sort method its block form takes two variables
http://www.ruby-doc.org/core-2.0.0/Array.html#method-i-sort
For the second question, the spaceship operator does a comparison between the two operands and then returns -1, 0, or 1, and then you're sorting on the results. Yes, you're sorting on -1, 0, and 1, but those values are obtained from the comparison.
There are two block variables because to sort you need two items - you can't compare one item against nothing or itself.
You are sorting by -1, 0 and 1 - through the words.
Both of these questions are related to the sort method - here's an example which might make it clearer:
(1..10).sort { |a, b| b <=> a } #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
for each number 1 -10, sort looks at 'a' and 'b' - the block arguments. then in the block, the code says to order b higher than a - and this is what it does
The sort function by default sorts the items in some order.
However, if it is passed a block, it uses that block to compare the elements of the array, so that you can define a different, custom order of the elements.
That is, the block has to compare the elements. A minimalistic working version of such comparison is to compare two elements, just to know which one is "greater-or-equal".
This is why the custom block takes two parameters: those are the elements to compare. You don't actually know which one. The sort will perform some sorting algorithm and depending on the internals, it will pick some pairs of elements, compare them using your block, and then, well, it will use that knowledge to reorder the elements in order.
As you provide a block that 'compares', it'd be not very efficient to just return BOOL that says "greater or not". A better and often a bit faster way is to determine if the elements are equal, less, or greater. At once.
arr.sort {|item1, item2}
if item1 < item2 return :lesser
if item1 == item2 return :equal
if item1 < item2 return :greater
}
This is just pseudocode.
With numbers, it is very easy: just subtract them. If you get less-than-zero, you know that the first was lesser. If you get more-than-zero, the first was bigger. If you got zero, they were equal. So, over the time it was 'standarized' way of describing the three-way comparison result to some sorting algorithms.
arr.sort {|item1, item2}
if item1 < item2 return -1
if item1 == item2 return 0
if item1 > item2 return 1
}
or just
arr.sort {|item1, item2}
return item1 - item2
}
Not all types can be subtracted though. Ruby went somewhat further and defined "comparison operator". Instead of just separate </>/==/<=/>=, it provides you with <=> that returns numeric values. -1 meaning that left was lesser and so on.
arr.sort {|item1, item2}
return item1 <=> item2
}
Now, if you provide <=> operator to MyClass, you will be able to sort them easily, even if plain item1-item2 cannot work on non-numeric 'MyClass'..

Ruby - Picking an element in an array with 50% chance for a[0], 25% chance for a[1]

Nothing too complicated, basically I just want to pick an element from the array as if I were making coin tosses for each index and and choosing the index when I first get a head. Also no heads means I choose the last bin.
I came up with the following and was wondering if there was a better/more efficient way of doing this.
def coin_toss(size)
random_number = rand(2**size)
if random_number == 0
return size-1
else
return (0..size-1).detect { |n| random_number[n] == 1 }
end
end
First guess...pick a random number between 1 and 2**size, find the log base 2 of that, and pick the number that many elements from the end.
Forgive my horrible ruby skillz.
return a[-((Math.log(rand(2**size-1)+1) / Math.log(2)).floor) - 1]
if rand returns 0, the last element should be chosen. 1 or 2, the next to last. 3, 4, 5, or 6, third from the end. Etc. Assuming an even distribution of random numbers, each element has twice as much chance of being picked as the one after it.
Edit: Actually, it seems there's a log2 function, so we don't have to do the log/log(2) thing.
return a[-(Math.log2(rand(2**size - 1)+1).floor) - 1]
You may be able to get rid of those log calls altogether like
return a[-((rand(2**size-1)+1).to_s(2).length)]
But you're creating an extra String. Not sure whether that's better than complicated math. :)
Edit: Actually, if you're going to go the string route, you can get rid of the +1 and -1 altogether. It'd make the probabilities more accurate, as the last two elements should have an equal chance of being chosen. (If the next-to-last value isn't chosen, the last value would always be.)
Edit: We could also turn the ** into a bit shift, which should be a little faster (unless Ruby was smart enough to do that already).
return a[-(rand(1<<size).to_s(2).length)]
A non-smart, simple way is:
def coin_toss( arr )
arr.detect{ rand(2) == 0 } || arr.last
end

sorting algorithm where pairwise-comparison can return more information than -1, 0, +1

Most sort algorithms rely on a pairwise-comparison the determines whether A < B, A = B or A > B.
I'm looking for algorithms (and for bonus points, code in Python) that take advantage of a pairwise-comparison function that can distinguish a lot less from a little less or a lot more from a little more. So perhaps instead of returning {-1, 0, 1} the comparison function returns {-2, -1, 0, 1, 2} or {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5} or even a real number on the interval (-1, 1).
For some applications (such as near sorting or approximate sorting) this would enable a reasonable sort to be determined with less comparisons.
The extra information can indeed be used to minimize the total number of comparisons. Calls to the super_comparison function can be used to make deductions equivalent to a great number of calls to a regular comparsion function. For example, a much-less-than b and c little-less-than b implies a < c < b.
The deductions cans be organized into bins or partitions which can each be sorted separately. Effectively, this is equivalent to QuickSort with n-way partition. Here's an implementation in Python:
from collections import defaultdict
from random import choice
def quicksort(seq, compare):
'Stable in-place sort using a 3-or-more-way comparison function'
# Make an n-way partition on a random pivot value
segments = defaultdict(list)
pivot = choice(seq)
for x in seq:
ranking = 0 if x is pivot else compare(x, pivot)
segments[ranking].append(x)
seq.clear()
# Recursively sort each segment and store it in the sequence
for ranking, segment in sorted(segments.items()):
if ranking and len(segment) > 1:
quicksort(segment, compare)
seq += segment
if __name__ == '__main__':
from random import randrange
from math import log10
def super_compare(a, b):
'Compare with extra logarithmic near/far information'
c = -1 if a < b else 1 if a > b else 0
return c * (int(log10(max(abs(a - b), 1.0))) + 1)
n = 10000
data = [randrange(4*n) for i in range(n)]
goal = sorted(data)
quicksort(data, super_compare)
print(data == goal)
By instrumenting this code with the trace module, it is possible to measure the performance gain. In the above code, a regular three-way compare uses 133,000 comparisons while a super comparison function reduces the number of calls to 85,000.
The code also makes it easy to experiment with a variety comparison functions. This will show that naïve n-way comparison functions do very little to help the sort. For example, if the comparison function returns +/-2 for differences greater than four and +/-1 for differences four or less, there is only a modest 5% reduction in the number of comparisons. The root cause is that the course grained partitions used in the beginning only have a handful of "near matches" and everything else falls in "far matches".
An improvement to the super comparison is to covers logarithmic ranges (i.e. +/-1 if within ten, +/-2 if within a hundred, +/- if within a thousand.
An ideal comparison function would be adaptive. For any given sequence size, the comparison function should strive to subdivide the sequence into partitions of roughly equal size. Information theory tells us that this will maximize the number of bits of information per comparison.
The adaptive approach makes good intuitive sense as well. People should first be partitioned into love vs like before making more refined distinctions such as love-a-lot vs love-a-little. Further partitioning passes should each make finer and finer distinctions.
You can use a modified quick sort. Let me explain on an example when you comparison function returns [-2, -1, 0, 1, 2]. Say, you have an array A to sort.
Create 5 empty arrays - Aminus2, Aminus1, A0, Aplus1, Aplus2.
Pick an arbitrary element of A, X.
For each element of the array, compare it with X.
Depending on the result, place the element in one of the Aminus2, Aminus1, A0, Aplus1, Aplus2 arrays.
Apply the same sort recursively to Aminus2, Aminus1, Aplus1, Aplus2 (note: you don't need to sort A0, as all he elements there are equal X).
Concatenate the arrays to get the final result: A = Aminus2 + Aminus1 + A0 + Aplus1 + Aplus2.
It seems like using raindog's modified quicksort would let you stream out results sooner and perhaps page into them faster.
Maybe those features are already available from a carefully-controlled qsort operation? I haven't thought much about it.
This also sounds kind of like radix sort except instead of looking at each digit (or other kind of bucket rule), you're making up buckets from the rich comparisons. I have a hard time thinking of a case where rich comparisons are available but digits (or something like them) aren't.
I can't think of any situation in which this would be really useful. Even if I could, I suspect the added CPU cycles needed to sort fuzzy values would be more than those "extra comparisons" you allude to. But I'll still offer a suggestion.
Consider this possibility (all strings use the 27 characters a-z and _):
11111111112
12345678901234567890
1/ now_is_the_time
2/ now_is_never
3/ now_we_have_to_go
4/ aaa
5/ ___
Obviously strings 1 and 2 are more similar that 1 and 3 and much more similar than 1 and 4.
One approach is to scale the difference value for each identical character position and use the first different character to set the last position.
Putting aside signs for the moment, comparing string 1 with 2, the differ in position 8 by 'n' - 't'. That's a difference of 6. In order to turn that into a single digit 1-9, we use the formula:
digit = ceiling(9 * abs(diff) / 27)
since the maximum difference is 26. The minimum difference of 1 becomes the digit 1. The maximum difference of 26 becomes the digit 9. Our difference of 6 becomes 3.
And because the difference is in position 8, out comparison function will return 3x10-8 (actually it will return the negative of that since string 1 comes after string 2.
Using a similar process for strings 1 and 4, the comparison function returns -5x10-1. The highest possible return (strings 4 and 5) has a difference in position 1 of '-' - 'a' (26) which generates the digit 9 and hence gives us 9x10-1.
Take these suggestions and use them as you see fit. I'd be interested in knowing how your fuzzy comparison code ends up working out.
Considering you are looking to order a number of items based on human comparison you might want to approach this problem like a sports tournament. You might allow each human vote to increase the score of the winner by 3 and decrease the looser by 3, +2 and -2, +1 and -1 or just 0 0 for a draw.
Then you just do a regular sort based on the scores.
Another alternative would be a single or double elimination tournament structure.
You can use two comparisons, to achieve this. Multiply the more important comparison by 2, and add them together.
Here is a example of what I mean in Perl.
It compares two array references by the first element, then by the second element.
use strict;
use warnings;
use 5.010;
my #array = (
[a => 2],
[b => 1],
[a => 1],
[c => 0]
);
say "$_->[0] => $_->[1]" for sort {
($a->[0] cmp $b->[0]) * 2 +
($a->[1] <=> $b->[1]);
} #array;
a => 1
a => 2
b => 1
c => 0
You could extend this to any number of comparisons very easily.
Perhaps there's a good reason to do this but I don't think it beats the alternatives for any given situation and certainly isn't good for general cases. The reason? Unless you know something about the domain of the input data and about the distribution of values you can't really improve over, say, quicksort. And if you do know those things, there are often ways that would be much more effective.
Anti-example: suppose your comparison returns a value of "huge difference" for numbers differing by more than 1000, and that the input is {0, 10000, 20000, 30000, ...}
Anti-example: same as above but with input {0, 10000, 10001, 10002, 20000, 20001, ...}
But, you say, I know my inputs don't look like that! Well, in that case tell us what your inputs really look like, in detail. Then someone might be able to really help.
For instance, once I needed to sort historical data. The data was kept sorted. When new data were added it was appended, then the list was run again. I did not have the information of where the new data was appended. I designed a hybrid sort for this situation that handily beat qsort and others by picking a sort that was quick on already sorted data and tweaking it to be fast (essentially switching to qsort) when it encountered unsorted data.
The only way you're going to improve over the general purpose sorts is to know your data. And if you want answers you're going to have to communicate that here very well.

Resources