Ruby 将N维数组投影到一维

Ruby 将N维数组投影到一维,ruby,algorithm,Ruby,Algorithm,我有一个n维数组,我想在表中显示。大概是这样的: @data = [[1,2,3],[4,5,6],[7,8,9]] @dimensions = [{:name => "speed", :values => [0..20,20..40,40..60]}, {:name => "distance", :values => [0..50, 50..100, 100..150]}] speed | distance | count 0..20

我有一个n维数组,我想在表中显示。大概是这样的:

@data = [[1,2,3],[4,5,6],[7,8,9]]
@dimensions = [{:name => "speed", :values => [0..20,20..40,40..60]}, 
              {:name => "distance", :values => [0..50, 50..100, 100..150]}]
speed  | distance | count
0..20  | 0..50    | 1
0..20  | 50..100  | 2
0..20  | 100..150 | 3
20..40 | 0..50    | 4
20..40 | 50..100  | 5
20..40 | 100..150 | 6
40..60 | 0..50    | 7
40..60 | 50..100  | 8
40..60 | 100..150 | 9
我希望桌子最终看起来像这样:

@data = [[1,2,3],[4,5,6],[7,8,9]]
@dimensions = [{:name => "speed", :values => [0..20,20..40,40..60]}, 
              {:name => "distance", :values => [0..50, 50..100, 100..150]}]
speed  | distance | count
0..20  | 0..50    | 1
0..20  | 50..100  | 2
0..20  | 100..150 | 3
20..40 | 0..50    | 4
20..40 | 50..100  | 5
20..40 | 100..150 | 6
40..60 | 0..50    | 7
40..60 | 50..100  | 8
40..60 | 100..150 | 9
有什么好办法可以解决这个问题吗?我有一个工作的解决方案,我真的有点自豪;这篇文章有点自吹自擂。然而,它确实感觉太复杂了,我或其他任何人都无法理解以后会发生什么

[nil].product(*@dimensions.map do |d|
   (0...d[:values].size).to_a
end).map(&:compact).map(&:flatten).each do |data_idxs|
   row = data_idxs.each_with_index.map{|data_idx, dim_idx|
     @dimensions[dim_idx][:values][data_idx]
   }
   row << data_idxs.inject(@data){|data, idx| data[idx]}
   puts row.join(" |\t ")
end
[nil].product(*@dimensions.map do|d|
(0…d[:值].size)。到
结束)。映射(&:compact)。映射(&:flatte)。每个do|数据都是IDX|
row=data_idxs。每个_都带有_index.map{| data_idx,dim_idx|
@尺寸[dim_idx][:values][data_idx]
}
行这个怎么样

first, *rest = @dimensions.map {|d| d[:values]}
puts first
  .product(*rest)
  .transpose
  .push(@data.flatten)
  .transpose
  .map {|row| row.map {|cell| cell.to_s.ljust 10}.join '|' }
  .join("\n")
这个怎么样

first, *rest = @dimensions.map {|d| d[:values]}
puts first
  .product(*rest)
  .transpose
  .push(@data.flatten)
  .transpose
  .map {|row| row.map {|cell| cell.to_s.ljust 10}.join '|' }
  .join("\n")

本特,首先让我对你的解决方案发表几点看法。(然后我将提供另一种方法,它也使用
Array#product
)以下是您的代码,格式化为公开结构:

[nil].product(*@dimensions.map { |d| (0...d[:values].size).to_a })
.map(&:compact)
.map(&:flatten)
.each do |data_idxs|
  row = data_idxs.each_with_index.map
    { |data_idx, dim_idx| @dimensions[dim_idx][:values][data_idx] }
  row << data_idxs.inject(@data) { |data, idx| data[idx] }
  puts row.join(" |\t ")
end
我将以这样一种方式解决这个问题,即您可以拥有任意数量的属性(即“速度”、“距离”…),并且格式将由数据决定:

V_DIVIDER = ' | '
COUNT = 'count'

attributes = @dimensions.map {|h| h[:name]}   
sd = @dimensions.map { |h| h[:values].map(&:to_s) }
fmt = sd.zip(attributes)
        .map(&:flatten)
        .map {|a| a.map(&:size)}
        .map {|a| "%-#{a.max}s" }   
attributes.zip(fmt).each { |a,f| print f % a + V_DIVIDER }
puts COUNT

