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

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

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

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

継承

継承(インヘリタンス)はオブジェクト指向プログラミングに存在するシステムです。継承はオブジェクトが各自定義する必要をなくし、継承元のオブジェクトで定義されている内容を引き継ぎます。

Q&A

解決済

4回答

494閲覧

九九出題プログラムで「7×8=?」「?×?=56」という別種類の問題を共通化or個別化して処理すべきか。

hinatahinata

総合スコア29

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

継承

継承(インヘリタンス)はオブジェクト指向プログラミングに存在するシステムです。継承はオブジェクトが各自定義する必要をなくし、継承元のオブジェクトで定義されている内容を引き継ぎます。

0グッド

1クリップ

投稿2020/03/19 00:07

編集2020/03/19 01:16

九九のプログラムを作成しています。
0. 問題を作成しユーザーに出題。
0. ユーザーが答えを入力すると正解か間違いかを判定し返す。
0. 間違いの場合、正答を表示する。

という流れです。

問題には3タイプあり
0. 通常:3×4=?
0. 穴埋め:3×?=12
0. 穴埋め2:?×?=12

のいずれかがランダムで出題されます。

以下のような実装を考えてみました。

IQuestion

1public interface IQuestion { 2 bool IsCorrect(int answer); 3 int GetCorrectAnswer(); 4}

QuestionNormal

