質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

意見交換

クローズ

18回答

3030閲覧

二次元配列の中身の後ろに空白を入れるけど末尾だけは空白をつけずに改行を出力する問題

gsgsrt

総合スコア2

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2023/05/06 23:54

編集2023/05/07 00:00

0

2

末尾が改行の二次元配列の書き方

https://atcoder.jp/contests/apg4b/tasks/APG4b_cd?lang=ja
とか
https://paiza.jp/works/mondai/array_utilization_primer/array_utilization_primer__reduse_easy
みたいな二次元配列の中身の後ろに空白を入れるけど末尾だけは空白をつけずに改行を出力する問題ってありますよね

どう書けばいいんでしょうか?

if文を使いたくない

ミスが怖いので末尾かどうかを判定すだけのために条件分岐を書きたくないんです

どう書けばいいかはわかっているが、なにが一番しっくりくるかが知りたいのでQandAではなく意見交換に投稿しました

多分一般的

cpp

1//九九の生成 2#include <iostream> 3using namespace std; 4int main(void) { 5 6 7 for (int i = 1; i <= 9; i++) { 8 9 for (int j = 1; j <= 9; j++) { 10 if (j < 9) 11 std::cout << i * j << " "; //i段目の最後の一回以外空白をつける 12 else 13 std::cout << i * j << std::endl;//最後だけ改行する 14 15 } 16 } 17 18 return 0; 19} 20 21

我流

cpp

1//九九の生成 2#include <iostream> 3#include <string> 4using namespace std; 5int main(void) { 6 7 8 for (int i = 1; i <= 9; i++) { 9 10 string s = "";//sはiループが始まったら空文字列に初期化される 11 for (int j = 1; j <= 9; j++) { 12 13 s += to_string(i * j) + " ";//数字を文字列に変換して全ての数字の後ろに空白をつけたものを文字列sに付け加える 14 15 } 16 s.pop_back();//一文字削除して最後の空白を削る 17 std::cout << s << std::endl; //改行はendlに任せる 18 19 20 } 21 22 return 0; 23}

同じ実行結果

1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81

皆さんはこういう二次元配列の列の要素数がn個、空白の数がn-1個の問題はどうやって記述していますか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答18

#1

Zuishin

総合スコア28669

投稿2023/05/07 00:01

