2014-01-23 49 views
0

我有一個n維數組,我想在表中顯示。事情是這樣的:將N維數組投影到1-d

@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 
+0

特,我編輯了我的解決方案,爲您提供一些意見。 –

回答

4

這是怎麼回事?

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") 
+0

哇!非常好,謝謝。我不知道「第一,休息」的句法,也不是簡單的,而轉置/拼合也很出色。我打算讓/ rest/\ * rest/g使它在1.8.7/1.9.4中工作。如果它在其他版本中有效,請隨時恢復。 – ABentSpoon

1

彎,讓我先提出一些關於您的解決方案的意見。 (然後,我將提供同樣採用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 
  • 我覺得很混亂,部分。我首先計算product的參數並將其分配給變量x。我說x,因爲很難提出一個好名字。然後,我會將product的結果分配給另一個變量,如:y = x.shift.product(x)或(如果您不想要x修改)y = x.first.product(x[1..-1)。這避免了需要compactflatten
  • 我發現變量名稱的選擇令人困惑。問題的根源在於@dimensions@data都以d開頭!如果你簡單地使用@vals而不是@data,這個問題會大大減少。
  • data_idxs.each_with_index.map寫爲data_idxs.map.with_index會比較習慣。
  • 最後,但最重要的是,您決定使用索引而不是數值本身。 不要這樣做。只是不要那樣做。這不僅是不必要的,但它會讓你的代碼變得如此複雜以至於很難解決這個問題,這很耗費時間,而且令人頭痛。

考慮是多麼容易被操縱的數據沒有任何參考指數:

vals = @dimensions.map {|h| h.values } 
    # [["speed", [0..20, 20..40, 40..60 ], 
    # ["distance", [0..50, 50..100, 100..150]] 
attributes = vals.map(&:shift) 
    # ["speed", "distance"] 
    # vals => [[[0..20, 20..40, 40..60]],[[0..50, 50..100, 100..150]]] 
vals = vals.flatten(1).map {|a| a.map(&:to_s)} 
    # [["0..20", "20..40", "40..60"],["0..50", "50..100", "100..150"]] 
rows = vals.first.product(*vals[1..-1]).zip(@data.flatten).map { |a,d| a << d } 
    # [["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]] 

我會以這樣的方式解決這個問題,你可以有任意數量的屬性(即「速度「,」距離「,...)和格式化將由數據決定:

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  

如果

@dimensions = [{:name => "speed", :values => [0..20,20..40,40..60]  }, 
       {:name => "volume", :values => [0..30, 30..100, 100..1000]}, 
       {:name => "distance", :values => [0..50, 50..100, 100..150] }] 

這顯示:

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是屬性的列表。作爲一個陣列,它保持它們的順序:

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"] 

這裏

sd.zip(attributes) 
    # => [[["0..20", "20..40", "40..60"], "speed" ], 
    #  [["0..50", "50..100", "100..150"], "distance"]] 

8"%-8s"等於列標籤長度的最大值distance(8)和distance範圍的最長字符串表示的長度(對於"100..150"也是8)的最大長度。格式化字符串中的-左鍵調整字符串。

我們現在可以打印頭:

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"]] 

我們需要flaten @data:

flat_data = @data.flatten 
    # => [1, 2, 3, 4, 5, 6, 7, 8, 9] 

第一次通過until 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] 
+0

'(sd.shift).product(* sd)'嘿,這很聰明。 %-Ns格式也是一個很好的技巧,一般來說答案比我的版本更簡單。謝謝回覆! – ABentSpoon