1public class QuestionNormal : IQuestion { 2 int factor1; // 因数1 3 int factor2; // 因数2 4 5 bool IsCorrect(int answer) { 6 return factor1 * factor2 == answer; 7 } 8 9 int GetCorrectAnswer() { 10 return factor1 * factor2; 11 } 12 13}

QuestionAnaumeSingle

1public class QuestionAnaumeSingle: IQuestion { 2 int factor; // 因数 3 int product; // 積 4 5 bool IsCorrect(int answer) { 6 return factor * answer == product; 7 } 8 9 int GetCorrectAnswer() { 10 return product / factor; 11 } 12 13}

KukuGenerator

1public class KukuGenerator() { 2 IQuestion GenerateQuestion() { 3 // 問題3タイプのうちどれかを生成し返す 4 } 5}

main

1// 九九の問題を作成(3タイプのうちどれか) 2var question = kukuGenerator.GenerateQuestion(); 3// ユーザーからの入力を受け付け 4var 入力 = inputProvider.GetAnswer(); 5// 入力された回答が合っているかのチェック 6bool isCorrect = question.IsCorrect(入力); 7 8if (isCorrect) { 9 Console.Write("正解"); 10} else { 11 Console.Write("不正解"); 12 // 不正解なら正解を取得 13 var correctAnswer = question.GetCorrectAnswer(); 14 // 正解を表示 15 // 問題タイプ「通常」なら「12」と表示 16 // 問題タイプ「穴埋め2」なら「(2,6) (3,4) (4,3) (6,2)」と表示 17 // 可能であれば文字列でなく、数値で受け取り、それを別のViewに投げて表示したい 18} 19

##困ったこと
ここで困ったことが穴埋め2の実装です。
問題タイプによってユーザーからの回答受付数と正解の個数が以下の様に異なるため
穴埋め2のみ、回答数と正解個数が違うためうまく共通化できません。

通常:3×4=? 回答数1, 正解個数1,正解は12。
穴埋め:3×?=12 回答数1, 正解個数1,正解は4。
穴埋め2:?×?=12 回答数2, 正解個数4,正解は(2,6) (3,4) (4,3) (6,2)。

##考えたこと
インターフェースを複数の回答、正解に対応するよう変更し、問題タイプ1,2もそれに合わせて実装する。

IQuestion

1public interface IQuestion { 2 bool IsCorrect(List<int> answers); 3 List<List<int>> GetCorrectAnswers(); 4}

しかしこれだと問題タイプ3を共通化したいがために無理やり作った感がでるためしっくりきていません。
個別にゴリゴリに書けば動作するものは作れるのですが、こういった場合に上手い実装の仕方はないでしょうか。

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

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

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

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

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

Zuishin

2020/03/19 00:16

正解を出題者があらかじめ入力しておかなくてもその都度計算すればいいのでは?
Zuishin

2020/03/19 00:49 編集

こんな感じに簡単に書いても良さそうです。 public abstract class Question { public abstract bool IsCorrect(params int[] answers); } public class Question1 : Question { public Question1() { var random = new Random(); Factors = Enumerable.Range(0, 2).Select(_ => random.Next(9) + 1).ToList(); Answer = Factors[0] * Factors[1]; } public IReadOnlyList<int> Factors { get; private set; } public int Answer { get; private set; } public override string ToString() => $"{Factors[0]}×{Factors[1]}=□"; public override bool IsCorrect(params int[] answers) => answers.Length == 1 && answers[0] == Answer; }
hinatahinata

2020/03/19 00:44

コメントありがとうございます。 「その都度計算する」という場合、どのクラスの役割として実装すべきでしょうか。 なるべくmain側にロジックが染み出さず、mainの変更が少なくなるようするのがスマートかなと思うのですが、上手く思い浮かびません。 そもそもの設計の仕方が良くないのでしょうか。 実装例を記述していただけると大変助かります。
Zuishin

2020/03/19 00:51

IsCorrect がおかしかったので直しました。実際にコンパイルして確かめたわけではないので他にもおかしいところがあるかもしれません。一例として見てください。
hinatahinata

2020/03/19 00:54

コメントかぶってしまい申し訳ありません。 実装例ありがとうございます。 この例では基底クラスを予め回答の受付数を可変にしておくことで全ての問題タイプに対応するということですね。 問題クラスの役割として ・回答が合ってるかを判定 ・正解を返す という役割を考えましたが、 Zuishinさんの前のコメントでの「正解を出題者があらかじめ入力しておかなくてもその都度計算すればいいのでは?」というお考えでは、「問題の正解を教えてくれる」役割はどこが担うべきでしょうか。
Zuishin

2020/03/19 01:12 編集

正解は IsCorrect で出ます。この考えではオーバーライドすべきは問題を成形する ToString と判定する IsCorrect のみです。 編集前のコメントではタイプ 3 の IsCorrect を書いてしまっていました。 public override bool IsCorrect(params int[] answers) => answers.Length == Factors.Count && answers.Aggregate((a, b) => a * b) == Answer; 要するに answers には空白部分をユーザーが埋めたものが順に入るので、それを計算して出題の通りになれば正解ということです。タイプ 3 の場合は answers の要素数が 2 で、それらをすべてかけたものがあらかじめ計算しておいた Answer と一致すれば正解です。正解した時、IsCorrect は true を返します。 クラスを三通り作れば三タイプの問題に対応できます。
hinatahinata

2020/03/19 01:13

コメントありがとうございます。 申し訳ありません、私の質問が不十分でした。 最終的に不正解の場合に正解パターンを全て表示したいと考えています。 つまり 3x4=? の場合 12 3x?=12 の場合 4 ?x?=12 の場合 (2,6) (3,4) (4,3) (6,2) と表示したいのです。 ・・・とここまでコメント頂いた中で思ったのですが string GetCorrectAnswer() にして内部で正解パターンの文字列を生成し返せばよいのでは?と思いました。 (正解パターンごとに分ける場合は List<string>で) もしくは数字で欲しければ、やはり返り値をList<List<int>>とかにするのがよいのでしょうか。
Zuishin

2020/03/19 01:19 編集

string でいいと思います。ユーザーに表示する他に使い道のない値なので。もちろん List<List<int>> でもいいと思います。
takabosoft

2020/03/19 01:28

横から割り込んじゃいますが、正解パターンが必須であるのならば、 List<List<int>> GetCorrectAnswers();として派生クラスでそれぞれ実装しておけば、 IsCorrectの方は派生クラスで処理しなくても、GetCorrectAnswers()の結果と一致してればOK的な処理を基本クラスかどこかに一回書いておけば済みそうかなとは思いました。
Zuishin

2020/03/19 01:50

確かにその通りですね。
hinatahinata

2020/03/19 03:12

Zuishinさん、takabosoftさん、コメントありがとうございます。 非常に参考になりました。 >Zuishinさん 私の拙い質問に細かく回答頂き非常に勉強になりました。 もしよろしければベストアンサーにさせていただきたいので、お時間があれば回答として投稿いただけますでしょうか?
Zuishin

2020/03/19 03:21

ありがとうございます。ですが私は一例を適当に書いただけなので、もっと相応しい回答があるのではないかと思いますから辞退します。
hinatahinata

2020/03/19 03:38

承知いたしました。 度々の回答、誠にありがとうございました。 質問と回答をしていく中で自分の考えがまとまっていくのを実感しました。 感謝します。
guest

回答4

0

ベストアンサー

自分だったらどうするかなーと、いろいろいじった結果だけ載せておきます。

csharp

1namespace ConsoleApp1 2{ 3 /// <summary> 4 /// 九九の問題定義クラス 5 /// </summary> 6 internal class KukuQuestion 7 { 8 /// <summary> 9 /// 被乗数 10 /// nullなら?とします。 11 /// </summary> 12 internal int? Multiplicand { get; set; } 13 14 /// <summary> 15 /// 乗数 16 /// nullなら?とします。 17 /// </summary> 18 internal int? Multiplier { get; set; } 19 20 /// <summary> 21 /// 積 22 /// nullなら?とします。 23 /// </summary> 24 internal int? Product { get; set; } 25 26 /// <summary> 27 /// 正解パターン 28 /// </summary> 29 internal int[][] CorrectAnswers { get; set; } 30 31 /// <summary> 32 /// 問題を文字列化します。 33 /// </summary> 34 /// <returns></returns> 35 public override string ToString() 36 { 37 Func<int?, string> Conv = v => v?.ToString() ?? "?"; 38 return $"{Conv(Multiplicand)}×{Conv(Multiplier)}={Conv(Product)}"; 39 } 40 41 /// <summary> 42 /// ユーザーが入力しないといけない数字の数を取得します。 43 /// 文字列処理でも良かったかもね。 44 /// </summary> 45 internal int NumberOfQuestion => (Multiplicand == null ? 1 : 0) + (Multiplier == null ? 1 : 0) + (Product == null ? 1 : 0); 46 47 /// <summary> 48 /// 正解判定を行います。 49 /// </summary> 50 /// <param name="answer"></param> 51 /// <returns></returns> 52 internal bool IsCorrect(int[] answer) 53 { 54 return CorrectAnswers.Any(a => a.SequenceEqual(answer)); 55 } 56 } 57 58 59 class Program 60 { 61 static void Main(string[] args) 62 { 63 var q = new KukuQuestion() 64 { 65 Multiplicand = 3, 66 Multiplier = 5, 67 CorrectAnswers = new int[][] { new int[] { 15 } }, 68 }; 69 70 Console.WriteLine($"問題:{q}"); 71 Console.WriteLine($"?の数:{q.NumberOfQuestion}"); 72 Console.WriteLine($"正解判定:{q.IsCorrect(new[] { 15 })}"); 73 74 } 75 } 76}

実行結果:

問題:3×5=? ?の数:1 正解判定:True

3パターンとも、あらかじめ問題と正解が固定値(不変値(あとから変わらない))なので、問題クラスとしてはただの入れ物として定義しました。
問題を生成する方は上には書いてないので頑張って実装する必要がありますが、
そこにポリモーフィズムを積極的に使う理由は今の所あんまり無いかなぁと思います。

※そもそも派生やインターフェースを使うための課題とかだったらごめんなさい

投稿2020/03/19 02:06

編集2020/03/19 02:11
takabosoft

総合スコア8356

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

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

hinatahinata

2020/03/19 03:18

回答ありがとうございます。 詳しい実装例も載せていただいて非常に参考になりました。 「EDGE」の作者様だったのですね!使わせていただいてます!
guest

0

IsCorrect()の引数は無しにして,
代わりに,
必要な回答の個数分だけの回答値入力手段をIQuestionから得る…みたいな.

public interface IQuestion { /// <summary> /// 回答の入力手段を得る /// </summary> /// <returns> /// 必要な個数分の,回答値を入力する手段 /// </returns> Action<int>[] GetAnswerInputWays(); /// <summary> /// (現在入力されている)回答が正解かどうかをチェック /// </summary> /// <returns>正解ならtrue</returns> bool IsCorrect(); }

IQuestionの具体実装と,それを使う側の雰囲気は,こんな感じでしょうか.

/// <summary> /// □ * □ = あらかじめ定められた数 /// という,2つの答えを入力するタイプの穴埋め問題. /// </summary> public class QuestionImpl : IQuestion { /// <summary> /// ctor /// </summary> /// <param name="TgtValue"> /// □ * □ = あらかじめ定められた数 /// という問題の右辺の値を指定. /// </param> public QuestionImpl( int TgtValue ){ m_TgtValue = TgtValue; } public Action<int>[] GetAnswerInputWays() { return new Action<int>[] { (int _)=>{ m_Ans1=_; }, (int _)=>{ m_Ans2=_; } }; } public bool IsCorrect(){ return (m_Ans1 * m_Ans2 == m_TgtValue); } //入力された値 //※まともにやるなら,入力前に IsCorrdct() がtrueにならないように「入力を行ったか?」がわかるような方法にする必要かもしれない. private int m_Ans1 = 0; private int m_Ans2 = 0; //ctorで指定された値 private readonly int m_TgtValue; } class Program { static void Main(string[] args) { IQuestion Q = new QuestionImpl( 15 ); var InputActs = Q.GetAnswerInputWays(); //※InputActsの個数をチェックして,その分だけ入力処理を行うことになるが, // ここでは面倒なので,その結果として3と4が入力されたのだとする. InputActs[0]( 3 ); InputActs[1]( 4 ); Console.WriteLine( Q.IsCorrect().ToString() ); //false //2番目の答えを入力し直す InputActs[1]( 5 ); Console.WriteLine( Q.IsCorrect().ToString() ); //true } }

投稿2020/03/19 01:54

編集2020/03/19 04:28
fana

総合スコア11656

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

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

hinatahinata

2020/03/19 03:49

コメントありがとうございます。 Action<int>[] は引数がintの配列、戻り値が無いdelegateですよね。 勉強不足で申し訳ありません、実装と処理の流れがよくわからないのですが、 これをmain側でどのように使用し、IQuestionの実装クラス側ではどのように実装するのか例をご提示いただけないでしょうか。
fana

2020/03/19 04:30

Action<int> は,引数がintで戻り値無しのdelegateで, Action<int>[] は Action<int> の配列ですね. とりあえず雰囲気を追記してみました.
hinatahinata

2020/03/19 04:59

回答ありがとうございます。 なるほど!こういった実装方法もあるのですね、勉強になりました!
guest

0

インターフェイスをジェネリクスにして IQuestion<T> にする
回答が全てintではなかったですね。すみません。

csharp

1public class CalcQuestion : IQuestion<int, int> 2{ 3 private readonly int First; 4 private readonly int Second; 5 public CalcQuestion(int x, int y) 6 { 7 First = x; 8 Second = y; 9 } 10 11 public string Inquire() => $"{First} x {Second} = ?"; 12 13 public bool IsCorrect(int answer) => answer == First * Second; 14 15 public int GetCorrectAnswer() => First * Second; 16} 17public class CombinationQuestion : IQuestion<(int, int), IEnumerable<(int, int)>> 18{ 19 private readonly int Prod; 20 private readonly IEnumerable<(int, int)> Pairs; 21 public CombinationQuestion(int x) 22 { 23 Prod = x; 24 Pairs = Enumerable.Range(1, x).Select(i => x % i == 0 ? (i, x / i) : (0, 0)).Where(t => t.Item1 != 0); 25 } 26 27 public string Inquire() => $"? x ? = {Prod}"; 28 public bool IsCorrect((int, int) answer) => Pairs.Contains(answer); 29 30 public IEnumerable<(int, int)> GetCorrectAnswer() => Pairs; 31} 32 33public interface IQuestion<in T, out U> 34{ 35 string Inquire(); 36 bool IsCorrect(T answer); 37 U GetCorrectAnswer(); 38}

投稿2020/03/19 00:14

編集2020/03/19 15:25
papinianus

総合スコア12705

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

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

hinatahinata

2020/03/19 00:56

回答ありがとうございます。 ジェネリクスにすることでこの問題が解決できる例が上手く思い浮かばないのですが もしよければ実装例を記述していただけないでしょうか。 IQuestion<T>にした場合 IsCorrect(T answer) T GetCorrectAnswer() のように実装するのかなと考えましたが、回答数と正解個数は共通化できないと思うので実装例が浮かびませんでした。
hinatahinata

2020/03/22 01:40

回答ありがとうございます。 なるほど!こういう方法もあるのですね。勉強になります。
guest

0

本質問に回答するまでC#を書いたことがなかったため、間違っている可能性もありますが、質問者様のアイデアの種となれば幸いです。

c#

1 2 3public class Answer { 4 readonly int? single; 5 readonly Dictionary<int, int>? multiple; 6 7 public Answer(int? single, Dictionary<int, int>? multiple) { 8 this.single = single; 9 this.multiple = multiple; 10 } 11 12 public int? GetSingle() { 13 return single; 14 } 15 16 public Dictionary<int, int>? GetMultiple() { 17 return multiple; 18 } 19} 20 21public interface IQuestion { 22 bool IsCorrect(int firstAnswer, int? secondAnswer = null); 23 Answer GetCorrectAnswer(); 24} 25 26 27public class QuestionNormal : IQuestion { 28 int factor1; // 因数1 29 int factor2; // 因数2 30 31 bool IsCorrect(int firstAnswer, int? secondAnswer = null) { 32 if (secondAnswer != null) throw new Exception(); 33 return factor1 * factor2 == firstAnswer; 34 } 35 36 Answer GetCorrectAnswer() { 37 return new Answer(factor1 * factor2, null); 38 } 39} 40 41public class QuestionAnaumeSingle: IQuestion { 42 int factor; // 因数 43 int product; // 積 44 45 bool IsCorrect(int firstAnswer, int? secondAnswer = null) { 46 if (secondAnswer != null) throw new Exception(); 47 return factor * firstAnswer == product; 48 } 49 50 Answer GetCorrectAnswer() { 51 return new Answer(product / factor, null); 52 } 53} 54 55public class QuestionAnaumeDouble: IQuestion { 56 int product; // 積 57 58 bool IsCorrect(int firstAnswer, int? secondAnswer = null) { 59 if (secondAnswer == null) throw new Exception(); 60 return firstAnswer * secondAnswer == product; 61 } 62 63 Answer GetCorrectAnswer() { 64 Dictionary<int, int>? multiple = new Dictionary<int, int>(); 65 // 省略:解答の組み合わせを辞書に重複しないように追加 66 return new Answer(null, multiple); 67 } 68}

投稿2020/03/21 15:47

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

hinatahinata

2020/03/22 01:43

回答ありがとうございます。 「正答」のクラスを別に定義するパターンですね。 勉強になります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問