for (int j = 1; j <= 9; j++) {

j <= 9 を j < 9 にし、9 の要素は for が終わった後に出力すれば if は無くなりますね。
string.join を実装する言語は多いので、それを参考に自作しても良いのではないかと思います。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

gsgsrt

総合スコア2

投稿2023/05/07 00:10

編集2023/05/07 00:15

解答ありがとうございます
つまりこういう感じですか?
iとjでループの条件が変わるのが少し怖いですけど、文字列sを作る必要がなくなって行数が減らせました

cpp

1#include <iostream> 2 3using namespace std; 4int main(void) { 5 6 7 for (int i = 1; i <= 9; i++) { 8 for (int j = 1; j < 9; j++) { 9 std::cout << i * j << " "; 10 } 11 std::cout << i*9 << std::endl; 12 } 13 14 return 0; 15}

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

maisumakun

総合スコア146050

投稿2023/05/07 00:31

編集2023/05/07 00:36

ミスが怖いので末尾かどうかを判定すだけのために条件分岐を書きたくないんです

先頭を判定するほうが楽なので、ループの中身を「先頭でなければ改行」→「列の内容を出力」とすれば末尾を判定する必要はなくなります。

…というより、「ミスが怖い」のならテストをキッチリすればいいだけの話です。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

otn

総合スコア85933

投稿2023/05/07 00:41

「if文を使いたくない」という条件が無ければ、

C++

1 for (int i = 1; i <= 9; i++) { 2 for (int j = 1; j <= 9; j++) { 3 std::cout << i * j << (j<9 ? " " : "\n"); 4 } 5 } 6 return 0;

かと思います。終端判断的なこと j<=9j<9を2回書きたくないという気持ちはわからないでもないです。
まあ、普通は9のところは定数定義すると思うので、終端判断を2回書いてしまうのでしょうか。
j==9 : "\n" ? " "と書くか悩むか。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

thkana

総合スコア7703

投稿2023/05/07 01:20

ミスが怖いので末尾かどうかを判定すだけのために条件分岐を書きたくない

for (int i = 1; i <= 9; i++) { ってのは結局i <= 9で末尾を判定している条件分岐なのですが、これは怖くないのでしょうか。

私は条件分岐はあまり怖くありませんが、マジックナンバーをベタ書きするのは怖いので

C++

1//九九の生成 2#include <iostream> 3const int ROW=9; 4const int COL=9; 5int main(void) { 6 for (int i = 1; i <= ROW; i++) { 7 for (int j = 1; j <= COL; j++) { 8 std::cout << i * j; 9 if (j < COL){ 10 std::cout << ' '; 11 }else{ 12 std::cout << std::endl; 13 } 14 } 15 } 16 return 0; 17}

ですかね。

あるいは、「怖さ」の根っこがループの終了判定と事実上同じ判定を重ねることだったりするならば

C++

1for( int i=1; i<=ROW; i++){ 2 int j=0; 3 for(;;){ 4 std::cout << i * j; 5 if(j>=COL){ 6 std::cout<<std::endl; 7 break; 8 } 9 std::cout<<' '; 10 j++; 11 } 12}

とすることでひとつの判定でループの終了と空白/改行の切り替えができます。

何が「怖い」のかを突き詰めていけばその対策も明らかになるのではないでしょうか。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

gsgsrt

総合スコア2

投稿2023/05/07 01:23

編集2023/05/07 01:30

九九の表はほとんどの人が知っているので、マジックナンバーの1と9のままのほうが変数をつけるより九九を出力するということがわかりやすいと思ったからそうしました

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

melian

総合スコア20673

投稿2023/05/07 02:53

編集2023/05/07 02:55

条件分岐を書きたくないんです

以前、似た様な話がありまして、それを真似てみました。

配列の数を順番に表示していく際に、最後に表示された数だけ","をつけないようにしたい

c++

1#include <iostream> 2#include <array> 3 4int main(void) { 5 std::array<std::string, 2> sep = {" ", ""}; 6 for (int i = 1; i <= 9; i++) { 7 for (int j = 1; j <= 9; j++) { 8 std::cout << i*j << sep[j/9]; 9 } 10 std::cout << std::endl; 11 } 12 return 0; 13}

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

kazuma-s

総合スコア8224

投稿2023/05/07 03:52

編集2023/05/07 13:36

ループの中で条件判定をしたくないのなら、

C++

1#include <iostream> 2using namespace std; 3 4int main() 5{ 6 for (int i = 1; i <= 9; i++) { 7 const char *s = ""; 8 for (int j = 1; j <= 9; j++) { 9 cout << s << i * j; 10 s = " "; 11 } 12 cout << endl; 13 } 14}

追記
さらに、i と j 以外の変数を使いたくなければ、

C++

1#include <iostream> 2using namespace std; 3 4int main() 5{ 6 for (int i = 1; i <= 9; i++) { 7 cout << i; 8 for (int j = 2; j <= 9; j++) 9 cout << ' ' << i * j; 10 cout << endl; 11 } 12}

追記2
if文が嫌なだけで、条件判定は使ってよいのなら、

C++

1#include <iostream> 2using namespace std; 3 4int main() 5{ 6 for (int i = 1; i <= 9; i++) 7 for (int j = 1; j <= 9; j++) 8 cout << i*j << "\n "[j<9]; 9} 10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#9

fana

総合スコア11996

投稿2023/05/07 05:38

「要素間にスペースを入れる」という話であれば

後ろに空白を入れる

という捉え方が何か変(?)であるようにも感じます.
素直に(?)考えると「N個目の要素を出力する際に,N>=2 であれば既に前の要素が出力されているのだからスペースを入れるべき」,すなわち,要素の「前に」スペースを入れる感じかなぁ,と.

…という感じなので,私は#8 の2つ目のような形(:まずは先頭要素だけを出力し,以降の要素をループで出力する際に手前にスペースを入れる)に書きますね.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#10

gsgsrt

総合スコア2

投稿2023/05/07 12:35

#9
素直に(?)考えると「N個目の要素を出力する際に,N>=2 であれば既に前の要素が出力されているのだからスペースを入れるべき」

これはすごいわかるんですけど、記述の仕方として最後以外要素の後にスペースを入れる、という書き方のほうが理解されやすいと思ったのでこのタイトルに決めました

if文は嫌だって言っておいてなんですが自分が最初に書いた条件文 if (j < 9) が自分でも理解しにくかったから悩んでいた気がしてきました。

cpp

1if (j < 9)//ぱっと見よくわからない 2 3if(j+1<=9) //隣り合う右側の数字が配列の範囲内なら空白 4 5//もしくは 6bool two_valid_index= 1=<j&&(j+1)<=9; 7 8 if (two_valid_index) 9 std::cout << i * j << " "; //i段目の最後の一回以外空白をつける 10 else 11 std::cout << i * j << std::endl;//最後だけ改行する 12 13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#11

fana

総合スコア11996

投稿2023/05/08 01:44

if (j < 9)//ぱっと見よくわからない

とのことですが,この程度のレベルの不安に関しては,コメントでも付しておけば良い話なのではないかな,とか.
(この程度の事柄を理由にしてあえて変に技巧に走った(?)ようなコードに変えてしまうのではなく,実装自体は愚直な形のままにしておくのが良いような所と思えます.)

cpp

1//例えばこんなのを思いついたとして,コレを採用するか?っていったら,自分なら嫌かな. 2int i = 1; 3do{ std::cout << i; }while( ( ++i <= 9 ) && (std::cout<<',') ); 4std::cout << std::endl;

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#12

SaitoAtsushi

総合スコア5686

投稿2023/05/11 03:09

C++20 から range という概念が導入されました。 従来はなんらかの「範囲」を扱うためには開始と終了のイテレータを使っていましたがこれを一体にしたものと考えるとよいでしょう。 range の導入と同時にそれを扱うための機構も整備され、パイプをつなぐように各要素を加工していけるという特徴があります。

C++23 ではそれらの機構に join_with が追加され、要素の「間」に要素を追加するするという加工が簡単に出来るようになりました。

九九の表を書くならこのように使えます。

cpp

1#include <algorithm> 2#include <iostream> 3#include <ranges> 4 5int main(void) { 6 std::ranges::for_each(std::views::iota(1, 10), [](int y) { std::ranges::for_each(std::views::iota(1, 10) | std::views::transform([y](int x) { return std::to_string(x * y); }) | std::views::join_with(' '), [](auto ch){std::cout << ch;}); std::cout << std::endl; }); 7}

条件分岐が (直接には) 含まれないコードになりました。

が……、これが最初の要素 (または最後の要素) を特別扱いするよりわかりやすいとか間違えにくい書き方になっているとは思えません。 こういった凝った仕組みは複雑なものをマシにするために使うものであって簡単なものを当てはめようとすると迂遠すぎるのはよくあることです。

質問の事例程度の簡単なコードは愚直に条件分岐するのがよいように思います。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#13

episteme

総合スコア16612

投稿2023/05/11 10:51

C++

1#include <iostream> 2 3int main(void) { 4 for (int i = 1; i <= 9; i++) { 5 for (int j = 1; j <= 9; j++) { 6 std::cout << i * j << ((j == 9) ? '\n' : ' '); 7 } 8 } 9}

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#14

xebme

総合スコア1090

投稿2023/05/11 21:41

編集2023/05/12 23:46

(j%9 == 0)

c++

1#include <iostream> 2#include <cmath> 3//template <typename T> int sgn(T val) { 4// return (T(0) < val) - (val < T(0)); 5//} 6int main(void) { 7 for (int i = 1; i <= 9; i++) 8 for (int j = 1; j <= 9; j++) 9 //std::cout << i * j << (char)('\n' + (' '-'\n') * sgn(j%9)); 10 //std::cout << i * j << (char)('\n' + (' '-'\n') * std::ceil(j%9/9.0)); 11 //std::cout << i * j << (char)(' ' - (' '-'\n') * std::pow(0,j%9)); 12 std::cout << i * j << (char) (' ' - (' '-'\n') * (j%9 == 0)); 13 return 0; 14}

あれこれやってみたら(j%9 == 0)でした。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#15

gsgsrt

総合スコア2

投稿2023/05/12 12:15

#12
Zen of Pythonの
単純であることはよいことだ、
しかし、ややこしいよりは複雑である方がいい
ということですかね

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#16

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/05/14 07:05

C++14以降(clangは16~)で動く無理矢理テンプレートのSFINAEとパラメータパックを使って書いてみました。
オススメしてるわけではないです。

cpp

1#include <iostream> 2#include <utility> 3template<typename Out, typename Char, typename Elem> 4void print_content(Out& out, const Char* separator, Elem head) { 5 out << head; 6} 7template<typename Out, typename Char, typename Elem, typename... Elems> 8void print_content(Out& out, const Char* separator, Elem head, Elems... tails) { 9 out << head << separator; 10 print_content(out, separator, tails...); 11} 12template<typename Out, typename Char, typename T, T... SEQ> 13void print(Out& out, const Char* separator, const Char* post, std::integer_sequence<T, SEQ...>) { 14 print_content(out, separator, SEQ...); 15 out << post; 16} 17template<typename T, T NUM> 18constexpr T add(T value) { 19 return value + NUM; 20} 21template<typename T, T NUM> 22constexpr T mul(T value) { 23 return value * NUM; 24} 25template<typename Func, Func FUNC, typename T, T... SEQ> 26constexpr auto make_sequence(std::integer_sequence<T, SEQ...>) { 27 return std::integer_sequence<T, FUNC(SEQ)...>(); 28} 29template<typename T, T... SEQ> 30constexpr auto make_sequence_add1(std::integer_sequence<T, SEQ...> seq) { 31 return make_sequence<T(*)(T), add<T, 1>>(seq); 32} 33template<typename T, T N, T... SEQ> 34constexpr auto make_sequence_mul_n(std::integer_sequence<T, SEQ...> seq) { 35 return make_sequence<T(*)(T), mul<T, N>>(seq); 36} 37template<typename T, T N, T... SEQ, typename Out> 38void print_kuku_n(Out& out, std::integer_sequence<T, SEQ...> seq) { 39 print(out, " ", "\n", make_sequence_mul_n<T, N>(seq)); 40} 41template<typename T, typename Out, T... SEQX, T HEADY> 42void print_kuku(Out& out, std::integer_sequence<T, SEQX...> seqx, std::integer_sequence<T, HEADY>) { 43 print_kuku_n<T, HEADY>(out, seqx); 44} 45template<typename T, typename Out, T... SEQX, T HEADY, T... TAILY> 46void print_kuku(Out& out, std::integer_sequence<T, SEQX...> seqx, std::integer_sequence<T, HEADY, TAILY...>) { 47 print_kuku_n<T, HEADY>(out, seqx); 48 print_kuku<T, Out, SEQX...>(out, seqx, std::integer_sequence<T, TAILY...>()); 49} 50const int ROW = 9; 51const int COL = 9; 52int main() { 53 const auto rowseq = make_sequence_add1(std::make_index_sequence<ROW>()); 54 const auto colseq = make_sequence_add1(std::make_index_sequence<COL>()); 55 using value_type = typename decltype(rowseq)::value_type; 56 print_kuku(std::cout, colseq, rowseq); 57 std::cout.flush(); 58 return 0; 59}

個人的には安全で遅くなければ何でもいいです(提示コードは速度未検証)。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#17

lehshell

総合スコア1156

投稿2023/05/15 15:10

C++

1#include <iostream> 2#include <vector> 3#include <string> 4#include <algorithm> 5#include <numeric> 6 7int main() 8{ 9 std::vector<int> vi(9); 10 std::iota(vi.begin(), vi.end(), 1); 11 for (auto n : vi) { 12 std::string s = std::accumulate(vi.begin(), vi.end(), std::string(), 13 [n](std::string acc, int x) {return acc + std::to_string(n * x) + " ";}); 14 std::cout << (s.pop_back(), s) << std::endl; 15 } 16}

C++

1#include <iostream> 2 3int main() 4{ 5 for (int i = 1; i <= 9; i++) { 6 for (int j = 1; j <= 9; j++) { 7 std::cout << i * j << " \n"[j == 9]; 8 } 9 } 10}

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#18

red_snow

総合スコア21

投稿2023/05/26 08:04

ミスが怖いなら、表示する関数をあらかじめ作っておいて、
それを徹底的にテストしておき、それを使いまわす方がいいと思います。

むしろ表示でミスる分だけなら、わかりやすいしその後に影響も与えないので
変換処理をいれるよりいいと思います。
条件分岐への心配が変換処理への心配に代わるだけでしょうから。

末尾判定の条件分岐を過剰に怖がらず、慣れる方がいいと思います。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問