2

I'm fairly new to Ruby and I'm trying to achieve the following : I have an array of objects with two properties (refers_to and is) which are already sorted by date. On top of that, I want to sort them such as

obj1 < obj2 if obj1.refers_to == obj2.is

This is what I have tried so far, but :

  • I'm pretty such that is no the 'Ruby' way of doing it.
  • It's not even working : Error undefined methodsort_by' for #<` (I reckon I could have something to do with mutating while enumerating)

    @array.each_with_index do |e,i|
      if e.refers_to != 0
        @array.slice(i+1,@array.count-i-1).each_with_index do |f,j|
          if f.is == e.refers_to
            #@array.insert( ... ) Insert doesn't work
          end
        end
      end
    end
    

Update : Majioa's answer

Although I learned a lot from that answer and though it was working while fiddling with a small dataset, I now realize it's not and I'm beginning to think that this result can't be obtained by a (binary) sort algorithm.

My algebra is out of order so I'm going to lack the proper formalism, but basically what the sorting algorithm is doing is comparing the elements two by two and moving them accordingly.

Let's use this example :

4, refers_to =>2
3
2
1

when we reach this state :

3,
2,
4,refers_to =>2
1

At that point, we are going to need that say : if 4 is at the right place, don't move it further, i.e. when comparing '4' to '1', we need to know the state of '2', hence my postulate :

This can't be achieved by a binary sort.

  1. Do I make sense ?
  2. Can we make a tertiary sort ?
4
  • 2
    you can use :sort method with block like: arr.sort do |x,y| x <=> y end, and when the result is -1, x has been put before y, if 1 vice versa. Commented Jan 10, 2014 at 19:02
  • 1
    You said they are already sorted by date. Do you want to keep the date order but sort those with the same date using the two attributes? Do these attributes provide an ordering? Does obj1.refers_to == obj2.is => true imply obj2.refers_to == obj1.is => false? Commented Jan 10, 2014 at 20:45
  • No, unfortunately it's not reciprocal. That's why in my opinion we can't define an ordering function using only 2 elements (cf my remark). Commented Jan 13, 2014 at 16:25
  • have the answer been helpful? Commented Jan 22, 2014 at 10:01

2 Answers 2

3

I guess you should do something like:

sorted = arr.sort do | obj1, obj2 |
   obj1.date < obj2.date && -1 ||
   obj1.date > obj2.date && 1 ||
   obj1.refers_to == obj2.is && -1 || 0
end

It sorts the array at first by date, when dates are equal, by the condition. When the comparison result is -1, obj1 has been put before obj2, if 1 vice versa.

Update

Assume we have the array formed from your example:

l = [ [ 1, nil ], [ 2, nil ], [ 3, nil ], [ 4, 2 ] ]

Let's sort in an accending order:

l.sort do | x, y |
   y[ 1 ] && x[ 0 ] <=> y[ 1 ] || x[ 0 ] <=> y[ 0 ]
end

# => [[1, nil], [2, nil], [4, 2], [3, nil]]

Or let's sort it in descending order, but keeping the number, and its referrence in the same order:

l.sort do | x, y |
   y[ 1 ] && y[ 1 ] <=> x[ 0 ] || y[ 0 ] <=> x[ 0 ]
end

# => [[3, nil], [2, nil], [4, 2], [1, nil]] 
Sign up to request clarification or add additional context in comments.

4 Comments

I would personally use the ternary operator instead of && and ||, but (with appropriate parens, as necessary) they should be equivalent.
@Heisennberg what do you mean Can we make a tertiary sort ? you may sort by any number of parameters, which may be combined by && and || operators.
Well, instead of using a binary relation to order our elements two by two (order_2(obj1,obj2)==-1 =>obj1 <=obj2; order_2(obj1,obj2)==1 => obj1 < obj2), can we do the same thing using a tertiary relation (order_3(obj1,obj2,obj3)==-1 =>obj1<obj2<obj3; etc`). It seems highly impractical now that I'm typing it though :S
@Heisennberg this obj1.date < obj2.date && -1 || obj1.date > obj2.date && 1 || obj1.refers_to == obj2.is && -1 || 0 could be replaced to c = obj1.date <=> obj2.date c == 0 && obj1.refers_to == obj2.is && -1 || 0
1

As I understand, you have an an array arr that is already sorted by date, and you want to use attributes of the elements to perform a secondary sort, for each date. To do that, the attributes must provide an ordering, meaning that you must be able to construct a method compare(obj1, obj2) such that:

obj1 <  obj2 implies compare(obj1, obj1) => -1 
obj1 == obj2 implies compare(obj1, obj1) =>  0
obj1 >  obj2 implies compare(obj1, obj1) =>  1

or

obj1 <= obj2 implies compare(obj1, obj1) => -1 
obj1 >  obj2 implies compare(obj1, obj1) =>  1

or

obj1 <  obj2 implies compare(obj1, obj1) => -1 
obj1 >= obj2 implies compare(obj1, obj1) =>  1

Given this method compare, you may do the following:

arr.chunk {|h| h[:date]}.map(&:last).map \
  {|e| e.sort {|obj1, obj2| compare(obj1, obj2)}}.flatten

Here's an example:

arr = [{date: 1, val: 2},
       {date: 2, val: 3}, {date: 2, val: 1},
       {date: 4, val: 3}, {date: 4, val: 2}]

def compare(obj1, obj2)
  obj1[:val] <=> obj2[:val]
end

a = arr.chunk {|h| h[:date]}
  # => #<Enumerator: #<Enumerator::Generator:0x007fc494a4b8d8>:each>
  # a.to_a
  # => [[1, [{:date=>1, :val=>2}]],
  #     [2, [{:date=>2, :val=>3}, {:date=>2, :val=>1}]],
  #     [4, [{:date=>4, :val=>3}, {:date=>4, :val=>2}]]]   

b = a.map(&:last)
  # => [[{:date=>1, :val=>2}],
  #    [{:date=>2, :val=>3}, {:date=>2, :val=>1}],
  #    [{:date=>4, :val=>3}, {:date=>4, :val=>2}]]

c = b.map {|e| e.sort {|obj1, obj2| compare(obj1, obj2)}}
  # => [[{:date=>1, :val=>2}],
  #     [{:date=>2, :val=>1}, {:date=>2, :val=>3}],
  #     [{:date=>4, :val=>2}, {:date=>4, :val=>3}]]

c.flatten
  # => [{:date=>1, :val=>2},
  #     {:date=>2, :val=>1}, {:date=>2, :val=>3},
  #     {:date=>4, :val=>2}, {:date=>4, :val=>3}] 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.