Kirill Zhuravlov
Kirill Zhuravlov

Reputation: 464

Search an array by the first letters in Ruby?

I have an array:

dictionary = [ 
  'abnormal',
  'arm-wrestling',
  'absolute',
  'airplane',
  'airport',
  'amazing',
  'apple',
  'ball'
]

How can I search the first five matches, or less if there are not five matches, by their first letters?

input = "a"
match = dictionary.select { |a| a.match(input) }
puts match

match returns

["abnormal", "arm-wrestling", "absolute", "airplane", "airport", "amazing", "apple", "ball"]

but I need it to return

["abnormal", "arm-wrestling", "absolute", "airplane", "airport"]

and do not return words like ["ball"] just because it contains "a".

Upvotes: 2

Views: 2663

Answers (3)

Andrey Deineko
Andrey Deineko

Reputation: 52357

If I understood you correctly, you need first five words that starts with 'a'.

You can use String#start_with?:

dictionary.select { |word| word.start_with?('a') }.first(5)

For better performance you can lazy select these five words. It will especially make sense if the collection you are performing search on is growing bigger:

dictionary.lazy.select { |word| word.start_with?('a') }.first(5)

For case insensitive selection:

dictionary.lazy.select { |word| word.start_with?('a', 'A') }.first(5)

Upvotes: 7

the Tin Man
the Tin Man

Reputation: 160551

Looks like there are lots of answers being tossed out with no regard to processing speed. This doesn't attempt to measure returning the first five words, only determining whether words begin with an 'a':

words = %w[
  aback
  agonizing
  bell
  bubble
  dear
  lackadaisical
  mouth
  nonstop
  rinse
  steel
  stroke
]

var = 'a'

require 'fruity'

This compares all:

var = 'a'
compare do
  starts_with { words.select { |w| w.start_with?('a') } }
  downcase { words.select { |w| w.downcase.start_with?('a') } }
  regexp { words.select { |w| w[0] =~ /a/i } }
  mudasobwa1 { words.select { |a| %w|a A|.include? a[0] } }
  mudasobwa2 { words.group_by { |w| w[0] }['a'] }
  mudasobwa3 { words.grep(/\Aa/).to_a }
  mudasobwa4 { words.lazy.grep(/\Aa/).to_a }
  stefan { words.select { |word| word.start_with?('a', 'A') } }
  andrey_deinko { words.select { |a| a[0] =~ /#{var}/ }}
  andrey_deinko2 { words.lazy.select { |word| word.start_with?('a') }.to_a }
end

# >> Running each test 2048 times. Test will take about 4 seconds.
# >> starts_with is similar to stefan
# >> stefan is similar to downcase
# >> downcase is faster than mudasobwa1 by 2x ± 0.1
# >> mudasobwa1 is similar to mudasobwa3
# >> mudasobwa3 is similar to regexp
# >> regexp is similar to mudasobwa2
# >> mudasobwa2 is similar to andrey_deinko2
# >> andrey_deinko2 is similar to mudasobwa4
# >> mudasobwa4 is faster than andrey_deinko by 4x ± 1.0

To try to help the lazy methods, I expanded the size of the words array by 1000:

words = (%w[
  aback
  agonizing
  bell
  bubble
  dear
  lackadaisical
  mouth
  nonstop
  rinse
  steel
  stroke
] * 1000).shuffle

Rerunning shows:

# >> Running each test 2 times. Test will take about 4 seconds.
# >> starts_with is similar to stefan
# >> stefan is similar to andrey_deinko2
# >> andrey_deinko2 is similar to downcase
# >> downcase is similar to mudasobwa2
# >> mudasobwa2 is similar to mudasobwa1
# >> mudasobwa1 is similar to mudasobwa3
# >> mudasobwa3 is similar to mudasobwa4
# >> mudasobwa4 is similar to regexp
# >> regexp is faster than andrey_deinko by 9x ± 1.0

There's a serious speed price to pay with regex so learn to use anchors. I won't go into it here but there are questions and answers on SO showing the differences so search for them.

Keep it simple. Take advantage of the built-in methods designed for the purpose.

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

▶ dictionary.group_by { |w| w[0] }
#⇒ {
#    "a" => [
#        [0] "abnormal",
#        [1] "arm-wrestling",
#        [2] "absolute",
#        [3] "airplane",
#        [4] "airport",
#        [5] "amazing",
#        [6] "apple"
#    ],
#    "b" => [
#        [0] "ball"
#    ]
# }

dictionary.group_by { |w| w[0] }['a'].take(5) will return the array requested.

or, using grep:

dictionary.grep(/\Aa/).take(5)

or, lazy:

dictionary.lazy.grep(/\Aa/).first(5)

Upvotes: 3

Related Questions