[計算機]CodeIQのピッグデータ問題の回答

CodeIQの結城さんの「《ピッグデータ》に負けないで!」https://codeiq.jp/ace/yuki_hiroshi/q303 に応募した際の回答を貼っておきます.結果,評価5 ベスト・ピッグデータ賞(結果が正しく技術メモも十分な解答)をいただきました.成績の分布は下のとおりだそうです.

評価5: 38
評価3: 0
評価1: 15
合計: 53

このソート方法にはバケットソートという名前がついていることは知りませんでした.現在,私のところへはスカウトは来ていない.:-)



[技術メモ]
209679232 <--これが答え

Date: 2013/5/3

1)答えを求めるのに必要なデータ件数は 100*(2^30)件である.一件あたりのデータサイズは2byteなので,最低でもデータサイズは200Gバイトになる.

2)このデータをソートしなければいけない点を考慮すると,このデータをメモリ上はおろか,外部ファイルに持つのも簡単ではない.したがって別のアプローチを考える.

3)データの値は2byteなので,この値をキーとし,値を発生件数とするHashテーブルを作成して処理を行う.これによりソートを行う必要は無くなくなる.

4)処理を2フェーズに分け,前半のHashテーブルの作成はC(ソースコード1)で行い,結果をCSVファイルに出力する.後半の総和を求める部分はRubyソースコード2)で記述する.Cを採用したの高速化のため,Rubyを採用したのは,記述のしやすさが理由である.なおCの処理では,SHA1の呼び出し回数をを減らすため,2重ループにしてある.Cの処理部分は,下記の環境で142分かかった.

5)検証はサンプルデータに対して行っているが,充分という気がしていない.もう少し考える必要がありそうだ.出題者がどのように正解を検証したのか非常に興味がある.

6)問題の理解のため,最初にHaskellソースコード3)でプロトタイプを作成した.これはサンプルデータでは動くが,実課題のデータサイズでは遅すぎて使えない.

[使用環境]
MacOS 10.7.5
CPU 2.4Ghz Intel Core 2 Duo
メモリ 4G
gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)

[感想]
簡単と思い始めましたが,予想以上に手こずり,2日程ゴールデンウィークをつぶしてしまいました.面白い問題をありがとうございました.

[ソースコード1]

#include 
#include 
#include 
#include 
#define COMMON_DIGEST_FOR_OPENSSL
#include 
#define SHA1 CC_SHA1

#define COUNT 100 * pow(2,30)

#define SKIPS pow(2,24)
#define DATA_MAX pow(2,16)

int64_t data[(uint16_t)DATA_MAX];

int main(){
  printf("start\n");
  uint64_t i;
  uint32_t j;
  int k,k2,value;
  char sbuf[256];
  unsigned char digest[40];

  // init data array
  for(j = 0; j < DATA_MAX; j++){
	data[j] = 0;
  }
  printf("array init done\n");

  i = COUNT;
  printf("limit:%llu\n",i);
  for(i = 0;; i+=1){
	sprintf(sbuf,"%llu",i);
	SHA1(sbuf,strlen(sbuf),digest);
	for(k=0;k<10;k++){
	  if(COUNT <= (i*10)+k) goto done;
	  value = digest[k*2]*256 + digest[k*2+1];
	  // printf("%llu: %d\n",i*10+k,value);
	  data[value]+=1;
	}
  }
 done:
  for(j = 0; j < DATA_MAX; j++){
	if (0 < data[j]) printf("%d,%llu\n",j,data[j]);
  }
}

[ソースコード2]
#!/usr/bin/ruby

count = 100*(2**30)
skip = 2**24
sum = 0
accum_count = 0
k = 0

while l = gets
  data = l.chop.split(',')
  value = data[0].to_i
  n = data[1].to_i
  accum_count += n
 
  while k < accum_count
    sum += value
    puts k.to_s + " add:" + value.to_s
    k += skip
  end
  break if count <= k
end
puts sum

[ソースコード3]
import Data.Digest.SHA1
import Codec.Binary.UTF8.String
import Numeric
import Data.List

getdata :: Integer -> Integer
getdata index = 
  let 
    divisor = 10
    q = div index divisor
    r = mod index divisor
  in
   getdata_2 q r
   
getdata_2 :: Integer -> Integer -> Integer
getdata_2 q r =
  let str = int2hexstring (Data.Digest.SHA1.toInteger $ hash $ encode $ show q) 
      hexarray = string2hexarray str
  in
   getdata_3 hexarray r
   
getdata_3 :: [String] -> Integer -> Integer
getdata_3 s r =
  let r2 = fromIntegral(r * 2) 
      n0 = hex(s !! r2)
      n1 = hex(s !! (r2 + 1))
  in
   (n0 * 256) + n1
   
getsign :: Integer -> Int -> Integer
getsign count skips =
  let d = sort $ makedata count
  in calc_sum d skips
     
calc_sum :: [Integer] -> Int -> Integer
calc_sum d skips =
 sum $ calc_sum_filter d skips

calc_sum_filter :: [Integer] -> Int -> [Integer]
calc_sum_filter d skips =
  let x = (head d) 
  in 
   if *1 ++ str
     
genzeros:: Integer -> String
genzeros 0 = ""
genzeros n = "0" ++ (genzeros (n-1))

string2hexarray :: String -> [String]
string2hexarray (x1:x2:) = [x1:x2:]
string2hexarray (x1:x2:rest) = (x1:x2:[]):(string2hexarray rest)
                               
hex :: String -> Integer
hex s = read ("0x" ++ s) :: Integer
                            
main = do
  let 
    count = 107374182400
    -- count = 100
    skips = 16777216
    result = makedata count
  mapM_ (putStrLn . show) result

*1:length d) <= skips) then x:[] else x:calc_sum_filter (drop skips d) skips makedata :: Integer -> [Integer] makedata 1 = [ getdata 0 ] makedata n = getdata(n-1):makedata(n-1) int2hexstring :: Integer -> String int2hexstring n = let str = showHex n "" r = 40 - (Prelude.length str) in if r == 0 then str else (genzeros (fromIntegral r