challenge タブ区切りデータの処理

タブ区切りのデータを読み込んで操作をし書き出す方法を教えてください。 読み込み・書き出しの方法は任意とします。

与えられるデータは:

  • レコードの区切りは改行、カラムの区切りはタブです。
  • 最初のレコードはヘッダで、カラムの名前が書いてあります。
  • それ以降はデータで、第1,4カラムは整数値、第2,3カラムは文字列値です。

この入力データに対して以下の操作をしたものを書き出してください:

  • 第1カラムの値でデータを昇順にソートする。
  • 第2カラムと第3カラムをヘッダを含めて入れ替える。
  • 第4カラムの値にそれぞれ1を加える。

入力の例:

ID      Surname Forename        Age
1       Sato    Hanako  17
0       Suzuki  Taro    18
...

出力の例:

ID      Forename        Surname Age
0       Taro    Suzuki  19
1       Hanako  Sato    18
...

Posted feedbacks - Nested

Flatten Hidden
こんなんでいいのかなぁ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#text = open("data.txt").read()
text = """ID    Surname    Forename    Age
1    Sato    Hanako    17
0    Suzuki    Taro    18
3    Ozawa    Ichiro    66
2    Asoh    Taro    68
"""

records = [l.split('\t') for l in text.split('\n')][:-1]
header = records.pop(0)

records.sort(key=lambda r: int(r[0]))
for r in [header] + records:
    r[1],r[2] = r[2],r[1]
for r in records:
    r[3] = "%d" % (int(r[3]) + 1)

for r in [header] + records:
    print '\t'.join(r)
標準入力から読込
標準出力への出力
1
2
3
4
5
@header = split(/\t/, <>);
($header[2], $header[1]) = ($header[1], $header[2]);
print join("\t", @header);

print map { ($_->[2], $_->[1])=($_->[1], $_->[2]);$_->[3]++; join("\t", @$_)."\n"; } sort{ $a->[0] <=> $b->[0] } map [split /\t/], <> ;

sortを使いawkを使わない解。bashとdashで動作確認。

1、3、4、5行目にはタブ文字が含まれています。

1
2
3
4
5
6
IFS='    '
read -r id forename surname age
echo "$id    $surname    $forename    $age"
sort -n -t '    ' | while read -r id forename surname age;do
    echo "$id    $surname    $forename    $((age + 1))"
done

項目の入れ替えが行われていない

