Ruby 将N维数组投影到一维
我有一个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
@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,我编辑我的解决方案是为了对你的提出一些意见。