日本語が下手な件
どういうことかというと...
#この配列を _lst_data = [ [1,2,3], ["a", "b", "c"], [true, false, true] ] #こうしたい _output_image = [ [1, "a", true], [2, "b", false], [3, "c", false] ]
要するに「2次元のリストを縦方向に結合(merge)する」ってことでしょ(強引
elixirでスクレイピングやった時に結構この手の操作があった
ある商品データの
- 商品名
- 金額
- レビュータイトル
- レビューテキスト
をdomの属性を使って抽出すると
scraping_res = [ [4,5,5] ["goog", "いい品だ", "ふつくしい"] ["手軽に購入できてよかったです", "良いものだ", "強靭無敵最強!!"] ]
こんな感じの配列になって、これを以下のようにすると各商品の情報がまとまり
このままCSVデータとして書き込むことができる(あえてマップにしていない)
adj_res = [ [4, "good", "手軽に購入できてよかったです"] [5, "いい品だ", "良いものだ"] [5, "ふつくしい", "強靭無敵最強!!"] ]
Enumを使う最も手軽な方法
一番楽なのはおそらくこれ
lst_data = [ [1,2,3], ["a", "b", "c"], [true, false, true] ] cal_res = lst_data |> Enum.zip() |> Enum.map(&(Tuple.to_list(&1))) IO.inspect(cal_res) #result #[ # [1, "a", true], # [2, "b", false], # [3, "c", false] #]
もしくは
cal_res = lst_data |> Stream.zip() |> Stream.map(&(Tuple.to_list(&1)))
いいね
動き方
Enum.zip()の時点でほとんどの操作は終了している
lst_data = [ [1,2,3], ["a", "b", "c"], [true, false, true] ] cal_res = lst_data |> Enum.zip() IO.inspect(cal_res) #result #[{1, "a", true}, {2, "b", false}, {3, "c", true}]
この時点で縦方向への結合は終了しているわけです(クソ便利
ただ、なぜかタプルに変換されてしまっているので元に戻してやろうねってことで
Enum.mapとTupleのto_listを使ってリストinリストに変換しています
zip_res = [{1, "a", true}, {2, "b", false}, {3, "c", true}] cal_res = zip_res |> Enum.map(&(Tuple.to_list(&1))) # Tuple.to_list({1, "a", true}) -> [1, "a", true] # Tuple.to_list({2, "b", false}) -> [2, "b", false] # Tuple.to_list({3, "c", true}) -> [3, "c", false]
手軽にやる方法は以上で終了です
あとは僕の自己満足です
一回タプルに変換されるのキモくない?
これが個人的には納得がいってない
なんでリストを一度、タプルに変換する必要があるのか...
コレガワカラナイ
タプルに変換しないモジュールを自分で作ってみました
とりえあず書いた
lst_data = [ [1,2,3], ["a", "b", "c"], [true, false, true] ] defmodule Merge do def zip(lst_data) do exe_count = Enum.count(lst_data) _zip(lst_data, 0, exe_count, []) end defp _zip(_lst_data, _searcher, counter, accum) when counter === 0, do: accum defp _zip(lst_data, searcher, counter, accum) do select_values = zip_helper(lst_data, searcher) _zip(lst_data, searcher+1, counter-1, accum ++ [select_values]) end def zip_helper(lst_data, searcher) do exe_count = List.first(lst_data) |> Enum.count() _zip_helper(lst_data, searcher, exe_count, []) end defp _zip_helper(_lst_data, _seracher, counter, accum) when counter === 0, do: accum defp _zip_helper([head | tail], seracher, counter, accum) do target = Enum.at(head, seracher) _zip_helper(tail, seracher, counter-1, accum++[target]) end end res = Merge.zip(lst_data) IO.puts("--> result") IO.inspect(res) #result: [[1, "a", true], [2, "b", false], [3, "c", true]]
全体の動きとしては再帰関数の中で再帰関数を呼んでいる
zip関数はリストを受け取り、要素の数だけzip_helper関数をcallする
zip_helper関数では受け取った配列とindex番号から縦方向に結合した配列を返す
#zip_helper lst_data = [ [1,2,3], ["a", "b", "c"], [true, false, true] ] zip_helper(lst_datam, 0) # [1, _, _] # ["a", _, _] # [true, _, _] #result: [1, "a", true]
pythonでいうfor in forのような動きをイメージして作りました
for column in data: for record in column: pass
csvデータを操作する時にこの手の処理はよくやる
ただEnum.reduceとか使えばもっと綺麗にかける気はする....
Enumにaccumulatorを渡す方法はないものなのか
もう少し研究します
追記:もっと簡単にかけるやん
アホみたいに再帰関数使ってたのを反省
Enum.mapで普通に取り出せることに気づきました
ただEnum.reduceかEnum.foldlあたりを使えばもっと短くできる気はする
elixir
lst_data = [ [1,2,3], ["a", "b", "c"], [true, false, true] ] defmodule Merge do def easy_zip(lst_data) do _easy_zip(lst_data, 0, Enum.count(lst_data), []) end defp _easy_zip(_lstdata, _searcher, counter, accum) when counter === 0, do: accum defp _easy_zip(lst_data, searcher, counter, accum) do merge = Enum.map(lst_data, &(Enum.at(&1, searcher))) _easy_zip(lst_data, searcher+1, counter-1, accum ++ [merge]) end end
ついでにpython
lst_data = [ [1,2,3], ["a", "b", "c"], [True, False, True] ] def merge_column(lst_data, index_num): merge = list(map(lambda x: x[index_num], lst_data)) return merge res = [] for i in range(len(lst_data)): merge = merge_column(lst_data, i) res.append(merge) print(res) #result: [[1, 'a', True], [2, 'b', False], [3, 'c', True]]
コメントより: pythonでクールに1行で
lst_data = [ [1,2,3], ["a", "b", "c"], [True, False, True] ] res = list(map(list, zip(*lst_data)))
うーん、zip(*lst_data)ってのは思いつきませんでした
zipから出力された結合されたタプルをmap関数使ってリストに戻して1行で終了
お見事です