↑(#7736)は、勘違い すみませんでした。
標準入力から標準出力へ

$ runghc tsv.hs < tsv.data
ID      Forename        Surname Age
0       Taro    Suzuki  19
1       Hanako  Sato    18
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Control.Arrow
import Data.Function
import Data.List

main :: IO ()
main = putStr . unlines . map (concat . intersperse "\t") . uncurry (++) 
     . second (map inccol4 . sortBy (compare `on` readInt . head)) 
     . splitAt 1 . map (swap23 . words) 
     . filter (not . null) . lines =<< getContents
  where swap23 (x:y:z:ws) = x:z:y:ws
        inccol4 (x:y:z:[w]) = x:y:z:[show (readInt w+1)]
        readInt :: String -> Int
        readInt = read

効率すこし改善版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import Control.Arrow
import Data.Function
import Data.List

main :: IO ()
main = mapM_ (putStrLn . concat . intersperse "\t") . uncurry (++) 
     . (map (swap23 . words) *** sortBy (compare `on` readInt . head) . map (swap23inccol4 . words)) 
     . splitAt 1 . filter (not . null) . lines =<< getContents
  where swap23 (x:y:z:ws) = x:z:y:ws
        swap23inccol4 (x:y:z:[w]) = x:z:y:[show (readInt w+1)]
        readInt :: String -> Int
        readInt = read

統計処理言語だけあって、Rでは、この手のデータ処理は非常に直感的です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# タブ区切りのデータを読み込む
d <- read.delim("input.tsv")

# 第1カラムの値でデータを昇順にソートする。
d <- d[sort.list(d[,1]),]

# 第2カラムと第3カラムをヘッダを含めて入れ替える。
d[,c(2,3)] <- d[,c(3,2)]
colnames(d)[c(2,3)] <- colnames(d)[c(3,2)]

# 第4カラムの値にそれぞれ1を加える。
d[,4] <- d[,4] + 1

# 書き出す
write.table(d, "output.tsv", sep="\t", quote=F, row.names=F)
何とか。
しかし何か美しくない...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>

namespace
{

class Row
{
  std::vector<std::string> data_;

  friend std::istream& operator>>(std::istream&, Row&);
  friend std::ostream& operator<<(std::ostream&, const Row&);

  public:
  std::size_t find(const std::string& key) const; 

  std::string& operator[](std::size_t i)
  { return data_.at(i); }
  const std::string& operator[](std::size_t i) const
  { return data_.at(i); }

  bool empty() const
  { return data_.empty() || data_.size() == 1 && data_[0] == ""; }
};

std::istream&
operator>>(std::istream& is, Row& r_)
{
  Row r;
  std::string line;
  std::getline(is, line);
  
  std::string::size_type i, o = 0; 
  while ((i = line.find("\t", o)) != std::string::npos)
  {
    r.data_.push_back(line.substr(o, i - o));
    o = i+1;
  }
  r.data_.push_back(line.substr(o));
  std::swap(r, r_);
  return is;
}

std::ostream&
operator<<(std::ostream& os, const Row& r)
{
  std::copy(r.data_.begin(), r.data_.end(), std::ostream_iterator<std::string>(os, "\t"));
  os << "\n";
  return os;
}

std::size_t
Row::find(const std::string& key)
const
{
  std::vector<std::string>::const_iterator it =
    std::find(data_.begin(), data_.end(), key);
  if ( it == data_.end() )
    throw "not found";
  return std::distance(data_.begin(), it);
}

typedef Row Header;
class Table
{
  Header header_;
  std::vector<Row> rows_;

  friend std::istream& operator>>(std::istream&, Table&);
  friend std::ostream& operator<<(std::ostream&, const Table&);

  public:
    void sort_by(std::size_t idx);
    void swap_columns(std::size_t col1, std::size_t col2);
    template <typename Filter>
      void filter(std::size_t idx, Filter f);

  private:
    class comparator
      : public std::binary_function<bool, Row, Row>
    {
      std::size_t col_;
      public:
      comparator(std::size_t col) : col_(col) {}

      bool operator()(const Row& r1, const Row& r2) const
      { return r1[col_] < r2[col_]; }
    };
};

std::istream&
operator>>(std::istream& is, Table& t)
{
  is >> t.header_;

  std::istream_iterator<Row> b(is),e;
  std::copy(b, e, std::back_inserter(t.rows_));
  if (t.rows_.back().empty())
    t.rows_.erase(t.rows_.end() - 1);

  return is;
}

std::ostream&
operator<<(std::ostream& os, const Table& t)
{
  os << t.header_;
  std::copy(t.rows_.begin(), t.rows_.end(), std::ostream_iterator<Row>(os, ""));
  return os;
}

void 
Table::sort_by(std::size_t idx)
{
  comparator comp(idx);
  std::sort(rows_.begin(), rows_.end(), comp);
}

void
Table::swap_columns(std::size_t col1, std::size_t col2)
{
  if ( col1 == col2 ) return;
  std::swap(header_[col1], header_[col2]);
  std::vector<Row>::iterator it = rows_.begin();
  for ( ; it != rows_.end(); ++it )
    std::swap((*it)[col1], (*it)[col2]);
}

template <typename Filter>
void
Table::filter(std::size_t idx, Filter f)
{
  std::vector<Row>::iterator it = rows_.begin();
  for ( ; it != rows_.end(); ++it )
  {
    std::stringstream ss((*it)[idx]);
    typename Filter::result_type v;
    ss >> v;
    v = f(v);
    ss.clear();
    ss << v;
    (*it)[idx] = ss.str();
  }
}

int filter(int i)
{ return i+1; }

} // namespace

int main()
{
  std::istringstream ss(
      "ID\tSurname\tForename\tAge\n"
      "1\tSato\tHanako\t17\n"
      "0\tSuzuki\tTaro\t18\n"
      );

  Table t;
  ss >> t;
  std::cout << t << "\n=====\n";

  t.sort_by(0);
  t.swap_columns(1,2);
  t.filter(3, std::ptr_fun(filter));

  std::cout << t << "\n";
  return 0;
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


public class Sample {

    private List<String> header;

    private List<List<String>> data;

    public Sample(String str) {
        boolean isFirst = true;
        String[] lines = str.split("\n");
        data = new ArrayList<List<String>>(lines.length - 1);
        for (String line : str.split("\n")) {
            List<String> row = Arrays.asList(line.split("\t"));
            if (isFirst && !(isFirst = false)) {
                header = row;
            } else {
                data.add(row);
            }
        }
    }

    public Sample sort(final int column) {
        Collections.sort(data, new Comparator<List<String>>() {
            public int compare(List<String> l, List<String> r) {
                return r.get(column).compareTo(l.get(column));
            }
        });
        return this;
    }

    public Sample swapColumn(final int column1, final int column2) {
        List<List<String>> list = new ArrayList<List<String>>(data);
        list.add(0, header);
        forAll(list, new Closure() {
            public void each(List<String> row) {
                row.set(column1, row.set(column2, row.get(column1)));
            }
        });
        return this;
    }

    public Sample addValue(final int column, final int add) {
        forAll(new Closure() {
            public void each(List<String> row) {
                row.set(column,
                    Integer.toString(Integer.parseInt(row.get(column)) + add)); 
            }
        });
        return this;
    }

    private void forAll(Closure c) {
        forAll(data, c);
    }

    private void forAll(List<List<String>> list, Closure c) {
        for (List<String> row : list) {
            c.each(row);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(join(header)).append("\n");
        for (List<String> row : data) {
            builder.append(join(row)).append("\n");
        }
        return builder.toString();
    }

    private String join(List<String> list) {
        StringBuilder builder = new StringBuilder();
        boolean isFirst = true;
        for (String str : list) {
            if (!isFirst) {
                builder.append("\t");
            } else {
                isFirst = false;
            }
            builder.append(str);
        }
        return builder.toString();
    }

    private static interface Closure {
        public void each(List<String> row);
    }

    public static void main(String[] args) {
        String str =
            "ID\tSurname\tForename\tAge\n" +
            "1\tSato\tHanako\t17\n" +
            "0\tSuzuki\tTaro\t18\n";
        System.out.println(
            new Sample(str).sort(1).swapColumn(1, 2).addValue(3, 1));
    }
}

Squeak Smalltalk で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| data cr numRows numCols table rIdx labels rows |
data := 'ID    Surname    Forename    Age
1    Sato    Hanako    17
0    Suzuki    Taro    18
3    Ozawa    Ichiro    66
2    Asoh    Taro    68'.

cr := Character cr.
numRows := (data occurrencesOf: cr) + 1.
numCols := ((data upTo: cr) occurrencesOf: Character tab) + 1.
table := Matrix rows: numRows columns: numCols.
rIdx := 0.
data linesDo: [:line |
    | cIdx |
    rIdx := rIdx + 1.
    cIdx := 0.
    line tabDelimitedFieldsDo: [:each | table at: rIdx at: (cIdx := cIdx + 1) put: each]].

labels := table atRow: 1.
table atColumn: 4 put: (table atColumn: 4) + 1.
table atRow: 1 put: labels.
table swapColumn: 2 withColumn: 3.
labels := table atRow: 1.
rows := (2 to: table rowCount) collect: [:idx | table atRow: idx].
rows sort: [:a :b | a first < b first].

World findATranscript: nil.
{labels}, rows do: [:each |
    Transcript cr.
    each do: [:elem | Transcript show: elem] separatedBy: [Transcript tab]]

"=>
ID  Forename  Surname  Age
0   Taro      Suzuki   19
1   Hanako    Sato     18
2   Taro      Asoh     69
3   Ichiro    Ozawa    67  "

Python Code Reading 4に出てきたmetaclassをつかってみました。

http://coreblog.org/ats/stuff/python-codereading/event-04

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def readheader(line):
  class _Meta(type):
    def __init__(cls, name, bases, dct):
      cls._attrnames = tuple(line.split('\t'))

  class TSVObject(object):
    __metaclass__ = _Meta
    def __init__(self, line):
      attrvalues = tuple(line.split('\t'))
      self.__dict__.update(dict(zip(self._attrnames, attrvalues)))
    def __str__(self):
      return '<TSVObject %s>'%(','.join(['%s=%s'%(key, value) for key, value in self.__dict__.items()]),)
    __repr__ = __str__

  return TSVObject

def parse(lines):
  klass = None
  L = list()
  for line in lines:
    if klass is None:
      klass = readheader(line)
      continue
    L.append(klass(line))
  return L

data = (
  '''ID\tSurname\tForename\tAge\n'''
  '''1\tSato\tHanako\t17\n'''
  '''0\tSuzuki\tTaro\t18\n'''
  )

lines = data.splitlines()
L = parse(lines)
def cmp(a, b):
  a = int(a.ID), int(b.ID)
  if a > b:
    return -1
  elif b < a:
    return 1
  return 0
L.sort(cmp)

print '%s\t%s\t%s\t%s'%('ID', 'Forename', 'Surname', 'Age')
for item in L:
  print '%s\t%s\t%s\t%s'%(item.ID, item.Forename, item.Surname, int(item.Age)+1)
なんだか書き捨てって感じになってしまいました(^^;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(defpackage :doukaku-209 (:use :cl :split-sequence))
(in-package :doukaku-209)

(defun parse-integer-or-never (string &key (after #'values))
  (let ((num (parse-integer string :junk-allowed 'T)))
    (if num (funcall after num) string)))

(with-open-file (in "doukaku-209.data")
  (with-open-file (out "doukaku-209.out" :direction :output :if-exists :supersede)
    (format out "~{~{~A~^    ~}~%~}"
            (destructuring-bind (title &rest data)          
                (loop :for (id sur fore age) :=  (split-sequence #\Tab (read-line in nil nil)) 
                      :while (and id sur fore age)
                      :collect (list (parse-integer-or-never id) 
                                     fore 
                                     sur 
                                     (parse-integer-or-never age :after #'1+)))
              `(,title ,@(sort data #'< :key #'first))))))
オブジェクト指向っぽく書いてみたつもりです…
(with-open-file (in "doukaku-209.data")
  (update-file (make-instance 'doukaku-209) in *standard-output*))
;>>>
ID Forename Surname Age
0 Taro Suzuki 19
1 Hanako Sato 18
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
(defpackage :doukaku-209 (:use :cl :split-sequence))
(in-package :doukaku-209)

(defclass file-op () ())

(defgeneric file-to-data (file-op stream))
(defgeneric swap-colum (file-op data))
(defgeneric sort-data (file-op data))
(defgeneric update-datum (file-op data))
(defgeneric format-out-data (file-op stream data))
(defgeneric update-file (file-op in-stream out-stream))

(defmethod update-file ((op file-op) (in stream) (out stream))
  (format-out-data op out
    (swap-colum op 
      (destructuring-bind (title &rest data) (file-to-data op in)
        `(,title ,@(sort-data op (loop :for line :in data 
                                       :collect (update-datum op line))))))))
                     
(defclass doukaku-209 (file-op) ())

(defmethod file-to-data ((op doukaku-209) (in stream))
  (loop :for line := (read-line in nil nil) :while line
        :collect (split-sequence #\Tab line)))

(defmethod swap-colum ((op doukaku-209) (data list))
  (loop :for xx :in (copy-list data) 
        :do (rotatef (nth 1 xx) (nth 2 xx)) 
        :collect xx))

(defmethod sort-data ((op doukaku-209) (data list))
  (sort (copy-list data) #'< :key #'first))

(defmethod format-out-data ((op doukaku-209) (out stream) (data list))
  (format out "~{~{~A~^    ~}~%~}" data))

(defmethod update-datum ((op doukaku-209) (row list))
  (destructuring-bind (id sur fore age) row
    (list (parse-integer id)
          sur
          fore
          (1+ (parse-integer age)))))
いまいちすっきりと書けません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Collections.Generic;
using System.Text;
using VB_IO = Microsoft.VisualBasic.FileIO;

class Program {
    static void Main(string[] args) {
        //テーブルの準備
        List<string> headers = new List<string>();
        List<List<string>> table = new List<List<string>>();

        //TSVの読み込みにはMicrosoft.VisualBasic.FileIOTextFieldParserクラスが便利。CSVにも使えるよ。
        //Microsoft.VisualBasicを参照に加えてね。
        using(VB_IO.TextFieldParser tfp = new VB_IO.TextFieldParser(args[0], Encoding.GetEncoding("shift-jis"))) {
            tfp.SetDelimiters("\t");//区切り記号はタブ

            //通常のテキストファイルと同じ感覚で扱えます。
            headers.AddRange(tfp.ReadFields());
            while(!tfp.EndOfData) {
                table.Add(new List<string>(tfp.ReadFields()));
            }

            tfp.Close();
        }

        //第1カラムの値でデータを昇順にソートする。
        table.Sort((a, b) => (int.Parse(a[0]) - int.Parse(b[0])));//何回見ても書いてもラムダ式きもい。

        //第2カラムと第3カラムをヘッダを含めて入れ替える。
        headers.Reverse(1, 2);
        foreach(List<string> row in table){
            row.Reverse(1, 2);
            //第4カラムの値にそれぞれ1を加える。
            row[3] = (int.Parse(row[3]) + 1).ToString();
        }

        //出力
        foreach(string header in headers){
            Console.Write(header + "\t");
        }
        Console.WriteLine();
        foreach(List<string> row in table) {
            foreach(string cell in row) {
                Console.Write(cell + "\t");
            }
            Console.WriteLine();
        }
    }
}

なぜか、ここまで Ruby が無かったので投稿。

こういうデータを扱うための、CSV というそのものズバリのクラスがあります。

データは標準入力から読み込み。

# もう少しすっきりと書けそう。

1
2
3
4
5
6
7
8
9
require 'csv'

rows = CSV.parse(STDIN.read, "\t")
rows[1], rows[2] = rows[2], rows[1]
result = []
result << rows.shift
rows.sort_by{ |r| r[0] }.
  inject(result) { |a, b| b[3] = b[3].to_i + 1; a << b }.
  each { |r| puts r.join("\t") }
1. ファイルの終わりまで繰り返す
1.1. 1行を読む
1.2. \tでsplitして*r=vector<string>に格納
1.3. swapで入れ替え
1.4. rをvector< vector<string> >に格納
2.1. 並び替え
2.2. \tをはさみながらjoinして表示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/foreach.hpp>
using namespace std;
using namespace boost::algorithm;
typedef vector<string> row;

bool comp(row* lhs, row* rhs) {
    return atoi((*lhs)[0].c_str()) < atoi((*rhs)[0].c_str());
}

int main()
{
    vector<row*> all;
    string line;
    while (getline(cin,line)) {
        row* r=new row();
        split(*r,line,is_any_of("\t"));
        swap((*r)[1],(*r)[2]);
        all.push_back(r);
    }
    sort(all.begin()+1,all.end(),comp);
    BOOST_FOREACH(row* r, all) cout<<join(*r,"\t")<<endl;
}

「第4カラムの値にそれぞれ1を加える」が抜けてますよー

ご指摘ありがとうございます。修正しました。

1. ファイルの終わりまで繰り返す
1.1. 1行を読む
1.2. \tでsplitして*r=vector<string>に格納
1.3. swapで入れ替え
1.4. 1を加える
1.5. rをvector< vector<string> >に格納
2.1. 並び替え
2.2. \tをはさみながらjoinして表示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
using namespace std;
using namespace boost;
using namespace boost::algorithm;
typedef vector<string> row;

bool comp(row* lhs, row* rhs) {
    return lexical_cast<int>((*lhs)[0]) < lexical_cast<int>((*rhs)[0]);
}

int main()
{
    vector<row*> all;
    string line;
    while (getline(cin,line)) {
        row* r=new row;
        split(*r,line,is_any_of("\t"));
        swap((*r)[1],(*r)[2]);
        try { (*r)[3]=lexical_cast<string>(lexical_cast<int>((*r)[3])+1); }
        catch (const bad_lexical_cast&) {}
        all.push_back(r);
    }
    sort(all.begin()+1,all.end(),comp);
    BOOST_FOREACH(row* r, all) cout<<join(*r,"\t")<<endl;
}

あまりScalaらしくありませんが...。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
object Tsv {
  
  def tsv(text:String) : Unit = {
    import scala.io.Source
    val lines = Source.fromString(text).getLines
    print(lines.next)
    val data = lines.map({s:String => s.split("\t")})
    val lst = List.fromIterator(data).sort({(a:Array[String], b:Array[String]) => Integer.parseInt(a(0)) < Integer.parseInt(b(0))})
    for (line <- lst) {print(line(0) + "\t" + line(1) + "\t" + line(2) + "\t" + line(3))}
  }
  
  def main(args : Array[String]) : Unit = {
    val testData = "ID\tSurname\tForename\tAge\n1\tSato\tHanako\t17\n0\tSuzuki\tTaro\t18\n"
    tsv(testData)
  }
}

ファイルから読んでファイルへ書き出します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
using System.IO;
using System.Linq;

class Program
{
    static void Main()
    {
        var header =
            from line in File.ReadAllLines("inFile.txt").Take(1)
            let x = line.Split('\t')
            select x[0] + '\t' + x[2] + '\t' + x[1] + '\t' + x[3];

        var lines =
            from line in File.ReadAllLines("inFile.txt").Skip(1)
            let x = line.Split('\t')
            orderby x[0]
            select x[0] + '\t' + x[2] + '\t' + x[1] + '\t' + (int.Parse(x[3]) + 1);

        File.WriteAllLines("outFile.txt", header.Concat(lines).ToArray());
    }
}

16行目を修正。

文字列型でソートしてました。

1

10 <

2

3

4

5

6

7

8

9

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
using System.IO;
using System.Linq;

class Program
{
    static void Main()
    {
        var header =
            from line in File.ReadAllLines("inFile.txt").Take(1)
            let x = line.Split('\t')
            select x[0] + '\t' + x[2] + '\t' + x[1] + '\t' + x[3];

        var lines =
            from line in File.ReadAllLines("inFile.txt").Skip(1)
            let x = line.Split('\t')
            orderby int.Parse(x[0])
            select x[0] + '\t' + x[2] + '\t' + x[1] + '\t' + (int.Parse(x[3]) + 1);

        File.WriteAllLines("outFile.txt", header.Concat(lines).ToArray());
    }
}
   ]d=.;:;._2 data            レコードを改行で、カラムをタブで区切りboxに入れる。
+--+-------+--------+---+
|ID|Surname|Forename|Age|
+--+-------+--------+---+
|1 |Sato   |Hanako  |17 |
+--+-------+--------+---+
|0 |Suzuki |Taro    |18 |
+--+-------+--------+---+

   ]h=.0{d                    1行目をヘッダに。J言語ではリストやテーブルの
+--+-------+--------+---+     インデックスは0-オリジン。
|ID|Surname|Forename|Age|
+--+-------+--------+---+

   ]d=.}.d                    2行目からをデータに。
+-+------+------+--+
|1|Sato  |Hanako|17|
+-+------+------+--+
|0|Suzuki|Taro  |18|
+-+------+------+--+

   ]d=. d /: 0{"1 d           第1カラムの値でデータを昇順にソートする。
+-+------+------+--+
|0|Suzuki|Taro  |18|
+-+------+------+--+
|1|Sato  |Hanako|17|
+-+------+------+--+

   ]h=.0 2 1 3{ h           ヘッダの第2カラムと第3カラムを入れ替える。
+--+--------+-------+---+
|ID|Forename|Surname|Age|
+--+--------+-------+---+

   ]d=. 0 2 1 3 {"1 d         データの第2カラムと第3カラムを入れ替える。
+-+------+------+--+
|0|Taro  |Suzuki|18|
+-+------+------+--+
|1|Hanako|Sato  |17|
+-+------+------+--+

   ]d=.(":&>:&".&.>3{"1 d) 3}"0 1 d      第4カラムの値にそれぞれ1を加える。
+-+------+------+--+                     数値に変換して加算した後、文字列に戻す。
|0|Taro  |Suzuki|19|
+-+------+------+--+
|1|Hanako|Sato  |18|
+-+------+------+--+

   ]d=.h,d                       ヘッダとデータを連結。
+--+--------+--------+---+
|ID|Surname |Forename|Age|
+--+--------+--------+---+
|0 |Taro    |Suzuki  |19 |
+--+--------+--------+---+
|1 |Hanako  |Sato    |18 |
+--+--------+--------+---+

  ]d=.(,TAB&;)/"1 d       カラムを区切るタブを挿入。
+--+-+--------+-+-------+-+---+
|ID| |Forename| |Surname| |Age|
+--+-+--------+-+-------+-+---+
|0 | |Taro    | |Suzuki | |19 |
+--+-+--------+-+-------+-+---+
|1 | |Hanako  | |Sato   | |18 |
+--+-+--------+-+-------+-+---+
                 最後にboxから取り出して出力。
1
2
3
4
5
6
7
8
9
data =. ;: ;._2 stdin''
header =. 0{data
data =. }.data
data =. data /: 0{"1 data
header =. 0 2 1 3{ header
data =. 0 2 1 3{"1 data
data =. (":&>:&".&.>3{"1 data) 3}"0 1 data
data =. header , data
wd&;"1 (, TAB&;)/"1 data

書き捨てっぽく。引き数でファイル名を取って標準出力へ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(use srfi-1)
(use text.csv)

(define (main args)
  (receive (header body) (call-with-input-file (cadr args)
                           (lambda (port)
                             (car+cdr (port->list (make-csv-reader #\tab)
                                                  port))))
    (for-each
     (cute (make-csv-writer #\tab) (current-output-port) <>)
     (cons (list (first header) (third header) (second header) (fourth header))
           (map (cut map x->string <>)
                (sort
                 (map (lambda (vs)
                        (list (string->number (first vs))
                              (third vs)
                              (second vs)
                              (+ (string->number (fourth vs)) 1)))
                      body)
                 (lambda (v1 v2) (< (first v1) (first v2)))))))))
バッチで。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@echo off
  setlocal enabledelayedexpansion
    set a=0
    set d=0
    set l=0
    set z=
    
    if "%1" == "" (echo %~n0 [FILE] & exit /b 1)
    
    for /f "tokens=1-4" %%a in (%1) do (
      echo %%a    %%c    %%b    %%d
      goto _BREAK_
    )
    :_BREAK_
    
    for /f "tokens=1" %%a in ('more +1 %1') do (
      if %%a gtr !a! set a=%%a
    )
    
    call :length %a% l
    for /l %%i in (1,1,%l%) do set z=!z!0
    
    for /f "tokens=1-4" %%a in ('more +1 %1') do (
      set a=%z%%%a
      set /a d=%%d+1
      set %~n0_!a:~-%l%!=%%a    %%c    %%b    !d!
    )
    
    for /f "tokens=2 delims==" %%l in ('set %~n0_') do echo %%l
  endlocal
goto :EOF

:length
  setlocal
    set i=0
    set t=%~1
    
    if "%t%" == "" endlocal & set %2=0
    
    :_LENGTH_
      set t=%t:~1%
      set /a i+=1
    if not "%t%" == "" goto _LENGTH_
  endlocal & set %2=%i%
goto :EOF
実行方法:
$ cat hoge.txt
ID	Surname	Forename	Age
1	Sato	Hanako	17
0	Suzuki	Taro	18

$ erlc doukaku7723.erl
$ erl -noshell -s doukaku7723 main hoge.txt -s init stop
ID	Forename	Surname	Age
0	Taro	Suzuki	19
1	Hanako	Sato	18
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-module(doukaku7723).
-export([main/1]).

main(Filename) ->
    {ok, Bin} = file:read_file(Filename),
    A1 = string:tokens(binary_to_list(Bin), "\r\n"),
    [H | T] = lists:map(curry(flip(fun string:tokens/2), "\t"), A1),
    A2 = lists:map(fun swap23inc4/1, T),
    A3 = lists:sort(fun([E1|_], [E2|_]) -> list_to_integer(E1) < list_to_integer(E2) end, A2),
    A4 = lists:map(func_comp(fun lists:concat/1, curry(fun intersperse/2, "\t")), [swap23(H) | A3]),
    A5 = lists:concat(intersperse("\r\n", A4)),
    io:format("~s~n", [A5]).

swap23([A,B,C,D | Rest]) -> [A,C,B,D|Rest].
swap23inc4([A,B,C,D | Rest]) -> [A,C,B,integer_to_list(1+list_to_integer(D)) | Rest].

intersperse(_, []) -> [];
intersperse(_, [X]) -> [X];
intersperse(Sep, [X | XS]) -> [X, Sep | intersperse(Sep, XS)].
curry(F, A) -> fun(B) -> F(A, B) end.
flip(F) -> fun(A, B) -> F(B, A) end.
func_comp(F, G) -> fun(X) -> F(G(X)) end.

ほとんど #7724 さんと同じですね・・・でも書いちゃったので投稿。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def convert(input, output):
    # read from file
    datas = [line.rstrip('\n').split('\t') for line in open(input)]
    # swap column2 and column3 (with header)
    for data in datas:
        data[1], data[2] = data[2], data[1]
    # remove header
    header = datas.pop(0)
    # sort by column1
    datas.sort(key=lambda data: int(data[0]))
    # add 1 to column4
    for data in datas:
        data[3] = str(int(data[3]) + 1)
    # restore header
    datas.insert(0, header)
    # write to file
    io = open(output, "w")
    for data in datas:
        io.write('\t'.join(data) + '\n')

if __name__ == '__main__':
    convert("input.txt", "output.txt")
data は複数形なので datas はちょっと変かも。。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let succ_str s =
  try (string_of_int (int_of_string s + 1))
  with Failure _ -> s

let read scanbuf f =
  let t = "\t"  and nl = "\n"  in
  Scanf.bscanf scanbuf "%[^\t]\t%[^\t]\t%[^\t]\t%[^\n]%c"
  (fun a b c d _ -> f [a;t;c;t;b;t; succ_str d; nl])  

let f ch =
  let accu = ref [] in
  try
    read ch (List.iter print_string);
    while true do
      (read ch (fun l -> accu := l::!accu))
    done;
  with End_of_file ->
    let l = List.stable_sort (fun a b ->
      compare (List.hd a) (List.hd b)) !accu
    in List.iter (List.iter print_string) l;;
(*  
let s = "\
ID\tSurname\tForename\tAge
1\tSato\tHanako\t17
0\tSuzuki\tTaro\t18
"
let (path,o) = 
  Filename.open_temp_file "doukaku209_" ".txt";;
output_string o s;  close_out o;;

let sbuf = Scanf.Scanning.from_file path in (f sbuf);;
*)
最近Rubyを始めました。 どうでしょうか?
1
2
3
4
5
6
7
8
9
lines = ARGF.readlines

csvs = lines.map {|line| line.split("\t")}
head = csvs.shift
print [head[0], head[2], head[1], head[3]].join("\t")

csvs.sort {|csv1, csv2| csv1[0] <=> csv2[0] }.each do |csv|
  puts [csv[0], csv[2], csv[1], csv[3].to_i + 1].join("\t")
end
5行目はprintでなくputsじゃないですか。
あと7行目、sortでなくsort_by{|csv| csv[0]}のがよくないですか。
(でもsort_byって昔なかったんですっけ)
import java.io.*;
import java.util.*;

class Table {
	
	public static void main( String[] args ) {
		Table table = new Table()
			.input( "in.tsv" )
			.sort( 0 )
			.replace( 1, 2 )
			.increment( 3 )
			.output( "out.tsv" );
	}
	
	Cell[][] cells;
	
	class Cell {
		String value;
		Cell( String value ) {
			this.value = value;
		}
		public String toString() { return value; }
		public int compareTo( Cell that ) { return this.value.compareTo( that.value ); }
		public void increment() { value = ( Integer.parseInt( value ) + 1 ) + ""; }
	}
	
	class Sorter implements Comparable {
		Cell key;
		Cell[] row;
		Sorter( Cell key, Cell[] row ) {
			this.key = key;
			this.row = row;
		}
		public int compareTo( Object that ) {
			return this.key.compareTo( ( (Sorter) that ).key );
		}
	}
	
	Table input( String file ) {
		try {
			BufferedReader in = new BufferedReader( new FileReader( file ) );
			List<Cell[]> list = new ArrayList<Cell[]>();
			String line;
			while ( ( line = in.readLine() ) != null ) {
				list.add( makeCells( line.split( "\t" ) ) );
			}
			in.close();
			cells = list.toArray( new Cell[0][] );
			return this;
		} catch ( IOException e ) { throw new RuntimeException ( e ); }
	}
	
	Table sort( int ic ) {
		Cell[] column = column( ic );
		Sorter[] sorters = new Sorter[rows()-1];
		for ( int ir = 1; ir < rows(); ++ir ) {
			sorters[ir-1] = new Sorter( column[ir], row( ir ) );
		}
		Arrays.sort( sorters );
		for ( int ir = 1; ir < rows(); ++ir ) {
			cells[ir] = sorters[ir-1].row;
		}
		return this;
	}
	
	Table replace( int ic1, int ic2 ) {
		Cell[] column1 = column( ic1 );
		Cell[] column2 = column( ic2 );
		set( ic1, column2 );
		set( ic2, column1 );
		return this;
	}
	
	Table increment( int ic ) {
		Cell[] column = column( ic );
		for ( int ir = 1; ir < rows(); ++ir ) {
			column[ir].increment();
		}
		return this;
	}
	
	Table output( String file ) {
		try {
			PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter( file ) ) );
			out.print( toString() );
			out.flush();
			out.close();
			return this;
		} catch ( IOException e ) { throw new RuntimeException( e ); }
	}

	Cell[] makeCells( String[] array ) {
		Cell[] cells = new Cell[array.length];
		for ( int i = 0; i < array.length; ++i ) {
			cells[i] = new Cell( array[i] );
		}
		return cells;
	}
	
	void set( int ic, Cell[] column ) {
		for ( int ir = 0; ir < rows(); ++ir ) {
			cells[ir][ic] = column[ir];
		}
	}
	
	Cell[] row( int ir ) {
		Cell[] bak = new Cell[columns()];
		for ( int ic = 0; ic < columns(); ++ic ) {
			bak[ic] = cells[ir][ic];
		}
		return bak;
	}
	
	Cell[] column( int ic ) {
		Cell[] bak = new Cell[rows()];
		for ( int ir = 0; ir < rows(); ++ir ) {
			bak[ir] = cells[ir][ic];
		}
		return bak;
	}
	
	int rows() { return cells.length; }
	
	int columns() { return cells[0].length; }
	
	public String toString() {
		StringBuilder bak = new StringBuilder();
		for ( int ir = 0; ir < cells.length; ++ir ) {
			for ( int ic = 0; ic < cells[0].length; ++ic ) {
				bak.append( ic > 0 ? "\t"
						: "" ).append( cells[ir][ic].toString() );
			}
			bak.append( "\r\n" );
		}
		return bak.toString();
	}
}

なでしこで、手順に忠実に書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
input="ID    Surname    Forename    Age
1    Sato    Hanako    17
0    Suzuki    Taro    18"

data=inputをTSV取得
# ヘッダを切り取る
head=dataの0を配列切り取る

# 第1カラムでソート
dataの0を表数値ソート
# 第2カラムと第3カラムを入れ替え
data=dataの1と2を表列入替
# ヘッダも入れ替え
head=headの1と2を配列入替
# 第4カラムに1を加算
Iで0から(dataの配列要素数-1)まで繰り返す
    data[I][3]=data[I][3]+1
# ヘッダとデータを結合
output=(headの表行列交換)にdataを表追加
# 結果を表示
outputを表TSV変換して表示

●配列入替(AのXとYを)
    AのXにA[Y]を配列挿入
    AのY+1を配列削除
    Aで戻る

●表列入替(AのXとYを)
    AのYを表列取得
    AのXにそれを表列挿入
    それのY+1を表列削除
    それで戻る

●表追加(AにBを)
    Aの(Aの配列要素数)にBを配列一括挿入
    Aで戻る
一行Pythonで190 bytes.
標準入力から標準出力へ。

横に広がって見づらいので
適当に改行を入れました。
1
2
3
import sys;T,N='\t\n';I=int;R=[l[:-1].split(T)for l in sys.stdin];A,B,C,D=R[0];
print N.join(map(T.join,[(A,C,B,D)]+[(a,c,b,str(I(d)+1))for a,b,c,d in sorted(
R[1:],None,lambda x:I(x[0]))])),N
python初心者ですが...
ちなみにpython2.3では動かないはず。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import sys
def reorderdOutput(line, headerFlag):
    line = line.strip()
    columns = line.split("\t")
    columns[1], columns[2] = columns[2], columns[1]
    if headerFlag == 0:
        columns[3] = str(int(columns[3]) + 1)
    print "\t".join(columns)
## main
inFile = sys.argv[1]
f = open(inFile)
## header line treatment
line = f.readline()
reorderdOutput(line, 1)
## data section treatment
for line in sorted(f):
    reorderdOutput(line, 0)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def text = new File("test.csv").text

def lines = text.split("\n")

def titles = lines[0].split("\t")
def records = lines[1..-1].collect{
    it.split("\t")
}

// 第1カラムの値でデータを昇順にソートする。
records.sort{ it[0] }

// 第2カラムと第3カラムをヘッダを含めて入れ替える。
def change( row ){
    def tmp = row[2]
    row[2] = row[1]
    row[1] = tmp
}
change(titles)
records.each{
    change(it)    
}

// 第4カラムの値にそれぞれ1を加える。
records.each{ r ->
    r[3] = (r[3].toInteger() + 1).toString()
}

([titles] + records).each{
    println it.join("\t")
}

genzouさんに触発されて書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
boolean firstLine = true
def lines = []
new File("test.csv").eachLine {
    e = it.split("\t")
    if (firstLine) {
        println([e[0],e[2],e[1],e[3]].join("\t"))
        firstLine = false
    }
    else {
        lines.push(e)
    }
}

lines.sort{it[0].toInteger()}.each{
    println([it[0],it[2],it[1],it[3].toInteger() +1].join("\t"))
}

私も書いてみました。わかりやすさ重視で書いたつもりです。。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
lines = new File('test.csv').readLines()
rows = lines.collect { it.split('\t') }
head = rows.remove(0)
rows.each { row ->
  [0, 3].each { row[it] = row[it].toInteger() }
}

rows.sort { it[0] }
[head, *rows].each { tmp = it[1]; it[1] = it[2]; it[2] = tmp }
rows.each { it[3]++ }

[head, *rows].each { println it.join('\t') }

間違えてOtherで投稿しちゃいました。削除はできないんですね。。Groovyに投稿し直します。ごめんなさい。

私も書いてみました。わかりやすさ重視で書いたつもりです。。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
lines = new File('test.csv').readLines()
rows = lines.collect { it.split('\t') }
head = rows.remove(0)
rows.each { row ->
  [0, 3].each { row[it] = row[it].toInteger() }
}

rows.sort { it[0] }
[head, *rows].each { tmp = it[1]; it[1] = it[2]; it[2] = tmp }
rows.each { it[3]++ }

[head, *rows].each { println it.join('\t') }
あまり美しくないですが、基本に忠実に。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import System.Environment
import Data.List

q1 (x:xs) = x:sortBy (\a b -> compare (toNum a) (toNum b)) xs
    where
        toNum n = read (head n)::Int
q2 = map (\[i,l,f,a] -> [i,f,l,a])
q3 (x:xs) = x:map (\[i,l,f,a] -> [i,l,f,show $ 1+read a]) xs

main = do
    args <- getArgs
    contents <- if (not.null) args
        then readFile $ head args
        else getContents
    let rec = map words $ lines contents
    putStrLn $ shows $ q1 rec
    putStrLn $ shows $ q2 rec
    putStrLn $ shows $ q3 rec
    where
        shows list = unlines $ map (concat.intersperse "\t") list
ワンライナ
1
groovy -e '{h,...r->[h,*r.each{it[0,4]*.toInteger()}.each{++it[3]}.sort{it[0]}]*.join("\t").any"".&println}(*"$System.in".trim().split(/\n/)*.split(/\t/)*.getAt([0,2,1,3]))'<data.csv
< it[0,4]*.toInteger()
---
> a->[0,3].each{a[it]=0.decode(a[it])}

全然違った。「it[0,3]=it[0,3]*.toInteger()」だとうまくいかなくて残念。
C:\>type emp.txt
ID      Surname Forename        Age
1       Sato    Hanako  17
0       Suzuki  Taro    18

C:\>cscript /nologo csv.js < emp.txt
ID      Forename        Surname Age
0       Taro    Suzuki  19
1       Hanako  Sato    18
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Array.prototype.each = function(iterator){
  for(var i=0, length=this.length; i<length; i++) iterator(this[i],i);
}

var table = [];
WScript.StdIn.ReadAll().split('\r\n').each(function(line){
  if(!line.match(/^\s*$/)) table.push(line.split('\t'));
});
var header = table.shift();

//第1カラムの値でデータを昇順にソートする
table.sort(function(a,b){return a[0]-b[0];});
//第4カラムの値にそれぞれ1を加える
table.each(function(row){row[3]=row[3]*1+1;});
//第2カラムと第3カラムをヘッダを含めて入れ替える
table.unshift(header);
table.each(function(row){
  WScript.StdOut.WriteLine([row[0],row[2],row[1],row[3]].join('\t'));
});

はじめまして。最初awk向きなのになぜ未投稿?と思ったのですが、ソートが必要なのですね。 教科書どおりのqsortですが、整数値をソートする、ということで17行目で +0 してから比較してみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
>type data.txt
ID    Surname    Forename    Age
0    Suzuki    Taro    19
1    Sato    Hanako    18
11    Kato    Junko    40
3    Yamammoto    Shingo    46

>type  tab_sort.awk
BEGIN{FS="\t";OFS="\t"}

NR == 1{ print $1,$3,$2,$4 }
NR > 1{t=$2;$2=$3;$3=t;$4=$4+1;A[NR]=$0;}

END{qsort(A,2,NR)
     for(i=2;i<=NR;i++) print A[i]
    }

function qsort(A,left,right){
   if(left>=right) return
   swap(A,left,left+int((right-left+1)*rand()))
   last=left
   for(i=left+1;i<=right;i++)
       #if(A[i] < A[left]) swap(A,++last,i)
       if(A[i] + 0 < A[left] + 0) swap(A,++last,i)
   swap(A,left,last)
   qsort(A,left,last-1)
   qsort(A,last+1,right)
}

function swap(A,i,j){
   t=A[i];A[i]=A[j];A[j]=t
}

>mawk32 -f tab_sort.awk data.txt > kekka.txt
>type kekka.txt
ID    Forename    Surname    Age
0    Taro    Suzuki    20
1    Hanako    Sato    19
3    Shingo    Yamammoto    47
11    Junko    Kato    41

はじめまして。最初awk向きなのになぜ未投稿?と思ったのですが、ソートが必要なのですね。 教科書どおりのqsortですが、整数値をソートする、ということで17行目で +0 してから比較してみました。 #最初の投稿時、言語を指定しそこない、otherに投稿してしまいました。申し訳ありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
>type data.txt
ID    Surname    Forename    Age
0    Suzuki    Taro    19
1    Sato    Hanako    18
11    Kato    Junko    40
3    Yamammoto    Shingo    46

>type  tab_sort.awk
BEGIN{FS="\t";OFS="\t"}

NR == 1{ print $1,$3,$2,$4 }
NR > 1{t=$2;$2=$3;$3=t;$4=$4+1;A[NR]=$0;}

END{qsort(A,2,NR)
     for(i=2;i<=NR;i++) print A[i]
    }

function qsort(A,left,right){
   if(left>=right) return
   swap(A,left,left+int((right-left+1)*rand()))
   last=left
   for(i=left+1;i<=right;i++)
       #if(A[i] < A[left]) swap(A,++last,i)
       if(A[i] + 0 < A[left] + 0) swap(A,++last,i)
   swap(A,left,last)
   qsort(A,left,last-1)
   qsort(A,last+1,right)
}

function swap(A,i,j){
   t=A[i];A[i]=A[j];A[j]=t
}

>mawk32 -f tab_sort.awk data.txt > kekka.txt
>type kekka.txt
ID    Forename    Surname    Age
0    Taro    Suzuki    20
1    Hanako    Sato    19
3    Shingo    Yamammoto    47
11    Junko    Kato    41

どうですかね。Rubyっぽいですか?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
lst = []

#全部リスト(lst)に取り込んで
while gets
  lst << chomp.split("\t")
end

#Array#mapで第2カラムと第3カラムを入れ替えて
lst.map!{|x|x[0], x[1], x[2], x[3] = 
            x[0], x[2], x[1], x[3]}

#見出しを出力して
puts lst.shift.join("\t")

#IDを数値にして、Ageに一つ加えて、IDで並び替えて出力
for i in lst.map{|x|[x[0].to_i, x[1], x[2], x[3].next]}.sort
  puts i.join("\t")
end

__END__
ID    Surname    Forename    Age
1    Sato    Hanako    17
0    Suzuki    Taro    18

俺なりのRubyっぽく、かつ各操作の直交性を意識して書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
str = "ID\tSurname\tForename\tAge
1\tSato\tHanako\t17
0\tSuzuki\tTaro\t18"

#取り込み
data = str.split(/\n/).map{|line| line.split("\t")}

#第2カラムと第3カラムをヘッダを含めて入れ替える。
data.each{|record| t = record[1]; record[1] = record[2]; record[2] = t}

head = data.shift

#第1カラムの値でデータを昇順にソートする。
data = data.sort_by{|record| record[0]}

#第4カラムの値にそれぞれ1を加える。
data.each{|record| record[3].succ!}

#表示
([head]+data).each{|record| puts record.join("\t")}

配列とか知ってればもっとスマートにかけたと思うのですが、まだ学習中なので……。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import List

testdata = "ID\tSurname\tForename\tAge\n" ++
           "1\tSato\tHanako\t17\n" ++
           "0\tSuzuki\tTaro\t18\n" ++
           "2\tTanaka\tYaeko\t16" 



readI ::String -> Int
readI = read

mkTable = map words.lines 
mkText = unlines .map (concat.intersperse "\t") 

sortTable (header:body)= header:sortBy (\a b->compare (readI $ head a) (readI $ head b)) body


swapColumn23 table = map swapItem table
    where swapItem [a,b,c,d]=[a,c,b,d]

add1Column4 (header:body)= header:map (reverse.(\(x:xs)->show(readI(x)+1):xs).reverse) body

converter :: String -> String
converter content = mkText $ add1Column4 $ swapColumn23 $ sortTable $ mkTable content

main = interact $ converter

Safe C String Library (SafeStr)に,ちょうどsafestr_splitがあるので,それを使ってみました.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "safestr.h"

#ifndef false
# define false 0
#endif
#ifndef true
# define true !false
#endif

struct item_t {
    int is_int;
    union {
        safestr_t str;
        int num;
    } value;
};


struct item_t *
create_record_from_list(safestr_t *list)
{
    struct item_t *item;
    int i;

    if ((item = (struct item_t *)malloc(sizeof(struct item_t[4])))
        == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    
    for (i = 0; i < 4 && list[i] != NULL; i++) {
        item[i].is_int = false;
        item[i].value.str = list[i];
        safestr_reference(list[i]);
    }
    if (i != 4) {
        fprintf(stderr, "ERROR: 4 fields needed.\n");
        exit(EXIT_FAILURE);
    }

    return item;
}

void
convert_int_fields(struct item_t *item)
{
    int value;
    int i;
    for (i = 0; i <= 3; i+=3) {
        value = safestr_toint32(item[i].value.str, 0);
        safestr_free(item[i].value.str);
        item[i].value.num = value;
        item[i].is_int = true;
    }
}

void
records_dump(struct item_t **list, struct item_t *header,
             ssize_t list_size, int *order)
{
    int i, j, col;
    struct item_t *cur;
    safestr_t str = safestr_create("", 0);
    safestr_t str_p = str;

    for (i = -1; i < list_size; i++) {
        cur = (i == -1) ? header : list[i];
        for (j = 0; j < 4; j++) {
            col = (order == NULL) ? j : order[j];
            if (cur[col].is_int) {
                safestr_sprintf(&str, SAFESTR_TEMP("%d"),
                                cur[col].value.num);
                str_p = str;
            } else {
                str_p = cur[col].value.str;
            }
            printf("%s%s", (j == 0) ? "" : "\t", (char *)str_p);
        }
        putchar('\n');
    }
    safestr_free(str);
}

void
records_copy(struct item_t **dest, struct item_t **src,
             int record_size)
{
    struct item_t *item;
    int i, j;

    for (i = 0; i < record_size; i++) {
        if ((item = (struct item_t *)malloc(sizeof(struct item_t[4])))
            == NULL) {
            perror("malloc");
            exit(EXIT_FAILURE);
        }
        dest[i] = item;

        for (j = 0; j < 4; j++) {
            dest[i][j] = src[i][j];
            if (!dest[i][j].is_int)
                safestr_reference(dest[i][j].value.str);
        }
    }
}

void
records_free(struct item_t **list, int record_size)
{
    int i, j;

    for (i = 0; i < record_size; i++) {
        for (j = 0; j < 4; j++)
            if (!list[i][j].is_int)
                safestr_release(list[i][j].value.str);
        free(list[i]);
    }
}

int
compare(const void *former, const void *latter)
{
    struct item_t *former_p = *((struct item_t **)former);
    struct item_t *latter_p = *((struct item_t **)latter);

    return (former_p[0].value.num - latter_p[0].value.num);
}

int
main(int argc, char **argv)
{
    safestr_t line, *list;
    struct item_t *record, *header = NULL;
    struct item_t *records[64], *r_temp[64];
    int i, record_size = 0;
    int order[4] = { 0, 2, 1, 3 };
    
    memset(&records, 0, sizeof(records));
    
    /* read data from stdin */
    while ((line = safestr_readline(stdin)) != NULL) {
        list = safestr_split(line, SAFESTR_TEMP("\t"));
        record = create_record_from_list(list);

        if (header == NULL)
            header = record;
        else {
            convert_int_fields(record);
            records[record_size++] = record;
        }
        safestr_free(line);
        safestr_freelist(list);
    }
    if (record_size == 0)
        exit(EXIT_SUCCESS);


    /*
     * show results
     */
    /* #1 */
    records_copy(r_temp, records, record_size);
    qsort(r_temp, record_size, sizeof(struct item_t *), compare);

    printf("Result of #1:\n");
    records_dump(r_temp, header, record_size, NULL);
    printf("\n");
    records_free(r_temp, record_size);

    /* #2 */
    records_copy(r_temp, records, record_size);
    qsort(r_temp, record_size, sizeof(struct item_t *), compare);

    printf("Result of #2:\n");
    records_dump(r_temp, header, record_size, order);
    printf("\n");
    records_free(r_temp, record_size);

    /* #3 */
    records_copy(r_temp, records, record_size);
    for (i = 0; i < record_size; i++)
        r_temp[i][3].value.num += 1;

    printf("Result of #3:\n");
    records_dump(r_temp, header, record_size, NULL);
    printf("\n");
    records_free(r_temp, record_size);


    /* finalize */
    records_free(records, record_size);

    exit(0);
}
いまさら投稿してみる。
分かりやすく書いたつもりです。

**ポイント
ヘッダとデータは #head(), #tail()を使って取得。
ヘッダごと入れ替え時は、列を1本ずつ取得してから、#transpose()で行・列を入れ替え。

**test.tsv
ID      Surname Forename        Age
1       Sato    Hanako  17
0       Suzuki  Taro    18

**実行結果
[入力時]
ID      Surname Forename        Age
1       Sato    Hanako  17
0       Suzuki  Taro    18

[出力時]
ID      Forename        Surname Age
0       Taro    Suzuki  19
1       Hanako  Sato    18
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 入力
def list0 = new File('test.tsv').readLines()*.split('\t')
// 第1カラムの値でデータを昇順にソートする。
def list1 = [list0.head(), *(list0.tail().sort{it[0]})]
// 第2カラムと第3カラムをヘッダを含めて入れ替える。
def list2 = [
  list1*.getAt(0),
  list1*.getAt(2), // ←第2カラムと
  list1*.getAt(1), // ←第3カラムの入れ替え
  list1*.getAt(3)
].transpose()
// 第4カラムの値にそれぞれ1を加える。
def list3 = [list2.head(),
   *(list2.tail().collect{ [*(it[0..2]), it[3].toInteger() + 1] })]
// 出力
println """\
[入力時]
${ list0*.join('\t').join('\n') }

[出力時]
${ list3*.join('\t').join('\n') }\
"""

F#で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#light "off"
open System;
open System.IO;

let reader (stream:TextReader) =
  0 |> Seq.unfold
      (fun x ->
         match stream.ReadLine() with
           | null -> None
           | s -> Some(s.Split([|'\t'|]), x))
in
let main (input:TextReader) (output:TextWriter) =
  let records = reader input in
  let header = records |> Seq.take 1 in
  let contents =
    records
    |> Seq.sort_by (fun x -> int x.[0])
    |> Seq.map (fun x -> [|x.[0]; x.[1]; x.[2]; string ((int x.[3]) + 1)|])
  in
    Seq.append header contents
    |> Seq.map (fun x -> String.Join("\t", [|x.[0]; x.[2]; x.[1]; x.[3]|]))
    |> Seq.iter output.WriteLine
in
  main Console.In Console.Out;;

Personクラスを定義して、それのインスタンスのリストを作って、それをソートするという余計な手間ばかりをかけてます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
open System.IO

let inputFileName = @"s:\inputData.txt"
let outputFileName = @"s:\outputData.txt"

type Person (st:string[]) =
    member this.id = System.Int32.Parse st.[0]
    member this.age = (System.Int32.Parse  st.[3]) + 1
    member this.toFormat () =
        this.id.ToString() + "\t" + st.[1] + "\t" + st.[2] + "\t" + this.age.ToString() 

let splitReplaceToArr (str:string) =
    let strArr = str.Split([|'\t'|])
    [|strArr.[0];strArr.[2];strArr.[1];strArr.[3]|]

let linesReadRepToList (inputFileName: string) =
    [                                             
        use fileReader = new StreamReader(inputFileName)  
        while not fileReader.EndOfStream do          
            let line = splitReplaceToArr (fileReader.ReadLine())
            yield line                               
     ]

let headerArr = List.hd (linesReadRepToList inputFileName)
let personList = List.map (fun arr -> new Person (arr))  (List.tl (linesReadRepToList inputFileName))
let sortedList = List.sortWith(fun (l:Person) (r:Person) -> r.age - l.age) personList 
let resultList = (Array.reduce  (fun s inStr -> s + "\t" + inStr) headerArr) :: 
                    (List.map (fun (p:Person) -> p.toFormat()) sortedList) 

File.WriteAllLines(outputFileName,resultList)
Table クラスを作って、それに処理させる、カラム名で処理するという方針で書いて見ました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
open System
open System.IO
open System.Collections.Generic

type Array with
  static member Join separator a =
    System.String.Join(separator, Array.ConvertAll(a, fun x -> x.ToString()))

type Table() =
  let column = new Dictionary<string, int>()
  let table = new ResizeArray<obj []>()
  let headLine() = 
    Seq.zip column.Values column.Keys
    |> Seq.sort
    |> Seq.map snd
    |> Seq.to_array

  member t.from_file(filename:string) =
    if table.Count <> 0 then table.Clear();column.Clear()
    use sr = new StreamReader(filename) 
    let headLine = sr.ReadLine()
    let fields = headLine.Split('\t')
    let size = fields.Length
    Array.iteri (fun i name -> column.Add(name, i)) fields
    while not sr.EndOfStream do
      let fields = sr.ReadLine().Split('\t')
//    printfn "%A" fields
      let size = fields.Length
      let data =
        Array.map
          (fun x -> 
           match System.Int32.TryParse(x) with
           | (true, i)  -> box i 
           | (false, _) -> box x
          ) fields
      table.Add(data)
  member t.columnNames =
    Seq.to_array column.Keys
  member t.apply(col, proc) = 
    let col = column.[col]
    for i=0 to table.Count-1 do
      table.[i].[col] <- box <| proc (unbox table.[i].[col])
  member t.swap(col1, col2) = 
    let (c1,c2)=(column.[col1],column.[col2])
    column.[col1]<-c2
    column.[col2]<-c1
    for i=0 to table.Count-1 do
      let (d1,d2) = (table.[i].[c1] , table.[i].[c2])
      table.[i].[c1] <- d2
      table.[i].[c2] <- d1
  member t.sort(col, cmp) = //安定なソートではない。
    let col = column.[col]
    table.Sort(fun (a:obj array) (b:obj array) -> cmp (unbox a.[col]) (unbox b.[col]))
  member t.to_file(filename:string) =
    use sw = new StreamWriter(filename)
    let headLine = headLine()
    sw.WriteLine(Array.Join "\t" headLine)
    table.ForEach(fun x -> sw.WriteLine(Array.Join "\t" x))
do 
  let t=new Table()
  t.from_file "table.txt"
  t.sort("ID", fun a b -> a - b)
  t.swap("Surname", "Forename")
  t.apply("Age", fun x -> x + 1)
  t.to_file "table_new.txt"

Index

Feed

Other

Link

Pathtraq

loading...