prod = (sd.shift).product(*sd)
flat_data = @data.flatten
until flat_data.empty? do
  prod.shift.zip(fmt).each { |d,f| print f % d + V_DIVIDER }
  puts (flat_data.shift)
end    
如果

这将显示:

speed  | volume    | distance | count
0..20  | 0..30     | 0..50    | 1
0..20  | 0..30     | 50..100  | 2
0..20  | 0..30     | 100..150 | 3
0..20  | 30..100   | 0..50    | 4
0..20  | 30..100   | 50..100  | 5
0..20  | 30..100   | 100..150 | 6
0..20  | 100..1000 | 0..50    | 7
0..20  | 100..1000 | 50..100  | 8
0..20  | 100..1000 | 100..150 | 9
它的工作原理如下(原始值为
@dimensions
,只有两个属性,“速度”和“距离”):

属性
是属性列表。作为一个数组,它保持它们的顺序:

attributes = @dimensions.map {|h| h[:name]}
  # => ["speed", "distance"] 
我们从
@dimensions
中提取范围,并将其转换为字符串:

sd = @dimensions.map { |h| h[:values].map(&:to_s) }
  # => [["0..20", "20..40", "40..60"], ["0..50", "50..100", "100..150"]] 
接下来,我们计算除最后一列之外的所有列的字符串格式:

fmt = sd.zip(attributes)
        .map(&:flatten)
        .map {|a| a.map(&:size)}
        .map {|a| "%-#{a.max}s" }
  # => ["%-6s", "%-8s"] 
这里

中的
8
“%-8s”
等于列标签的最大长度、
距离(8)和表示
距离的最长字符串的长度(也是8,表示
“100..150”
)。左侧格式化字符串中的
-
调整字符串

我们现在可以打印标题:

attributes.zip(fmt).each { |a,f| print f % a + V_DIVIDER }
puts COUNT
speed  | distance | count
为了打印其余的行,我们构造了一个包含前两列内容的数组。数组的每个元素对应于表中的一行:

prod = (sd.shift).product(*sd)
  # => ["0..20", "20..40", "40..60"].product(*[["0..50", "50..100", "100..150"]]) 
  # => ["0..20", "20..40", "40..60"].product(["0..50", "50..100", "100..150"]) 

  # => [["0..20", "0..50"], ["0..20", "50..100"],  ["0..20", "100..150"],
  #    ["20..40", "0..50"], ["20..40", "50..100"], ["20..40", "100..150"],
  #    ["40..60", "0..50"], ["40..60", "50..100"], ["40..60", "100..150"]] 
我们需要将@data放平:

flat_data = @data.flatten
  # => [1, 2, 3, 4, 5, 6, 7, 8, 9] 
第一次通过
直到do
循环

r1 = prod.shift
  # => ["0..20", "0..50"]
  # prod now => [["0..20", "50..100"],...,["40..60", "100..150"]] 
r2 = r1.zip(fmt)
  # => [["0..20", "%-6s"], ["0..50", "%-8s"]]
r2.each { |d,f| print f % d + V_DIVIDER }
0..20  | 0..50    | 
puts (flat_data.shift)
0..20  | 0..50    | 1
  # flat_data now => [2, 3, 4, 5, 6, 7, 8, 9]

本特,首先让我对你的解决方案发表几点看法。(然后我将提供另一种方法,它也使用
Array#product
)以下是您的代码,格式化为公开结构:

[nil].product(*@dimensions.map { |d| (0...d[:values].size).to_a })
.map(&:compact)
.map(&:flatten)
.each do |data_idxs|
  row = data_idxs.each_with_index.map
    { |data_idx, dim_idx| @dimensions[dim_idx][:values][data_idx] }
  row << data_idxs.inject(@data) { |data, idx| data[idx] }
  puts row.join(" |\t ")
end
我将以这样一种方式解决这个问题,即您可以拥有任意数量的属性(即“速度”、“距离”…),并且格式将由数据决定:

V_DIVIDER = ' | '
COUNT = 'count'

attributes = @dimensions.map {|h| h[:name]}   
sd = @dimensions.map { |h| h[:values].map(&:to_s) }
fmt = sd.zip(attributes)
        .map(&:flatten)
        .map {|a| a.map(&:size)}
        .map {|a| "%-#{a.max}s" }   
