如何优化从 ruby 中的嵌套哈希中提取数据?

史蒂夫·K

背景

我有一组嵌套散列,它们提供一组参数来定义应用程序行为:

custom_demo_options: {
    verticals: {
        fashion: true,
        automotive: false,
        fsi: false
    },
    channels: {
        b2b: true,
        b2c: true
    }
}

website_data: {
    verticals: {
        fashion: {
            b2b: {
                code: 'luma_b2b',
                url: 'b2b.luma.com'
            },
            b2c: {
                code: 'base',
                url: 'luma.com'
            }
        } 
    }
}

custom_demo_options散列中所做的选择与存储在website_data散列中的数据相关,并用于从中返回值:

data = []
collection = {}
custom_demo_options[:verticlas].each do |vertical_name, vertical_choice|
    # Get each vertical selection
    if vertical_choice == true
        # Loop through the channels for each selected vertical
        custom_demo_options[:channels].each do |channel_name, channel_choice|
            # Get each channel selection for each vertical selection
            if channel_choice == true
                # Loop through the website data for each vertical/channel selection
                website_data[:verticals].each do |site_vertical, vertical_data|
                    # Look at the keys of the [:website_data][:verticals] hash
                    # If we have a vertical selection that matches a website_data vertical...
                    if site_vertical == vertical_name
                        # For each website_data vertical collection...
                        vertical_data.each do |vertical_channel, channel_value|
                            # If we have a matching channel in the collection...
                            if vertical_channel == channel_name 
                                # Add the channel's url and code to the collection hash
                                collection[:url] = channel_value[:url]
                                collection[:code] = channel_value[:code]
                                # Push the collection hash(es) onto the data array
                                data.push(collection)
                            }
                        }
                    }
                }
            }
        }
    }
}

推送到数据数组的数据最终用于创建以下nginx映射定义:

map $http_host $MAGE_RUN_CODE {
    luma.com base;
    b2b.luma.com luma_b2b;
}

作为哈希之间关系的示例,如果用户设置了custom_demo_options[:channels][:b2b] tofalse , the b2b code/url pair stored in thewebsite_data` 哈希将从 nginx 块中删除:

map $http_host $MAGE_RUN_CODE {
    luma.com base;
}

问题

上面的代码有效,但我知道它的效率非常低。我对 ruby​​ 比较陌生,但我认为这很可能是一个逻辑挑战,而不是特定于语言的挑战。

我的问题是,连接这些散列而不是像我那样使用循环的正确方法是什么?我已经阅读了一些资料hash.select,似乎这可能是最佳路线,但我想知道:是否还有其他方法可以优化此操作?

更新

我已经能够实施第一个建议(再次感谢海报);但是,我认为第二种解决方案将是更好的方法。一切正常;但是,我的数据结构略有变化,虽然我了解解决方案的作用,但我无法相应地进行调整。这是新的结构:

custom_demo_options = {
    verticals: {
        fashion: true,
        automotive: false,
        fsi: false
    },
    channels: {
        b2b: true,
        b2c: true
    },
    geos: [
        'us_en'
    ]
}
website_data = {
    verticals: {
        fashion: {
            us_en: {
                b2b: {
                    code: 'luma_b2b',
                    url: 'b2b.luma.com'
                },
                b2c: {
                    code: 'base',
                    url: 'luma.com'
                }
            }
        } 
    }
}

因此,我为哈希添加了另一个级别,:geo.

我试图适应第二种解决方案如下:

class CustomOptionsMap
    attr_accessor :custom_options, :website_data

    def initialize(custom_options, website_data)
        @custom_options = custom_options
        @website_data = website_data[:verticals]
    end

    def data
        verticals = selected_verticals
        channels = selected_channels
        geos = selected_geos

        # I know this is the piece I'm not understanding.  How to map channels and geos accordingly.
        verticals.map{ |vertical| @website_data.fetch(vertical).slice(*channels) }
    end

    private
    def selected_geos
        @custom_options[:geos].select{|_,v| v } # I think this is correct, as it extracts the geo from the array and we don't have additional keys
    end
    def selected_verticals
        @custom_options[:verticals].select{|_,v| v }.keys
    end
    def selected_channels
        @custom_options[:channels].select{|_,v| v }.keys
    end
end

demo_configuration = CustomOptionsMap.new(custom_demo_options, website_data)
print demo_configuration.data

非常感谢我对地图声明所缺少的任何指导。

标记

面向对象的方法。

在这种情况下,使用 OOP 可能更具可读性和一致性,因为 Ruby 是面向对象的语言。引入简单的 Ruby 类和 usingactivesupport模块,它使用一些有用的方法扩展 Hash,可以通过以下方式实现相同的结果:

class WebsiteConifg
  attr_accessor :custom_options, :website_data

  def initialize(custom_options, website_data)
    @custom_options = custom_options
    @website_data   = website_data[:verticals]
  end

  def data
    verticals = selected_verticals
    channels = selected_channels

    verticals.map{ |vertical| @website_data.fetch(vertical).slice(*channels) }
  end

  private
  def selected_verticals
    @custom_options[:verticals].select{|_,v| v }.keys
  end

  def selected_channels
    @custom_options[:channels].select{|_,v| v }.keys
  end

根据passed,custom_demo_options我们可以只选择那些键的垂直和通道,其值设置为true

对于您的配置将返回

selected_verticals #  [:fashion]
selected_channels  #  [:b2b, :b2c]

+data()简单的公共接口verticals基于传递的选项遍历所有选定的对象,并使用 返回给定通道的哈希数组slice(keys)

fetch(key) 给定键的返回值相当于 h[:key]

h = {a: 2, b: 3}
h.fetch(:a)  # 2
h.fetch(:b)  # 3

slice(key1, key2) 确实需要 activesupport

返回散列,其中包含作为参数传递的键。方法接受多个参数,因为在我们的示例中我们正在获取这些键的数组,我们可以使用*splat 运算符来遵守此接口。

h = {a: 2, b: 3}
h.slice(:a)         # {:a=>2}
h.slice(:a, :b)     # {:a=>2, :b=>3}
h.slice(*[:a, :b])  # {:a=>2, :b=>3}

用法

  website_config = WebsiteConifg.new(custom_demo_options, website_data) 
  website_config.data   

  # returns
  # [{:b2b=>{:code=>"luma_b2b", :url=>"b2b.luma.com"}, :b2c=>{:code=>"base", :url=>"luma.com"}}]

更新

更改了相关部分:

def data
    verticals = selected_verticals
    channels = selected_channels
    geos = selected_geos

    verticals.map do |vertical|
      verticals_data = @website_data.fetch(vertical)

      # in case of multiple geolocations
      # collecting relevant entries of all of them
      geos_data = geos.map{|geo| verticals_data.fetch(geo) }

      # for each geo-location getting selected channels 
      geos_data.map {|geo_data| geo_data.slice(*channels)  }
    end.flatten
  end

  private 
  # as `website_data' hash is using symbols, we need to covert string->sym 
  def selected_geos
    @custom_options[:geos].map(&:to_sym)
  end

  def selected_verticals
    selected_for(:verticals).keys
  end

  def selected_channels
    selected_for(:channels).keys
  end

  def selected_for(key)
    @custom_options[key].select{|_,v| v }
  end


了解each(map)迭代器中每个步骤的输出(数据)类型的最简单方法是将调试器放在那里,例如:pry,byebug。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章