ページ 1 / 1
scanf を簡略化したい
Posted: 2018年12月26日(水) 16:12
by m3908714035
~標準入力 を簡略化(?)する関数の作成について~
最近になってiostreamよりもcstdioでの標準入出力が速いことを知りました。
そこで以下のような関数の作成をしようとしています。
関数inの<>内のクラスによって挙動が変わり、in<int>であれば関数の中身が
コード:
int temp; scanf("%d", &temp); return temp;
in<char>であれば関数の中身が
コード:
char temp; scanf("%c", &temp); return temp;
in<string>であれば関数の中身が
コード:
string temp; cin >> temp; return temp;
// string は scanf を使えないためおとなしく cin
となるようにしたいのです。
すると、以下のように変数の宣言と同時に入力が可能となるはずです。
コード:
// example
int a = in<int>(); string b = in<string>();
自分で調べていく中でテンプレートの特殊化を使えば作れると思うのですが、そもそもテンプレートの特殊化をあまり使ったことがなく、また、テンプレートの特殊化について自分が理解できるような文献を見つけることが出来ませんでした。
テンプレートの特殊化を使った場合この関数の実装はどうなるのか、またそれ以外に良い方法があれば教えていただきたいです。
Re: scanf を簡略化したい
Posted: 2018年12月26日(水) 22:16
by みけCAT
「C++ テンプレート 特殊化」でググって最初に出てきた
テンプレートの特殊化 | Programming Place Plus C++編【言語解説】 第23章
を参考に、こんな感じでしょうか。
コード:
#include <iostream>
#include <cstdio>
#include <string>
using std::cin;
using std::string;
template <typename T>
T in() {
T temp; cin >> temp; return temp;
}
template <>
int in() {
int temp; scanf("%d", &temp); return temp;
}
template <>
char in() {
char temp; scanf("%c", &temp); return temp;
}
int main(void) {
int a = in<int>(); string b = in<string>();
printf("%d %s\n", a, b.c_str());
return 0;
}
Re: scanf を簡略化したい
Posted: 2018年12月26日(水) 22:57
by m3908714035
回答ありがとうございます。テンプレートの特殊化に関して使い方が分かった気がします。
書いていただいたコードの入力書式のパターンを増やそうとすると
コード:
using uint = unsigned int;
using lint = long;
using ushort = unsigned short;
using ulong = unsigned long;
template<typename var>
var in() { var temp; cin >> temp; return temp; }
template<>
char in() { char temp; scanf("%c", &temp); return temp; }
template<>
int in() { int temp; scanf("%d", &temp); return temp; }
template<>
uint in() { uint temp; scanf("%u", &temp); return temp; }
template<>
float in() { float temp; scanf("%f", &temp); return temp; }
template<>
short in() { short temp; scanf("%hd", &temp); return temp; }
template<>
lint in() { lint temp; scanf("%ld", &temp); return temp; }
template<>
ushort in() { ushort temp; scanf("%hu", &temp); return temp; }
template<>
ulong in() { ulong temp; scanf("%lu", &temp); return temp; }
template<>
double in() { double temp; scanf("%lf", &temp); return temp; }
と非常に長くなってしまうのですが、template<>を省略する良い方法はあるのでしょうか。
var in() の中で if(typeid(var)==typeid(int)){... のようにif文を複数使うことを検討したのですが、短くなるどころか長くなってしまいました。
Re: scanf を簡略化したい
Posted: 2018年12月27日(木) 04:26
by かずま
m3908714035 さんが書きました: ↑5年前
と非常に長くなってしまうのですが、template<>を省略する良い方法はあるのでしょうか。
マクロを使えば短くはなりますが、いかがでしょうか?
コード:
#include <iostream>
#include <string>
using uint = unsigned int;
using ushort = unsigned short;
using ulong = unsigned long;
template<typename T> T in() { T v; std::cin >> v; return v; }
#define IN(T, F) template<> T in() { T v; scanf(F, &v); return v; }
IN(char, " %c") IN(int, "%d") IN(uint, "%u")
IN(short, "%hd") IN(ushort, "%hu") IN(long, "%ld") IN(ulong, "%lu")
IN(float, "%f") IN(double, "%lf")
int main()
{
char c = in<char>(); std::cout << "c = " << c << "\n";
int i = in<int>(); std::cout << "i = " << i << "\n";
uint u = in<uint>(); std::cout << "u = " << u << "\n";
short h = in<short>(); std::cout << "h = " << h << "\n";
ushort H = in<ushort>(); std::cout << "H = " << H << "\n";
long l = in<long>(); std::cout << "l = " << l << "\n";
ulong L = in<ulong>(); std::cout << "L = " << L << "\n";
float f = in<float>(); std::cout << "f = " << f << "\n";
double d = in<double>(); std::cout << "d = " << d << "\n";
std::string s = in<std::string>(); std::cout << "s = " << s << "l\n";
}
入力例
コード:
A
-1234567890
2147483648
-32768
65535
-2147483648
4294967295
2.2360679
3.1415926535897932
string
出力例
コード:
c = A
i = -1234567890
u = 2147483648
h = -32768
H = 65535
l = -2147483648
L = 4294967295
f = 2.23607
d = 3.14159
s = stringl
" %c" は空白でない文字を入力します。
Re: scanf を簡略化したい
Posted: 2018年12月27日(木) 10:52
by m3908714035
なるほど。マクロを使うという手があったんですね。
とても助かりました。ありがとうございました。
Re: scanf を簡略化したい
Posted: 2018年12月27日(木) 14:33
by usao
呼び出し時にいちいちテンプレート型引数を書かなきゃいけないのがめんどくさそう.
【特定の型に関してはscanf()を使いたいが,他の型(? 少なくともstring)に関してはiostream 使う】みたいな話であれば,
↓のような感じにすればどうか.
コード:
class IN
{
public:
template< class T >
IN &operator>>( T &V ){ std::cin >> V; return *this; }
template<>
IN &operator>>( int &V ){ scanf( "%d", &V ); return *this; }
};
//
int main(void)
{
IN in;
int I;
std::string S;
in >> S >> I;
}
Re: scanf を簡略化したい
Posted: 2018年12月27日(木) 15:18
by m3908714035
usao さんが書きました: ↑5年前
コード:
class IN
{
public:
template< class T >
IN &operator>>( T &V ){ std::cin >> V; return *this; }
template<>
IN &operator>>( int &V ){ scanf( "%d", &V ); return *this; }
};
このような書き方をするとcinと似た形で使えるんですね。回答ありがとうございます。
本題とは少しずれるのですが、cinはiostreamをインクルードしてstd::cin>>s;のように使えますが、
今回提示いただいたコードだと一度IN inと宣言が入ってからin>>s;となっていると思います。
何かコードに工夫をし、IN inの宣言なしにin>>s;と書くことはできるのでしょうか。
Re: scanf を簡略化したい
Posted: 2018年12月27日(木) 22:09
by かずま
m3908714035 さんが書きました: ↑5年前
本題とは少しずれるのですが、cinはiostreamをインクルードしてstd::cin>>s;のように使えますが、
今回提示いただいたコードだと一度IN inと宣言が入ってからin>>s;となっていると思います。
何かコードに工夫をし、IN inの宣言なしにin>>s;と書くことはできるのでしょうか。
IN in; の宣言をなくすのなら、in >> s; を IN() >> s; と書けば
よいのではありませんか?
宣言でオブジェクトを確保するのではなく、コンストラクタの
呼び出しによる一時オブジェクトを使えばよい、ということです。
クラス名を in にし、マクロ名を IN にすると、
コード:
#include <iostream>
#include <string>
using uint = unsigned int;
using ulong = unsigned long;
using ushort = unsigned short;
#define IN(T, F) in& operator>>(T& v) { scanf(F, &v); return *this; }
struct in {
template<typename T> in& operator>>(T& v) { std::cin >> v; return *this; }
IN(char, " %c") IN(int, "%d") IN(uint, "%u")
IN(short, "%hd") IN(ushort, "%hu") IN(long, "%ld") IN(ulong, "%lu")
IN(float, "%f") IN(double, "%lf")
};
int main()
{
char c; int i; uint u; short h; ushort H; long l; ulong L;
float f; double d; std::string s;
in() >> c >> i >> u >> h >> H >> l >> L >> f >> d >> s;
std::cout << c << "\n" << i << "\n" << u << "\n" << h << "\n" << H << "\n"
<< l << "\n" << L << "\n" << f << "\n" << d << "\n" << s << "\n";
}
ところで、本当に scanf は速いんでしょうか?
実行時に書式を解釈しないといけないので遅くなるような気がします。
まあ、実装に依存するんでしょうね。
fgetc や fgets は cin >> c や getline(cin, s) より速そうですが、
getline は、string s なので、サイズの制限がありませんよね。
動的にメモリを確保するから遅いのではありませんか?
また、このやり方だと EOF やエラーの検出はどうするのでしょうか?
Re: scanf を簡略化したい
Posted: 2018年12月28日(金) 00:49
by m3908714035
かずま さんが書きました: ↑5年前
ところで、本当に scanf は速いんでしょうか?
実行時に書式を解釈しないといけないので遅くなるような気がします。
まあ、実装に依存するんでしょうね。
fgetc や fgets は cin >> c や getline(cin, s) より速そうですが、
getline は、string s なので、サイズの制限がありませんよね。
動的にメモリを確保するから遅いのではありませんか?
下記リンク先からの引用となります。
C/C++ におけるデータ入力の速度
1000 万行のテキストファイルを C/C++ で作成したプログラムで読み込むとき,どのくらいの時間がかかるかを調べた結果です。
※ 1. nosync: std::ios_base::sync_with_stdio(false)
※ 2. _IONBF: std::setvbuf(stdin, NULL, _IONBF, 0)
※ 3. _IOLBF: std::setvbuf(stdin, io_buf, _IOLBF, 1048576)
※ 4. _IOFBF: std::setvbuf(stdin, io_buf, _IOFBF, 1048576)
※ 5. real, user, sys: time -f 'real %E, user %U, sys %S'
どうやらfgets>getline(std::ios_base::sync_with_stdio(false)有のもの)>>fgetcとなるようです。関数の中身までは把握しきれていないので、どうしてこのような結果になるのかは分かりませんが…。
また
競プロ初心者が書く「標準入出力からはじめる競プロ入門」 では、10^6個のランダムな符号付き32ビット整数のデータの入力がiostream - 4.96sに対し、cstdio - 0.42sという結果もありました。
scanfは基礎を学ぶ際によく用いていたということもあり、今回はscanfでの入力を考えました。もし、私が勘違いをしていて「やっぱりscanf遅いで!」みたいなことがあれば教えていただけると幸いです。
かずま さんが書きました: ↑5年前
また、このやり方だと EOF やエラーの検出はどうするのでしょうか?
この件に関しては、今回のコードは私の中で競技プログラミングにおけるコードの高速化を想定していたので、考えていませんでした。以前、cinでのエラーやEOFについては考えたことがあり、
コード:
if (cin.fail() && !cin.bad() && !cin.eof()) {
cerr << error << endl; // error は string
cin.clear();
cin.ignore(INT_MAX, '\n');
}
上のようなコード(一部抜粋)を書いた記憶があります。しかし、scanfにおけるエラーやEOFに関しては全くの無知ですので、質問を返すようで申し訳ないのですが、逆に教えていただきたく思います。
Re: scanf を簡略化したい
Posted: 2018年12月28日(金) 10:12
by usao
m3908714035 さんが書きました: ↑5年前
本題とは少しずれるのですが、cinはiostreamをインクルードしてstd::cin>>s;のように使えますが、
今回提示いただいたコードだと一度IN inと宣言が入ってからin>>s;となっていると思います。
何かコードに工夫をし、IN inの宣言なしにin>>s;と書くことはできるのでしょうか。
単純にオブジェクトを1個作ってその名前を公開しておけば良いのではないかと.
(cinだって,単に cinという名前のグローバルなオブジェクトが存在しているだけでしょうから)
Re: scanf を簡略化したい
Posted: 2018年12月28日(金) 12:33
by m3908714035
usao さんが書きました: ↑5年前
単純にオブジェクトを1個作ってその名前を公開しておけば良いのではないかと.
(cinだって,単に cinという名前のグローバルなオブジェクトが存在しているだけでしょうから)
cinの定義をiostrean->iosfwd->istreamと辿ってみると、おっしゃる通りでした。教えて下さりありがとうございます。
Re: scanf を簡略化したい
Posted: 2018年12月28日(金) 13:20
by かずま
scanf は遅いはずだと思って測ってみました。
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
コード:
#include <iostream>
#include <fstream>
#include <cstdio>
#include <ctime>
using namespace std;
const int N = 10000000;
char buf[N];
void read_char()
{
clock_t t0, t1;
FILE *fp;
fp = fopen("/usr/bin/nodejs", "r");
if (!fp) return;
t0 = clock();
for (int i = 0; i < N; i++)
buf[i] = fgetc(fp);
t1 = clock();
fclose(fp);
printf("%f sec: fgetc\n", (t1 - t0) / double(CLOCKS_PER_SEC));
ifstream ifs("/usr/bin/nodejs");
if (!ifs) return;
t0 = clock();
for (int i = 0; i < N; i++)
ifs >> buf[i];
t1 = clock();
ifs.close();
printf("%f sec: ifs >>\n", (t1 - t0) / double(CLOCKS_PER_SEC));
fp = fopen("/usr/bin/nodejs", "r");
if (!fp) return;
t0 = clock();
for (int i = 0; i < N; i++)
fscanf(fp, "%c", &buf[i]);
t1 = clock();
fclose(fp);
printf("%f sec: fscnaf\n", (t1 - t0) / double(CLOCKS_PER_SEC));
}
int main()
{
ios_base::sync_with_stdio(false);
for (int i = 0; i < 3; i++) {
read_char();
puts("---");
}
}
実行結果
コード:
0.172367 sec: fgetc
0.293313 sec: ifs >>
0.599683 sec: fscnaf
---
0.165610 sec: fgetc
0.294515 sec: ifs >>
0.598882 sec: fscnaf
---
0.167828 sec: fgetc
0.294134 sec: ifs >>
0.601244 sec: fscnaf
---
使用したファイルは 10000000バイト以上あります。
コード:
$ wc /usr/bin/nodejs
76561 297132 11187096 /usr/bin/nodejs
Re: scanf を簡略化したい
Posted: 2018年12月28日(金) 13:50
by かずま
最初 getchar, cin >>, scanf でやっていたのを
fgetc, ifs >>, fscanf に変更したので、
sync_with_stdio(false) は無意味になりました。
また、cin >> noskipws; または ifs >> noskipws; を
実行しておかないと、スペースや改行などの空白が読み飛ばされますから、
getchar, scanf("%c" よりもファイルを多く読むことになります。
忘れていました。