エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

プログラムコードの、ひとつ上の視点で


今年の後半は、鍋谷さんが主催されている「オフラインリアルタイムどう書く」にオンラインで非リアルタイムで参加させてもらいました(参加というか、勝手にコード書いてアップしてただけですが)。

以前にも書きましたが、鍋谷さんが出題される問題の難易度が絶妙で、ぱっと見るとすぐに解けそうで、そう思って考えなしにコードを書き出すとつまずいて、もう一度設問を読み直し解き方をひとつひとつ考えると確かに答えにたどり着く、という感じの難易度なのです。


問題を解きながら幾度となく思ったのが、人間の考え方とプログラミングでの考え方はこんなにも違うのか、というところ。
たとえば、大貧民の問題。人間なら見てすぐに答えが出せます。逆に答えの出し方を説明するのが難しいぐらいではないでしょうか。表現できないという意味でこれも暗黙知のひとつなんじゃないかな、と思います。プログラミングはこの表現できない知を表現できる知に変換する必要があります。
最終的に人の目に触れるのはコードという形で表現された知。

プログラミングの難しさはこの暗黙知から形式知への変換がコードのレベルでは表現されていない(少なくとも明示的には)というところじゃないだろうか、と感じました。コードのレベルで考えているあいだは問題は解決しない。コードの、ひとつ上の視点で視ることが問題を解決しプログラミングのレベルを上げていくのに必要なことじゃないかな、とそんなことを感じさせられる半年でした。
わたしが複数のプログラミング言語に手を出すのも、プログラミングのコードのレベルよりも高い視点が欲しいからなんだと思います。


ま、実態は。単なる言語好き、言語オタクというだけかもしれませんが。



そんなこんなで。
今年最後の実装です。


GitHubにも置きました


Haskell

module Answer(solve) where

import Data.List

type Pos = (Int, Int)

l_intersect :: (Pos, Pos, Pos) -> (Pos, Pos, Pos) -> String
l_intersect ((a11, a12), (a21, a22), (a31, a32)) ((b11, b12), (b21, b22), (b31, b32)) =
    show $ length $ intersect (union rect1 rect2) (union rect3 rect4)
    where
        rect1 = [(x,y)|x<-(n a11 a21), y<-(n a12 a22)]
        rect2 = [(x,y)|x<-(n a11 a31), y<-(n a12 a32)]
        rect3 = [(x,y)|x<-(n b11 b21), y<-(n b12 b22)]
        rect4 = [(x,y)|x<-(n b11 b31), y<-(n b12 b32)]
        n a b = if a < b then [a..b] else [b..a]

solve :: String -> String
solve [a11,a12,'-',a21,a22,'-',a31,a32,',',b11,b12,'-',b21,b22,'-',b31,b32] =
    l_intersect ((read [a11], read [a12]), (read [a21], read [a22]), (read [a31], read [a32]))
                ((read [b11], read [b12]), (read [b21], read [b22]), (read [b31], read [b32]))


テストコード。

module Main where
import Test.HUnit
import Answer

-- referring to source of words 
-- http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.6.0.0/src/Data-List.html#words
split :: (a -> Bool) -> [a] -> [[a]]
split f s =
    case dropWhile f s of
         [] -> []
         s' -> w : split f s''
               where (w, s'') = break f s'

doAssert :: [String] -> Assertion
doAssert (name:input:expected:_) =
    assertEqual name expected (solve input)
doAssert _ = error "Specify a list which contains more than 3 items."

main :: IO ()
main =
    readFile "patterns.tsv"
    >>= runTestTT . test . map (doAssert . split (== '\t')) . lines
    >>  return ()


テストパタン。

