1

I DID checkout both this question, and that other similar question, none of them offers a solution to replace elements of an array with multiple test values.

I have a Ruby array:

array = ["america", "europe", "asia", "africa", "france", "usa", "spain", "paris", "los angeles"]

I would like to transform this array, to get the following result:

array = ["continent", "continent", "continent", "continent", "country", "country", "country", "city", "city"]

My first attempt was to do something like that:

array.collect! do |element|
  (element == "america") ? "continent" : element
  (element == "europe") ? "continent" : element
  (element == "asia") ? "continent" : element
  (element == "africa") ? "continent" : element
  (element == "france") ? "country" : element
  (element == "usa") ? "country" : element
  (element == "spain") ? "country" : element
  (element == "paris") ? "city" : element
  (element == "los angeles") ? "city" : element
end

I have two problems with this code:

  1. I am not sure this is the way to use a Ruby block with do end.

  2. This code is not DRY and I believe I could use a set of three case loops instead, one for continents, one for countries and one for cities.

4 Answers 4

9

Here's a better way

array.collect! do |element|
  case element
  when 'america', 'europe', 'asia', 'africa'
    'continent'
  when 'france', 'spain'
    'country'
  when 'paris', 'los angeles'
    'city'
  else
    element
  end
end
Sign up to request clarification or add additional context in comments.

1 Comment

A small note - collect is an alias for map.
4

I'd use a hash to do the lookups. It's O(1) and very simple. The second argument to fetch is the default if a key doesn't exist.

INDEX = {
  "america" => "continent", 
  "europe" => "continent", 
  "asia" => "continent", 
  "africa" => "continent", 
  "france" => "country", 
  "usa" => "country", 
  "spain" => "country", 
  "paris" => "city", 
  "los angeles" => "city"
}

[
  "america", 
  "europe", 
  "asia", 
  "africa", 
  "france", 
  "usa", 
  "spain", 
  "paris", 
  "los angeles", 
  "not indexed"
].map{|key| INDEX.fetch(key, key) }

Comments

2

You can not get any more DRY than this. (also notice this is very similar to what TJ Singleton does)

array = ["america", "europe", "asia", "africa", "france", "usa", "spain", "paris", "los angeles"]

definitions = {
  "continent" => ["america", "europe", "asia", "africa"],
  "country" => ["france", "usa", "spain"],
  "city" => ["paris", "los angeles"],
  "planet" => ["mars","earth"]
}

inverse_array = definitions.map {|k,v| v.map { |e| [e, k]}}.flatten(1)
inverse_hash = Hash[inverse_array]

output = array.map { |e| inverse_hash[e] }
puts output.inspect

3 Comments

I like your approach, but would suggest definitions.flat_map { |k,v| v.product [k] }.to_h.values_at *array.
Ahah realy nice code man! I was just thinking now "uhm, sure there is something more elegant than this flatten(1) ..." big up!
One difference from the original code is it won't handle a value , but you use Hash.new to provide a default. inverse_hash = Hash.new {|hash, key| key }; inverse_array = definitions.each {|k,v| v.each {|e| inverse_hash[e] = k }}
1

What about cleaning it up by using Set to keep track of what a continent, country and city is? Lookups in a set are O(1) so you're no worse for wear here:

continents = Set.new ["america", "europe", "asia", "africa"]
countries = Set.new ["france", "usa", "spain"]
cities = Set.new ["paris", "los angeles"]

array = ["america", "europe", "asia", "africa", "france", "usa", "spain", "paris", "los angeles"]

newArray = array.map{ |c|
puts c
  if continents.include? c
    'continent' 
  elsif countries.include? c
    'country' 
  elsif cities.include? c
    'city'
  else
    'unknown'
  end
}

another solution with maps where we create a lookup tables where the keys are continents/countries/cities and the values are the corresponding strings "continent", "country" or "city":

continents = ["america", "europe", "asia", "africa"].each_with_object({}) { |k,h| h[k] = 'continent' }
countries = ["france", "usa", "spain"].each_with_object({}) { |k,h| h[k] = 'country' }
cities = ["paris", "los angeles"].each_with_object({}) { |k,h| h[k] = 'city' }

array = ["america", "europe", "asia", "africa", "france", "usa", "spain", "paris", "los angeles"]

newArray = array.map{ |c| continents[c] || countries[c] || cities[c] || c }

1 Comment

curly braces with multiline block? :)

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.