1

Given list of facts, test the fact is_real?, then return only passing_facts if the speaker attribute is not also stating alternative_facts.

passing_facts, alternative_facts = []
@facts.each do |fact|
  if fact.is_real?
    passing_facts << fact
  else
    alternative_facts << fact
  end
end

alternative_facts.each do |bad_fact|
  passing_facts = passing_facts.reject {|good_fact| good_fact.speaker == bad_fact.speaker }
end

return passing_facts

How do you re-arrange the loops/tests so data doesn't have to be access as many times.

Given data set has more passing_facts than alternative_facts

2
  • 1
    A simple example, with the expected result, would be helpful. Commented Jan 26, 2017 at 22:18
  • 1
    Your question title sounds more like a command than a question. PS this should probably be on CodeReview Commented Jan 26, 2017 at 22:49

3 Answers 3

4

Your first 8 lines can be replaced by Enumerable#partition.

This code iterates once over @facts, once over alternative_facts and once over passing_facts. It uses Set for faster lookups :

require 'set'

passing_facts, alternative_facts = @facts.partition(&:real?)

bad_speakers = Set.new(alternative_facts.map(&:speakers))

passing_facts.reject! do |fact|
  bad_speakers.include? fact.speaker
end

return passing_facts

The average complexity should be O(n), compared to O(n**2) for your code and O(n*log(n)) for the other answer.

Sign up to request clarification or add additional context in comments.

5 Comments

Nice to see Set used for this purpose. Not only does it automatically remove duplicates, which makes things faster, but it has very fast look-up times as well.
Really like that you used Set, that didn't come to my mind when I was writing my answer. Just to note a common misconception about the include? method. It in fact iterates over the whole array. So your solution would be O(n^2). Source: github.com/ruby/ruby/blob/… Source: ruby-doc.org/core-2.2.0/Array.html#include-3F-method
@StankoKrtalićRusendić : bad_speakers is a set. So it uses Set#include?, which is O(1) on average.
Oh, sorry. Didn't know that.
@StankoKrtalićRusendić : No problem. The score is now 1:1 for misguided comments ;)
3

Depending if you count a sort as a loop this might suffice.

Since hash access is cheap we can say it's O(1) and sorting is O(n*log(n)). The following implementation would have O(n + n*log(n)) which is the same as O(n*log(n)). That is less than O(n + n^2) ~ O(n^2) of your example.

This also meant that the data is accessed fewer times than in your example.

sorted_facts = @facts.sort_by(:real?).reverse!
author_invalidity = {}

sorted_facts.select do |fact|
  author_invalidity[fact.speaker] ||= !fact.real?
  fact.real? && !author_invalidity[fact.speaker]
end

A quick explanation of the idea.

We try to build a hash map of author validity to remove the nested loop from your example. By sorting the facts by truthfulness, so that the false ones come first, we can guarantee that by the time we iterate over the first true fact we have all the invalid authors in our hash map. Then by checking the hash and the fact we can build a list of valid facts in the same iteration we build the hash.

Note that the author_invalidity and double ! are confusing but required in order to utilize ||=. If instead author_validity would be stored (e.g. author_validity[fact.speaker] ||= fact.real?) then the check would always return true after the first valid author is processed. Therefore the logic has to be negative. As outlined in other answers, instead of a Hash a Set can be utilized. Then the logic would be positive.

Hope this gets you thinking in the right direction.

Comments

2

I suggest you write it as follows.

class Fact
  def initialize (fact)
     @fact = fact
  end
  def fact
    @fact[:fact]
  end
  def is_real?
     @fact[:real]
  end
  def speaker
     @fact[:speaker]
  end
end

Create some instances.

facts = [["grass is green", true, "Bob"], ["bears are orange", false, "Sue"],
         ["cats say 'woof'", false, "Bob"], ["dogs are delightful", true, "Hal"]].
          map { |f,t,s| Fact.new(fact: f, real: t, speaker: s) }
  #=> [#<Fact:0x007fd363e4bcc0 @fact=
  #      {:fact=>"grass is green", :real=>true, :speaker=>"Bob"}>,
  #    #<Fact:0x007fd363e4bc20 @fact=
  #      {:fact=>"bears are orange", :real=>false, :speaker=>"Sue"}>,
  #    #<Fact:0x007fd363e4bb80 @fact=
  #      {:fact=>"cats say 'woof'", :real=>false, :speaker=>"Bob"}>,
  #    #<Fact:0x007fd363e4bae0 @fact=
  #      {:fact=>"dogs are delightful", :real=>true, :speaker=>"Sue"}>
  #   ] 

Partition facts into passing_facts and alternative_facts.

passing_facts, alternative_facts = facts.partition(&:is_real?)
  #=> [[#<Fact:0x007fd363e4bcc0 @fact=
  #       {:fact=>"grass is green", :real=>true, :speaker=>"Bob"}>,
  #     #<Fact:0x007fd363e4bae0 @fact=
  #       {:fact=>"dogs are delightful", :real=>true, :speaker=>"Hal"}>
  #    ],
  #    [#<Fact:0x007fd363e4bc20 @fact=
  #       {:fact=>"bears are orange", :real=>false, :speaker=>"Sue"}>,
  #     #<Fact:0x007fd363e4bb80 @fact=
  #       {:fact=>"cats say 'woof'", :real=>false, :speaker=>"Bob"}>
  #    ]
  #   ] 
passing_facts
  #=> [#<Fact:0x007fd363e4bcc0 @fact=
  #      {:fact=>"grass is green", :real=>true, :speaker=>"Bob"}>,
  #    #<Fact:0x007fd363e4bae0 @fact=
  #      {:fact=>"dogs are delightful", :real=>true, :speaker=>"Hal"}>
  #   ]
alternative_facts
  #   [#<Fact:0x007fd363e4bc20 @fact=
  #      {:fact=>"bears are orange", :real=>false, :speaker=>"Sue"}>,
  #    #<Fact:0x007fd363e4bb80 @fact=
  #      {:fact=>"cats say 'woof'", :real=>false, :speaker=>"Bob"}>
  #   ]

Compile list of speakers for alternative_facts.

alternative_speakers = alternative_facts.map { |f| f.speaker }
  #=> ["Sue", "Bob"]

Reject elements of passing_facts for which the value of the key :speaker is a member of alternative_speakers, then map those remaining to the name of the fact.

passing_facts.reject { |f| alternative_speakers.include?(f.speaker) }.
              map { |f| f.fact }
  #=> ["dogs are delightful"]

Note

passing_facts.reject { |f| alternative_speakers.include?(f.speaker) }
  #=> [#<Fact:0x007fd364a38e70 @fact=
  #     {:fact=>"dogs are delightful", :real=>true, :speaker=>"Hal"}>
  #   ] 

If there are large number of "facts", efficiency could be improved by adding require 'set' and tack .to_set to the end of the expression that computes facts.

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.