attributes.zip(fmt).each { |a,f| print f % a + V_DIVIDER }
puts COUNT

prod = (sd.shift).product(*sd)
flat_data = @data.flatten
until flat_data.empty? do
  prod.shift.zip(fmt).each { |d,f| print f % d + V_DIVIDER }
  puts (flat_data.shift)
end    
如果

这将显示:

speed  | volume    | distance | count
0..20  | 0..30     | 0..50    | 1
0..20  | 0..30     | 50..100  | 2
0..20  | 0..30     | 100..150 | 3
0..20  | 30..100   | 0..50    | 4
0..20  | 30..100   | 50..100  | 5
0..20  | 30..100   | 100..150 | 6
0..20  | 100..1000 | 0..50    | 7
0..20  | 100..1000 | 50..100  | 8
0..20  | 100..1000 | 100..150 | 9
它的工作原理如下(原始值为
@dimensions
,只有两个属性,“速度”和“距离”):

属性
是属性列表。作为一个数组,它保持它们的顺序:

attributes = @dimensions.map {|h| h[:name]}
  # => ["speed", "distance"] 
我们从
@dimensions
中提取范围,并将其转换为字符串:

sd = @dimensions.map { |h| h[:values].map(&:to_s) }
  # => [["0..20", "20..40", "40..60"], ["0..50", "50..100", "100..150"]] 
接下来,我们计算除最后一列之外的所有列的字符串格式:

fmt = sd.zip(attributes)
        .map(&:flatten)
        .map {|a| a.map(&:size)}
        .map {|a| "%-#{a.max}s" }
  # => ["%-6s", "%-8s"] 
这里

中的
8
“%-8s”
等于列标签的最大长度、
距离(8)和表示
距离的最长字符串的长度(也是8,表示
“100..150”
)。左侧格式化字符串中的
-
调整字符串

我们现在可以打印标题:

attributes.zip(fmt).each { |a,f| print f % a + V_DIVIDER }
puts COUNT
speed  | distance | count
为了打印其余的行,我们构造了一个包含前两列内容的数组。数组的每个元素对应于表中的一行:

prod = (sd.shift).product(*sd)
  # => ["0..20", "20..40", "40..60"].product(*[["0..50", "50..100", "100..150"]]) 
  # => ["0..20", "20..40", "40..60"].product(["0..50", "50..100", "100..150"]) 

  # => [["0..20", "0..50"], ["0..20", "50..100"],  ["0..20", "100..150"],
  #    ["20..40", "0..50"], ["20..40", "50..100"], ["20..40", "100..150"],
  #    ["40..60", "0..50"], ["40..60", "50..100"], ["40..60", "100..150"]] 
我们需要将@data放平:

flat_data = @data.flatten
  # => [1, 2, 3, 4, 5, 6, 7, 8, 9] 
第一次通过
直到do
循环

r1 = prod.shift
  # => ["0..20", "0..50"]
  # prod now => [["0..20", "50..100"],...,["40..60", "100..150"]] 
r2 = r1.zip(fmt)
  # => [["0..20", "%-6s"], ["0..50", "%-8s"]]
r2.each { |d,f| print f % d + V_DIVIDER }
0..20  | 0..50    | 
puts (flat_data.shift)
0..20  | 0..50    | 1
  # flat_data now => [2, 3, 4, 5, 6, 7, 8, 9]

哇!那真是太好了,谢谢你。我不知道
第一,rest
语法,也不知道ljust,转置/展平非常出色。我将使用s/rest/*rest/g使其在1.8.7/1.9.4中起作用。如果它在其他版本中工作,请随时恢复。哇!那真是太好了,谢谢你。我不知道
第一,rest
语法,也不知道ljust,转置/展平非常出色。我将使用s/rest/*rest/g使其在1.8.7/1.9.4中起作用。如果它在其他版本中工作,请随时恢复。
(sd.shift.product(*sd)
嘿,这很聪明。%-Ns格式也是一个很好的技巧,一般来说,答案比我的版本简单得多。谢谢你的回复<代码>(sd.shift).product(*sd)
嘿,这太聪明了。%-Ns格式也是一个很好的技巧,一般来说,答案比我的版本简单得多。谢谢你的回复!Bent,我编辑我的解决方案是为了对你的提出一些意见。Bent,我编辑我的解决方案是为了对你的提出一些意见。