Matija    about    posts    reading

SRM 739 Div II - C++ vs Haskell

SRM 739 was held on October 10th. The original editorial can be found here. Since I was participating and I am also currently learning Haskell, I decided to solve all the problems in Haskell as an exercise and then I found it interesting to compare it with C++ solutions.

HungryCowsEasy - Div 2 Easy

Short problem statement: On a straight line cows and barns are located, and each of them is assigned a position on that line (x coordinate). For each cow determine the barn that is closest to it. Among two equally close barns choose the one with the lower position.

Due to modest input size limits, here it is more than enough to employ a straightforward brute-force solution. For each cow we can examine all the barns and see which one is the closest. If two barns are equally close, one with the lower position is preferred.

C++

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
class HungryCowsEasy
{
public:
	vector <int> findFood(vector <int> cowPositions, vector <int> barnPositions)
	{
            vector<int> cowChoice;

            // Process each cow separately.
            for (int cowIdx = 0; cowIdx < cowPositions.size(); cowIdx++) {
                int cowPosition = cowPositions[cowIdx];

                // Find the nearest barn.
                int nearestBarnIdx = 0;
                for (int barnIdx = 0; barnIdx < barnPositions.size(); barnIdx++) {
                    int currBestDist = abs(cowPosition - barnPositions[nearestBarnIdx]);
                    int newDist = abs(cowPosition - barnPositions[barnIdx]);

                    if (newDist < currBestDist) {
                        nearestBarnIdx = barnIdx;
                    } else if (newDist == currBestDist) {
                        if (barnPositions[barnIdx] < barnPositions[nearestBarnIdx]) {
                            nearestBarnIdx = barnIdx;
                        }
                    }
                }
                cowChoice.push_back(nearestBarnIdx);
            }
	    return cowChoice;
	}
};

Haskell

1
2
3
4
5
6
7
8
9
10
11
12
findFood :: [Int] -> [Int] -> [Int]
findFood cowPoss barnPoss = map findBarnForCow cowPoss
    where
        findBarnForCow cowPos = fst $ foldr1 (determineBetterBarn cowPos) $ zip [0..] barnPoss

        determineBetterBarn cowPos barn1 barn2
            | dist1 < dist2 = barn1 
            | dist2 < dist1 = barn2
            | otherwise     = if snd barn1 < snd barn2 then barn1 else barn2
            where 
                dist1 = abs(cowPos - snd barn1)
                dist2 = abs(cowPos - snd barn2)

Comparison

Language Lines 100 runs
C++ 30 TBD
Haskell 12 TBD

Both solutions are short, but I really like how Haskell solution is more readable while being shorter when compared to C++ solution. In Haskell we could very easily create pairs (idx, value) and did not have to explicitly handle them as we did in C++.

ForumPostMedium - Div 2 Medium

Short problem statement: We are given two timestamps in hh:mm:ss format, time when the forum post was published and current time. We have to determine how long ago was a post published and express it through the appropriate string (e.g. “a few seconds ago”, “X minutes ago”, …).

This problem can actually be solved almost entirely analytically - we just transform both timestamps to seconds, get the difference and then transform it from seconds back to the required format (hour,min and sec diff). There is on case to be aware of - if current time is before the publishing time, it means the post was published the day before.

C++

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
class ForumPostMedium
{
public:
        int getTimeInSeconds(string time) {

            string delimiter = ":";
            string token;
            int pos = 0;
            vector<int> tokens;
            while ((pos = time.find(delimiter)) != string::npos) {
                token = time.substr(0, pos);
                tokens.push_back(stoi(token));

                time.erase(0, pos + delimiter.length());
            }
            tokens.push_back(stoi(time));

            return tokens[0] * 3600 + tokens[1] * 60 + tokens[2];
        }

	string getShownPostTime(string currentTime, string exactPostTime)
	{
            int currentTimeInSec = getTimeInSeconds(currentTime);
            int postTimeInSec = getTimeInSeconds(exactPostTime);

            int totalSecDiff = currentTimeInSec - postTimeInSec;
            if (postTimeInSec > currentTimeInSec) {
                totalSecDiff = 24 * 3600 - (postTimeInSec - currentTimeInSec);
            }

            // Get hour, min, and sec diff from sec diff.
            int hourDiff = totalSecDiff / 3600;
            int minDiff = (totalSecDiff % 3600) / 60;

            if (hourDiff == 0 && minDiff == 0) {
                return "few seconds ago";
            }
            if (hourDiff == 0) {
                return to_string(minDiff) + " minutes ago";
            }
            return to_string(hourDiff) + " hours ago";
	}
};

