2
3

More than 5 years have passed since last update.

POH6+『「え、妻が松江?」松江Ruby会議07協賛 回文作成プログラミングコンテスト』 改良編

Last updated at Posted at 2015-09-03

概要

この記事には、POH6+に関するネタバレが含まれます。カンニングしたくない場合は、前々回の記事を読んで勉強するんだ!

……今回はコードを改良する話。
2015/09/04:重大なミスが見つかりましたので修正しました。

STLが足りてない!

実は前回挙げたコードには問題がある。100点満点ではあるのだが、美しくない。つまり、STLの力を生かしきれてないということ

手順としては、いかに「左側」一覧と「中央」を抽出するかなのだが、前回でも述べたように文字列は別に重複して構わない。その一方で、前回はゴリゴリと線形検索して、その後にソートを掛けていた。

つまりこれは、std:mapを使うべきところなのではないか?

std::mapなら突っ込んだデータが確実にソートされているので後からソートする必要はないし、同じ単語が複数あればfindで判別してインクリメントすればいい。検索も二分検索(std::unordered_mapならハッシュ法)を使うから超速いし、なにより(std::vectorと同じく)メモリ解放する手間が掛からない

と言うことで、単語リスト読み込みにはstd::unordered_map、『leftの集合』を入れるのにはstd::mapを使用した。以下ソース。

投稿しなおしたコード(修正前)

code5.cpp
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
using namespace std;
int main(void){
    // 単語リストに単語を代入する
    int N;
    cin >> N;
    unordered_map<string, int> word;    //単語リスト(単語->回数)
    for(int i = 0; i < N; ++i){
        string temp;
        cin >> temp;
        // 単語リスト
        if (word.find(temp) != word.end()){
            ++word.at(temp);
        }else{
            word[temp] = 1;
        }
    }
    // 単語リストから「左側」一覧と「中央」を取得する
    map<string, int> left;  //「左側」のリスト
    string center_word; int center_count = 0;   //「中央」
    for(auto it : word){
        // 単語を反転したものを用意する
        string reverse_word = it.first;
        reverse(reverse_word.begin(), reverse_word.end());
        // 「中央」ならば、「より数が多い」か「数は同じでより若い」もののみ受け入れる
        if(it.first == reverse_word){
            if(center_count < it.second){
                center_word = reverse_word;
                center_count = it.second;
            }else if(center_count == it.second){
                if(center_word > reverse_word){
                    center_word = reverse_word;
                }
            }
        }else{
            // 「左側」ならば、「より若い」もののみ受け入れ、値は「より数が少ない」方にする
            if(word.find(reverse_word) != word.end()){
                left[min(it.first, reverse_word)] = min(it.second, word.at(reverse_word));
            }
        }
    }
    // 解を出力する
    // (左側→中央→右側。右側は左側の反転、中央は当該文字列をただ吐くだけ)
    string all_left_word = "";
    for(auto it : left){
        for(int i = 0; i < it.second; ++i){
            all_left_word += it.first;
        }
    }
    cout << all_left_word;
    for(int i = 0; i < center_count; ++i){
        cout << center_word;
    }
    reverse(all_left_word.begin(), all_left_word.end());
    cout << all_left_word << endl;
}

ちなみにコイツを魔圧縮したら635バイトになった。まあまあ縮んだんじゃないかな?

投稿しなおしたコード(修正後)

code5_2.cpp
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
using namespace std;
int main(void){
    // 単語リストに単語を代入する
    int N;
    cin >> N;
    unordered_map<string, int> word;    //単語リスト(単語->回数)
    for(int i = 0; i < N; ++i){
        string temp;
        cin >> temp;
        // 単語リスト
        if (word.find(temp) != word.end()){
            ++word.at(temp);
        }else{
            word[temp] = 1;
        }
    }
    // 単語リストから「左側」一覧と「中央」一覧を取得する
    map<string, int> left;  //「左側」のリスト
    map<string, int> center;    //「中央」のリスト
    for(auto it : word){
        // 単語を反転したものを用意する
        string reverse_word = it.first;
        reverse(reverse_word.begin(), reverse_word.end());
        // 「中央」ならば、とりあえず全て取得する
        if(it.first == reverse_word){
            center[reverse_word] = it.second;
        }else{
            // 「左側」ならば、「より若い」もののみ受け入れ、値は「より数が少ない」方にする
            if(word.find(reverse_word) != word.end()){
                left[min(it.first, reverse_word)] = min(it.second, word.at(reverse_word));
            }
        }
    }
    // 「中央」のうち、2個以上ある単語はそのうちの半分を左側、もう半分を右側に振り分けておく
    // 例えば7個あった場合は3個を左側に入れて残り1個、8個なら4個入れて残り0個
    for(auto &it : center){
        if(it.second >= 2){
            left[it.first] = it.second / 2;
            it.second = it.second % 2;
        }
    }
    // 解を出力する
    // (左側→中央→右側。右側は左側の反転、中央は左右に過剰分を振り分けた後最若を出力)
    string all_left_word = "";
    for(auto &it : left){
        for(int i = 0; i < it.second; ++i){
            all_left_word += it.first;
        }
    }
    cout << all_left_word;
    for(auto &it : center){
        if(it.second == 1){
            cout << it.first;
            break;
        }
    }
    reverse(all_left_word.begin(), all_left_word.end());
    cout << all_left_word << endl;
}
2
3
0

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
3