2
4

More than 5 years have passed since last update.

tsvファイルの入出力を簡単にする技

Last updated at Posted at 2018-10-12

はじめに

データを扱うとき、相手がExcelだったりすること多いですよね。
Excelとの読み書きをするときには、tsv形式(タブ区切り)にすると便利です。
ただし、Excelはデフォルトでは文字コードがcp932なので気をつけましょう。

というわけで、各言語でのtsvファイルの扱いについて

方針

ヘッダを読み込み、それをキーにしてハッシュのリストにして扱うと簡単です。

出力時も、出力する項目をリストにしておけば、簡単です。

仕様変更にも柔軟に対応できます。

Python

ファイル読み込み

with open(filepath, encoding='cp932') as f:
    header = next(f)[:-1].split('\t')

    data = [dict(zip(header, l[:-1].split('\t'))) for l in f]

filepathは読み込みファイルのパスです。
dataにキーがヘッダで辞書形式の配列で格納されます。

ファイル書き込み

header = '出力したい項目/区切り'.split('/')
with open(filepath, 'w', encoding='cp932') as wf:
    wf.write('\t'.join(header) + '\n')
    for d in data:
        wf.write('\t'.join(str(d[k]) for k in header) + '\n')

filepathは読み込みファイルのパスです。

ちょっと便利な使い方

pythonの場合はformatが強力なので、こんな風に使うと便利です。

for d in data:
    print('{品目}:{単価}円'.format(**d))

説明なくても何をしているのか伝わるかと思います。

データを加工する際にも、加工した結果を辞書に追加していけばわかりやすいかと思います。

Javascript(nodejs v10)

ファイル1本にまとめてしまいます。
cp932を読み込むのは結構大変で本質的ではないので省略。
v10の追加機能として、fs.promisesを利用しています。
pythonと比較して、zipとかdictとかの便利な関数がないため、読みにくいかと思いますが。

const fs = require('fs');

// 読み込み
async function readData(filepath) {
    const text = await fs.promises.readFile(filepath, 'utf8');
    const lines = text.split('\n').map(l => l.split('\t'));
    const header = lines.shift();

    return lines.map(
        l => header.reduce(
            (a, c, i, s) => Object.assign(a, {[c]:l[i]}), {})
    );
}

// 書き込み
const writeData = (header, data, path) => fs.promises.writeFile(
    path, 
    header.join('\t') + '\n'
        + data.map(d => header.map(h => d[h]).join('\t')).join('\n') + '\n'
);

(async () => {
    const data = await readData('assets.tsv');

    var d = data[0];
    console.log(d);
    console.log(`${d.id}:${d.name}`);

    await writeData('id/name/route'.split('/'), data, 'assets_js.tsv');
})();

go編

ファイル1本にまとめてしまいます。
これもcp932を処理するのにはパッケージ追加が必要なので省略。
map系がないため、全部forで書いています。長いですね。

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "os"
    "strings"
)

func readData(path string) []map[string]string {
    fp, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fp.Close()
    reader := csv.NewReader(fp)
    reader.Comma = '\t'
    reader.LazyQuotes = true
    header, err := reader.Read()
    if err != nil {
        panic(err)
    }
    data := []map[string]string{}
    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        d := map[string]string{}
        for i, h := range header {
            d[h] = record[i]
        }
        data = append(data, d)
    }

    return data
}

func writeData(header []string, data []map[string]string, path string) {
    fp, err := os.Create(path)
    if err != nil {
        panic(err)
    }
    defer fp.Close()
    fp.Write(([]byte)(strings.Join(header, "\t") + "\n"))
    for _, d := range data {
        outdata := []string{}
        for _, h := range header {
            outdata = append(outdata, d[h])
        }
        fp.Write(([]byte)(strings.Join(outdata, "\t") + "\n"))
    }
}

func main() {
    data := readData("assets.tsv")
    fmt.Println(data[0])
    writeData(strings.Split("id/name/route", "/"), data, "assets_go.tsv")
}

余談その1

javascriptとgoの読み込み用ファイルassets.tsvはjqで作成しました。
jqはコマンドラインでjsonを扱える便利なツールです。
元データは、仮想通貨のapiから作成しました。

curl https://api.cryptowat.ch/assets | jq -r  '.result|((.[0]|keys_unsorted),map([.[]])[]|@tsv)' > assets.tsv

余談その2

tsv形式って言い方はあんまり一般的ではないみたいです。
Excelでも、タブ区切り出力は.txtになります。

csv形式も、カンマ区切りと思っていましたが、character-separated valuesの意味もあるそうです。

まあ、伝わる人には、区切り記号がはっきりしやすいように、「tsv形式です!」というほうがいいと思います。

2
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4