Haskell

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
getShownPostTime :: String -> String -> String
getShownPostTime currentTime exactPostTime
    | hourDiff == 0 && minDiff == 0 = "few seconds ago"
    | hourDiff == 0                 = show minDiff ++ " minutes ago"
    | otherwise                     = show hourDiff ++ " hours ago"
    where
        -- Convert string timestamp notations to seconds.
        currentTimeInSec = hmsToSec currentTime
        exactPostTimeInSec = hmsToSec exactPostTime

        -- Calculate the difference in seconds between the currentTime and
        -- postTime.
        totalSecDiff
            -- This is the case where post has been published a day before (E.g.
            -- if currentTime is "17:23:34" and postTime is "19:02:19"
            | exactPostTimeInSec > currentTimeInSec = 24 * 3600 - absDiff 
            | otherwise                             = absDiff
            where absDiff = abs(currentTimeInSec - exactPostTimeInSec)

        -- Translate difference in seconds to hour diff and min diff.
        hourDiff = totalSecDiff `div` 3600
        minDiff = (totalSecDiff `mod` 3600) `div` 60

hmsToSec :: String -> Int
hmsToSec hmsStr = h * 3600 + m * 60 + s
    where (h:m:s:_) = map read $ splitByColon hmsStr

splitByColon :: String -> [String]
splitByColon [] = []
splitByColon (':':str) = splitByColon str
splitByColon str = firstToken : (splitByColon rest)
    where (firstToken, rest) = break (==':') str

Comparison

Language Lines 100 runs
C++ 43 TBD
Haskell 32 TBD

Not such a huge difference here! What is interesting is that both solutions are actually very similar, since this problem can be solved in a completely analytical way, we don’t need even one for loop in the C++ solution (except in the helper method to get time in seconds from the original format).
It was actually quite straightforward to write a Haskell solution from the C++ solution.

CheckPolygon - Div 2 Hard

Short problem statement: We are given points respectively describing polygon in a 2D space. The task is to determine whether a polygon is a regular polygon, which means it satisfies certain conditions, such as that two adjacent segments are not collinear and none two segments are intersecting.
If the polygon is regular we should also calculate and return its area.

Input size limits are also not a problem here, so we just need to check if polygon satisfies all the requirements. This task was mostly about geometry - determining whether two segments interesect with each other, checking if point lines on a segment, calculating area of the polygon etc. It was also important to keep all the calculations in integers so we don’t get into precision problems.

Here are some links I found useful in understanding the formulas employed in the solution:

  • Orientation of 3 ordered points - link
  • Area of a polygon (shoelace formula) - link
  • Check if two segments intersect - link
  • Check if three points are collinear - link

C++

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
typedef pair<int, int> Point;
#define x first
#define y second

typedef pair<Point, Point> Segment;

class CheckPolygon
{
public:
        int orientation(Point a, Point b, Point c) {
            long long o = (long long)(b.y - a.y) * (c.x - b.x) - (long long)(c.y - b.y) * (b.x - a.x);

            if (o > 0) return 1;
            if (o < 0) return 2;
            return 0;
        }

        bool areCollinear(Point a, Point b, Point c) {
            // If area of a triangle is 0 -> points are collinear
            return orientation(a, b, c) == 0;
        }

        bool isPointOnSegment(Point point, Segment segment) {
            Point sA = segment.first;
            Point sB = segment.second;

            // Must be collinear and within the bounding rectangle of
            // the segment.
            return orientation(point, sA, sB) == 0
                && (point.x <= max(sA.x, sB.x) && point.x >= min(sA.x, sB.x))
                && (point.y <= max(sA.y, sB.y) && point.y >= min(sA.y, sB.y));
        }

        bool doSegmentsIntersect(Segment a, Segment b) {
            // NOTE(matija): what if segments are colinear?

            return orientation(a.first, a.second, b.first) != orientation(a.first, a.second, b.second) &&
                orientation(b.first, b.second, a.first) != orientation(b.first, b.second, a.second);
        }

        long long getPolygonArea(vector<Segment> segments) {
            long long area = 0;
            for (int i = 0; i < segments.size(); i++) {
                Point sA = segments[i].first;
                Point sB = segments[i].second;

                area += (long long)sA.x * sB.y - (long long)sB.x * sA.y;
            }
            return area;
        }

	string check(vector <int> X, vector <int> Y)
	{
            // Create segments of a polygon.
            vector<pair<Point, Point> > segments;
            for (int pointIdx = 0; pointIdx < X.size(); pointIdx++) {
                Point a = {X[pointIdx], Y[pointIdx]};
                Point b = {X[(pointIdx + 1) % X.size()], Y[(pointIdx + 1) % X.size()]};
                segments.push_back({a, b});
            }

            // Check every segment.
            for (int segmentIdx = 0; segmentIdx < segments.size(); segmentIdx++) {
                // Has positive length?
                // Is non-colinear with the next segment?
                // Does not intersect with other non-neighbouring segments?
                
                Segment s = segments[segmentIdx];
                int nextSegmentIdx = (segmentIdx + 1) % segments.size();
                int prevSegmentIdx = (segmentIdx - 1) < 0 ? segments.size() - 1 : segmentIdx - 1; 

                if (areCollinear(s.first, s.second, segments[nextSegmentIdx].second)) {
                    return "Not simple";
                }

                // Check with all non-neighbour segments.
                for (int i = 0; i < segments.size(); i++) {
                    if (i == segmentIdx || i == nextSegmentIdx || i == prevSegmentIdx) 
                        continue;

                    if (doSegmentsIntersect(s, segments[i])) {
                        return "Not simple";
                    }
                }
            }

            long long area = abs(getPolygonArea(segments));
            stringstream s;
            s << area/2 << '.' << "05"[area % 2];

            return s.str();
	}
};

Haskell

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
type Point = (Int, Int)
type Segment = (Point, Point)

orientation :: Point -> Point -> Point -> Int
orientation (ax, ay) (bx, by) (cx, cy)
    | o > 0     = 1
    | o < 0     = 2
    | otherwise = 0
    where o = (by - ay) * (cx - bx) - (cy - by) * (bx - ax)

areCollinear :: Point -> Point -> Point -> Bool
areCollinear a b c = (==0) $ orientation a b c

isPointOnSegment :: Point -> Segment -> Bool
isPointOnSegment point@(px, py) (sPoint1, sPoint2) = 
    -- Point must be collinear with the segment.
    orientation point sPoint1 sPoint2 == 0 &&
    -- Point must be within the bounding box of the segment.
    px <= max sax sbx && px >= min sax sbx &&
    py <= max say sby && py >= min say sby
    where
        (sax, say) = sPoint1
        (sbx, sby) = sPoint2

doSegmentsIntersect :: Segment -> Segment -> Bool
doSegmentsIntersect (a1, a2) (b1, b2) = 
    orientation a1 a2 b1 /= orientation a1 a2 b2 &&
    orientation b1 b2 a1 /= orientation b1 b2 a2

getPolygonArea :: [Segment] -> Int
getPolygonArea = foldr addSegmentToArea 0
    where addSegmentToArea ((p1x, p1y), (p2x, p2y)) area = area + p1x * p2y - p2x * p1y

checkPolygon :: [Int] -> [Int] -> String
checkPolygon xs ys = 
    if all isGood segmentsWithNextAndNonAdj then areaStr else "Not simple"
    where
        isGood (s, next, nnss) = not (areCollinear sPoint1 sPoint2 nextPoint2) 
                                 && all (not . doSegmentsIntersect s) nnss
            where
                (sPoint1, sPoint2) = s
                (_, nextPoint2) = next

        segmentsWithNextAndNonAdj = getNextAndNonAdjacent segments

        segments = getSegmentsFromPoints $ zip xs ys

        areaStr = (show $ areaNum `div` 2) ++ decimalPart
            where 
                areaNum = abs $ getPolygonArea segments
                decimalPart = if areaNum `mod` 2 == 0 then ".0" else ".5"

getSegmentsFromPoints :: [Point] -> [Segment]
getSegmentsFromPoints ps = zipWith (\p1 p2 -> (p1, p2)) ps psCircL1
    where psCircL1 = take (length ps) $ drop 1 $ cycle ps 
        
getNextAndNonAdjacent :: [Segment] -> [(Segment, Segment, [Segment])]
getNextAndNonAdjacent xs = zipWith getNextAndNonAdjacentForOne xsIndexed (repeat xsIndexed)
    where
        xsIndexed = zip [1..] xs

        getNextAndNonAdjacentForOne (idx, x) xsIdx = (x, next, nonAdjElems) 
            where 
                next = snd $ head $ drop idx $ concat $ repeat xsIdx
                nonAdjElems = map snd $ filter (not . areAdjacent (length xsIdx) idx . fst) xsIdx

        areAdjacent listLength idx1 idx2 = idx1 == idx2 || diff == 1 || diff == listLength - 1
            where diff = abs (idx1 - idx2)

Comparison

Language Lines 100 runs
C++ 93 TBD
Haskell 68 TBD

Solutions are here again pretty similar! Problem can be nicely decomposed in a functional way, we need a function for checking each condition, and then we just need to perform checks for every segment of the polygon.