【Cardクラスを定義】C++でクラスを使ってトランプカードの山札を作りたい【未完成】
c++をやるならclassをちゃんと作れるようにしないとね(逃げるな)
ポーカーにハマっているので、最終的に簡単なポーカー(テキサスホールデムが好き)ゲームを作ることを目標に
今回はジョーカーなしの13*4=52枚のカードデータの雛形のクラスを作ります。
class Cardに必要なもの
- 固有id(識別用)
- 柄(スーツ、♠♦♥♣)
- 数字(A23456789TJQK)
ポーカーではAが最強なので実際にはKよりAがでかくなるよう番号を振るほうが
良いように思いますが頭がおかしくなるのでAは1として扱い、Kは13とします。
別のクラスでルールを制定するときに改めて数字の強さを示すvalueを作ったほうが多分良いのかも。
データ構造のイメージ
id | A(1) | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | T(10) | J(11) | Q(12) | K(13) |
♠(1) | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 |
♦(2) | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
♥(3) | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
♣(4) | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
プログラミングのお作法的には0始まりにしたほうが絶対良いけどやはり慣れないので1スタートで。
Cardクラスのメンバ変数初期化の値を-1にしておいて、-1を検出したらエラーを返す。
……となると数値0が手持ち無沙汰になるのでやっぱり0スタートの-1エラー扱いが効率がいい。
もしくはオール0をジョーカーにするか。
ま、まあ初心者はまずどんな形でも完成させることが重要だからね。細かい修正は後にしよう。
最小限のclass Card
class Card{ public: int cardID; int cardSuit; int cardNumber; };
classではメンバ変数はprivateにしてset/get関数で入出力するというお作法があります。
お作法を全て無視したものがこちらです。カプセル化とかオブジェクト指向という概念に中指を立てすぎている。
動くかテストするコードはこれ
#include <bits/stdc++.h> using namespace std; class Card{ public: int cardID; int cardSuit; int cardNumber; }; int main(){ Card ASpade; ASpade.cardID=1; ASpade.cardSuit=1; ASpade.cardNumber=1; cout << "cardID="<<ASpade.cardID<<endl; cout << "cardSuit="<<ASpade.cardSuit<<endl; cout << "cardNumber="<<ASpade.cardNumber<<endl; }
出力結果
cardID=1
cardSuit=1
cardNumber=1
まあ動きます。シンプルですね。後は全てのカードをCard型で設定すれば山札が完成します。
こんなclassじゃ人に見せられない
ちゃんとclassの特性を活かしたコードにブラッシュアップしていきます。
#include <bits/stdc++.h> using namespace std; class Card{ private://メンバ変数はprivateに int cardID; int cardSuit; int cardNumber; public: Card(const int& id,const int& suit,const int& num) :cardID(id),cardSuit(suit),cardNumber(num) {//(id,suit,num)を受け取りその値で初期化 } void get_Card(){cout <<cardID<<cardSuit<<cardNumber<<endl;return;} }; int main(){ Card As(1,1,1); As.get_Card(); }
111
publicで宣言するclass名と同名のCard():{}は初回呼び出し時にメンバ変数を初期化します。
今回はCard(id,suit,num)を受け取り、その値でメンバ変数を初期化して宣言して
それらのメンバ変数が初回以降書き換わらないようにしてあるはずです。多分。
void get_Card()は設定されている(id,suit,num)をcoutするだけです。
これで最低限のcard情報を扱うclass Cardができました。
次にやることは52個のCard nを定義してデッキをつくることです。
次回vectorにCard(独自クラス)型を入れたい
コンストラクタ周りの理解が足りず、どうやらコピーコンストラクタをうんぬんすることで
独自クラスのデータをvectorで管理できるようだが実装できていない。
独習c++を読み直します。
またコメント等でもっと美しく機能的なclassを作る方法などご指南いただけると幸いです
とか思ってたらすぐにできた
#include <bits/stdc++.h> using namespace std; class Card{ private://メンバ変数はprivateに int cardID; int cardSuit; int cardNumber; public: Card(int& id,int& suit,int& num) :cardID(id),cardSuit(suit),cardNumber(num) {//(id,suit,num)を受け取ったらその値で初期化 } Card(const Card &cd):cardID(cd.cardID),cardSuit(cd.cardSuit),cardNumber(cd.cardNumber) {}//コピーコンストラクタ void get_Card(){cout <<cardID<<" "<< cardSuit <<" "<<cardNumber<<endl;return;} }; int main(){ vector<Card> Deck;//Card型データをまとめるvector int count=1;//id用 for(int i=1;i<5;++i){ for(int j=1;j<14;++j){ int id=count; int suit=i; int num=j; Deck.push_back(Card(id,suit,num)); count++; } } for(int i=0;i<52;++i){ Deck.at(i).get_Card(); } }
一応メイン関数にデッキ構築処理を書いていますがいずれ関数として切り分けます。
vector
二重ループのiはsuit番号,jはnumberに対応、idだけ別個にループ1回につきインクリメント処理されるcountを用意。
vec.push_back(x);はvectorの後ろにxを追加する操作。
xにclass(コンストラクタに渡したい引数)を入れることでコピーコンストラクタが頑張ってvectorにデータを渡している、はず。
順序的には初期化しているコンストラクタでまずCard(id,suit,num)を受け取って変数を初期化したCard型データができて、
それをコピーコンストラクタが参照してvectorに渡している……でいいのかな。
やはりもうちょっと動作の部分について学ぶべき。
正規表現と和解せよ【ABC049C - 白昼夢】
ABC049C-白昼夢
atcoder.jp
やること
- 文字列Sを受け取る
- Sが([dream]or[dreamer]or[erase]or[eraser])の繰り返しと等しいか判定する
- 正規表現から逃げるな
解答 正規表現
文字列を扱う上で正規表現から逃げていたら何も解決しません。
今まで逃げてきましたが、今日こそ正規表現に対する苦手意識を克服します。
cpprefjp.github.io
C++日本語リファレンスによると、何やらusing宣言されていますので
正規表現で判定したい文字列がchar型のときはregex nautokaと宣言すればbasic_regex
wchar型のときはwregex nantokaと宣言すればbasic_regex
char型とwchar型のことはとりあえずさておき、
まあ今回は普通にchar型として問題なく動くよねってことで今回はregexで正規表現を読み込んでもらいます。
その正規表現をどこからどうやって理解しようか……と足踏みしていたのですが
アルゴ式のところでコードテスト付きで正規表現が学べるコース(無料)がありました
algo-method.com
練習問題をいくつかやった結果、ちょっと感覚がつかめたのでコードが書けました。(ダイレクトマーケティング)
#include <bits/stdc++.h> using namespace std; int main(){ string S; cin>>S; regex reg(R"( (dream|dreamer|erase|eraser)* )");//正規表現 bool YES = regex_match(S,reg);//完全一致検索,返り値はbool if(YES){ cout << "YES" <<endl; }else{ cout << "NO" <<endl; } }
regex reg(R"( )");のR"()"は生文字列リテラルといって、エスケープ文字を使わなくてもまあまあ許してくれるやつです。
正直今回は不要ですが覚えておいて損はないはず。
あの恐ろしき正規表現部分が(dream|dreamer|erase|eraser)*ですね。
|はorと同じ意味なので、dream dreamer erase eraserのいずれかに完全一致するかどうか、という意味なります。
*は手前の文字の0回以上の繰り返しがあるかどうかを判定します。
手前を()でくくってひとかたまりにしているので(x)*、(x)の繰り返しを判定します。
(w|x|y|z|)*はw x y zのいずれかが繰り返されているか、という意味になります。
(dream|dreamer|erase|eraser)*は正規表現語で問題文をそのまま言い換えただけです。
regex_match(string,regex);は完全一致検索(string全体が正規表現と一致するか)
regex_search(string,regex);は部分一致検索をします。(stringの任意の箇所が正規表現と一致するか)
今回は完全一致してもらわないと(ゴミが残る)困るので、regex_matchで調べます。
成否を判定するので、返り値はbool型のtrueかfalseになります。そのままif文の判定式に突っ込んでも構いません。
よしこれで正規表現とはお別れや!
解答 真の正規表現
世の中は繰り返しが多いコンテンツに厳しいです。
コードの中に繰り返しが多いコンテンツが見つかるとき、大体短縮する方法があります。困ります。
dreamとdreamerって5文字一致してるし、eraseとeraserも5文字一致してるし、なんだか無駄がありますね? ケチかよ。
regex reg(R"((dream|dreamer|erase|eraser)*)");//正規表現 regex regreg(R"((dream(er){0,1}|erase(r){0,1})*)");//真の正規表現 regex sinreg(R"((dream(er)?|eraser?)*)");//シン・正規表現
{min,max}は直前の文字がmin回以上max回以下の繰り返しがあるかについて判定します。
(r)はくくってありますが、くくらなくても同じ意味です。
{0,1}と?は同じ意味を持ちます。より文字数を減らしてスマートになった(dream(er)?|eraser?)*がシン・正規表現です。
上記3つはどれも結果が同じです。
好きなやつを使いましょう。正規表現のこと好きになれねえや。
AtCoder Beginners Selectionをやる【ABC085B - Kagami Mochi】
日記代わりになにか書かないとサボってしまうので書きます。
プログラミング初心者のコードなので同じ初心者のひとはもっと上手い人の記事のコードを参考にしてください。
ABC085B - Kagami Mochi
atcoder.jp
やること
- 最大三桁の数値Nを受け取る(intで可)
- N個(最大100個)の数値を受け取る(配列)
- 重複する数値は削除するなどして1つにする
- 残った数値の数を数える
解答1 Vectorで受け取りUniqueで重複を潰す
要素数が100と小さいので、
1.vectorの要素をソート
2.uniqueで隣り合う重複要素を潰す
3.末端に発生する空要素をeraseで削除する
4.vectorのサイズを取得
この4ステップでも計算量が膨らんだりせずACを取れるはずです。
#include <bits/stdc++.h> using namespace std; int VecCinFor(vector<int>&a,int N){//入力をvectorに挿入 for(int i=0;i<N;i++){ cin >> a.at(i); } return 0; } int main() { int N; cin>>N; vector<int>vec(N); VecCinFor(vec,N); sort(vec.begin(),vec.end()); vec.erase( unique(vec.begin(),vec.end()) ,vec.end()); cout << vec.size() <<endl; }
cpprefjp.github.io
C++日本語リファレンスによるとuniqueは要素を削除せず、
末尾に重複削除分のゴミ要素を追加します。
またuniqueの返り値が重複のない要素の末尾の次のイテレーターを返すので
eraseでuniqueの返り値(ゴミ要素先頭)-vectror末尾(ゴミ要素最後尾)の範囲を削除することで
vectorのサイズが重複なし数値の数と等しくなり、解答となります。
今回作った処理を関数化していつでも呼び出せるように加工しましょう。
int DuplicateDeth(vector<int>&a){ sort(a.begin(),a.end()); a.erase(unique(a.begin(),a.end()),a.end()); return 0; }
これでいつでもvectorの重複要素を殺せるようになりました。
解答2 Setで受け取るだけ
数値の受け取りは何もvectorでしかできないということ無く
setに入力すればその時点で重複要素を弾くことができます。
ついでに入力時点でソートもしてくれます。
#include <bits/stdc++.h> using namespace std; SetCinFor(set<int> &a,int n){//入力をsetに挿入 for(int i=0;i<n,i++){ int x=0; cin>>x; a.insert(x); } return 0; } int main(void) { int N; cin>>N; set<int> moti; SetCinFor(moti,N); cout << moti.size() <<endl; }
要素を入力する場合、set.insert(要素)を使います。
仮受取用の変数xを毎回初期化して呼び出し、cin>>x;で入力を受け取った上で
set.insert(x);でsetに要素をぶちこみます。
ぶちこみ終わったらset.size()がそのまま解答となります。
余談
要素数Nの入力を受け取るためにいちいちfor文を書くのが面倒なことにようやく気がついたので
関数のオーバーロードでいいかんじに処理を予め作っておこうとようやく思い立った。
repマクロは……えーい使っちゃえ!
#include <bits/stdc++.h> #define rep(i, n) for (int i = 0; i < (int)(n); i++) using namespace std; int VecCinRep(vector<int> &a,int X){ //一次配列の入力 rep(i,X){ cin>>a.at(i); } return 0; } int VecCinRep(vector<vector<int>> &a,int X,int Y){ //二次配列の入力 rep(i,X){ rep(j,Y){ cin>>a.at(i).at(j); } } return 0; } int VecCinRep(vector<vector<vector<int>>> &a,int X,int Y,int Z){ //三次元配列の入力 rep(i,X){ rep(j,Y){ rep(k,Z){ cin>>a.at(i).at(j).at(k); } } } return 0; }
これで三次元配列までの入力がVecCinRep()にわたす引数の数でそれぞれ対応してくれる。
だがint型なのでオーバーフローすると全滅する儚い命。
これを解決するにはlong long型(最強の数値型)にしておけばとりあえず正解なのですが
まあ、今はいいか……(300点問題以上ではintが力不足らしい)
long longを乱用するために短くしたい場合、
typedef long long ll; //c++11より前はこれ using ll=long long;//c++11以降
これで最強魔法を詠唱短縮して使える。
大体の環境はすでにC++11以降の便利なC++なので好きな方でいいんじゃないかな。
あとC++のテンプレートを使うと型を差し替え可能にできる、らしい(勉強中)
余談2 setの得意不得意
鏡餅にたいして圧倒的優位性を見せつけたset君ですが、vectorとどう使い分けるべきか初心者はわからなくなったので調べてみました!(底質なキュレーションサイト味)
setが得意
- 自動で要素の重複の削除
- 自動で要素のソート
- 要素の挿入削除が早い(log n)
- 要素を検索する時間も早い(log n)
上のコードではset.insert(x)で要素を挿入したけれど、
どうやらset.emplace(x)のほうが若干早いらしい。
setが苦手
- メモリ使用量が多め(vectorの3倍?)
- 要素の全列挙が遅め
- 重複を消してしまう(multisetを使うと重複が許される)
setにしかできないことは特に無く、vectorでも工夫(これが難しい)すれば同じことができるので
setは一分野に突出した才能があるというだけのよう。
戦う場所を選べばsetは強いみたい