#1	23-94-28,89-06-51	11
#2	11-84-58,02-73-69	40
#3	18-41-86,77-04-32	26
#4	81-88-23,64-58-14	0
#5	31-29-07,41-87-69	0
#6	83-13-40,18-10-94	1
#7	77-80-92,21-72-38	2
#8	57-70-91,55-19-08	3
#9	18-22-75,66-80-91	4
#10	51-93-78,54-49-06	5
#11	58-70-96,17-43-76	6
#12	58-07-12,58-82-93	7
#13	41-29-07,35-95-88	8
#14	88-26-60,42-29-07	9
#15	18-40-85,34-40-91	10
#16	36-60-96,53-96-89	11
#17	51-39-02,44-98-69	12
#18	48-06-20,76-04-42	13
#19	85-29-18,26-50-93	14
#20	27-50-91,43-29-07	15
#21	57-06-20,48-60-91	16
#22	52-98-89,21-76-67	17
#23	67-12-40,45-80-92	18
#24	47-03-10,26-30-82	19
#25	74-28-06,21-86-37	20
#26	65-01-20,73-39-05	21
#27	17-72-86,36-50-94	22
#28	51-29-07,77-15-41	23
#29	33-98-39,82-16-02	24
#30	75-05-10,37-81-96	25
#31	72-58-06,48-80-96	26
#32	81-67-16,21-91-59	27
#33	13-96-57,24-96-79	28
#34	57-04-32,51-18-06	29
#35	88-03-52,28-41-86	30
#36	78-04-61,13-86-49	31
#37	58-12-20,27-50-85	32
#38	61-19-05,71-68-15	33
#39	63-29-16,18-31-83	34
#40	16-50-91,32-98-79	35
#41	82-17-03,38-40-81	36
#42	72-48-04,11-98-39	37
#43	77-05-10,28-50-62	38
#44	38-50-91,11-86-57	39
#45	87-05-10,13-97-69	40
#46	11-86-49,22-98-89	44
#47	11-97-69,12-86-67	46
#48	11-95-69,71-49-05	47
#49	28-31-92,13-98-79	48


実行結果。

$ runhaskell test.hs
Cases: 49  Tried: 49  Errors: 0  Failures: 0

C++

#ifndef DOUKAKU_ANSWER_HPP
#define DOUKAKU_ANSWER_HPP

#include <string>

std::string solve(const std::string& s);

#endif//DOUKAKU_ANSWER_HPP
#include "answer.hpp"

#include <bitset>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>

typedef std::bitset<100> Bitset;

struct El
{
    int p1, p2, p3;

    Bitset asBitset() const
    {
        int x1 = p1 / 10, y1 = p1 % 10;
        int x2 = p2 / 10, y2 = p2 % 10;
        int x3 = p3 / 10, y3 = p3 % 10;

        Bitset b2, b3;

        fill(b2, std::min(x1, x2), std::min(y1, y2), std::max(x1, x2), std::max(y1, y2));
        fill(b3, std::min(x1, x3), std::min(y1, y3), std::max(x1, x3), std::max(y1, y3));

        return b2 | b3;
    }

    static void fill(Bitset& b, int x1, int y1, int x2, int y2)
    {
        for(int y = y1; y <= y2; ++y)
        {
            for(int x = x1; x <= x2; ++x)
            {
                b.set(x * 10 + y);
            }
        }
    }
};

std::istream& operator >> (std::istream& in, El& el)
{
    int  p1, p2, p3;
    char d1, d2;
    in >> p1 >> d1 >> p2 >> d2 >> p3;
    if((d1 == '-') && (d2 == '-'))
    {
        el.p1 = p1; el.p2 = p2; el.p3 = p3;
    }
    return in;
}

std::string solve(const std::string& s)
{
    std::istringstream iss(s);
    El e1, e2;
    char d;

    iss >> e1 >> d >> e2;

    std::ostringstream oss;
    if((d == ',') && iss.eof())
    {
        oss << ((e1.asBitset() & e2.asBitset()).count());
    }
    else
    {
        oss << 0;
    }

    return oss.str();
}


テストコード。

#include "answer.hpp"

#include <string>
#include <iostream>
#include <fstream>

struct Pattern
{
    Pattern(const std::string& s) : name(), input(), expected()
    {
        std::size_t d1 = s.find('\t');
        std::size_t d2 = s.find('\t', d1 + 1);
        if((d1 != std::string::npos) && (d2 != std::string::npos))
        {
            name     = s.substr(0, d1);
            input    = s.substr(d1 + 1, d2 - d1 - 1);
            expected = s.substr(d2 + 1);
        }
    }

    std::string name;
    std::string input;
    std::string expected;
};

int main(int, char* [])
{
    std::ifstream patterns("patterns.tsv");
    int           count_cases    = 0;
    int           count_failures = 0;
    std::string   s;
    while(std::getline(patterns, s).good())
    {
        ++count_cases;
        Pattern     pattern(s);
        std::string actual = solve(pattern.input);
        if(actual != pattern.expected)
        {
            ++count_failures;
            std::cout << "Failure in " << pattern.name << "\n"
                      << "expected: \"" << pattern.expected << "\"\n"
                      << "  actual: \"" << actual << "\"\n";
        }
    }
    std::cout << "\nCases: " << count_cases << "  Failures: " << count_failures << "\n";

    return 0;
}