drmacsika
drmacsika

Reputation: 33

ruby program with list

I am trying to get a list of colors to print after the color blue without using the index. I did this

colors = ["Red", "Blue", "Green", "Purple", "White", "Black"]

colors.each { |item| print item if item > "Blue" }

but the output is this

Red Green Purple White

Does anyone know why?

Upvotes: 1

Views: 71

Answers (3)

knut
knut

Reputation: 27875

The question "Why" is already answered in the comment of davidhu2000:

Because you are printing all items that is alphabetically after blue

If you don't want to use the index, then you can use a solution with a help variable:

colors = ["Red", "Blue", "Green", "Purple", "White", "Black"]

blue_found  = false
colors.each { |item| 
  if blue_found
    print item
  else
      blue_found ||= item == "Blue" 
  end
}

If you prefer a one-liner you could use

  blue_found ? (print item) :  (blue_found ||= item == "Blue" )

Another possibility:

colors = ["Red", "Blue", "Green", "Purple", "White", "Black"]

blue_found  = false
colors.each { |item| 
  print item if blue_found 
  blue_found ||= item == "Blue" 
}

I was curious, why you don't want to use the index, so I made some more research outside your question.

The solution with index would be:

colors.each_with_index { |item,i| 
  print item if i > colors.index("Blue")
}

The index-method may need some more runtime. To test it, I made a little benchmark:

colors = ["Red", "Blue", "Green", "Purple", "White", "Black"]
require 'benchmark'

TEST_LOOPS = 100_000

def action(item)
  #Just a no-action.
end

Benchmark.bm(10) {|b|

  b.report('test/variable') {
   TEST_LOOPS.times { 
    blue_found  = false
    colors.each { |item| 
      action(item) if blue_found 
      blue_found ||= item == "Blue" 
    }
   }            #Testloops
  }             #b.report

  b.report('test/index') {
   TEST_LOOPS.times { 
    colors.each_with_index { |item,i| 
      action(item) if i > colors.index("Blue")
    }
   }            #Testloops
  }             #b.report

  b.report('test/index2') {
   TEST_LOOPS.times { 
    index_blue = colors.index("Blue")
    colors.each_with_index { |item,i| 
      action(item) if i > index_blue
    }
   }            #Testloops
  }             #b.report

  b.report('test/dogbert') {
   TEST_LOOPS.times { 
      # Drop all items until you find "Blue", then drop the "Blue".
      colors.drop_while { |item| item != "Blue" }.drop(1).each do |item|
        action(item)
      end
    }            #Testloops
  }             #b.report

  b.report('test/pjs') {
   TEST_LOOPS.times { 
      after_blue = colors.dup
      loop do
        break if after_blue.shift == 'Blue'|| after_blue.length < 1
      end
      after_blue.each{|item| action(item) }
   }            #Testloops
  }             #b.report

} #Benchmark

The result:

                 user     system      total        real
test/variable  0.187000   0.000000   0.187000 (  0.179010)
test/index     0.250000   0.000000   0.250000 (  0.254015)
test/index2    0.140000   0.000000   0.140000 (  0.136008)
test/dogbert   0.110000   0.000000   0.110000 (  0.111006)
test/pjs       0.327000   0.000000   0.327000 (  0.332019)

So the version with a help variable is (as expected) faster. But if you define the index once outside the loop, then the solution with index (test/index2) is nearly as fast as test/variable.

Disclaimer: I made not many thoughts for faster solutions. So maybe there is a more efficient way. Thanks to dogbert for his hint.

Upvotes: 4

Dogbert
Dogbert

Reputation: 222368

Although the two answers already present give the correct answer, I'd use a more functional style, which in my opinion is much more readable.

colors = ["Red", "Blue", "Green", "Purple", "White", "Black"]
# Drop all items until you find "Blue", then drop the "Blue".
colors.drop_while { |item| item != "Blue" }.drop(1).each do |item|
  puts item
end

Since @knut mentioned benchmarks, this code is only a tiny bit slower than @knut's test/variable benchmark:

                 user     system      total        real
test/variable  0.790000   0.000000   0.790000 (  0.790960)
test/drop      0.900000   0.000000   0.900000 (  0.898137)

Upvotes: 0

pjs
pjs

Reputation: 19855

Another variant that runs neck and neck with @knut's test/variable benchmark is:

def values_after(target, arr)
  result = arr.dup
  loop do
    break if result.shift == target || result.length < 1
  end
  result
end

colors = ['Red', 'Blue', 'Green', 'Purple', 'White', 'Black']
puts *values_after('Blue', colors)
# Or, if you prefer all on one comma-separated line:
#   puts values_after('Blue', colors).join(', ')

The || result.length < 1 is a guard clause to prevent an infinite loop if target is not an element in arr.

Upvotes: 2

Related Questions