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

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

新規登録して質問してみよう
ただいま回答率
85.51%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

425閲覧

穴掘り法迷路生成のLinqについて。

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

1グッド

0クリップ

投稿2019/01/14 06:01

編集2019/01/14 06:07

前提・実現したいこと

こちらのサイトで穴掘り法の迷路生成の勉強をしているのですが、Linqの処理が何をしているのかわかりません。
ご教示お願いします。

該当のソースコード

ソースのGetPositionメソッドの中の下記のLinqがわかりません。

C#

1Dictionary<int, int[]> positions = position.Where(p => !isOutOfRange(p[0], p[1]) && walls[p[0], p[1]] == 0) 2 .Select((p, i) => new { p, i }) 3 .ToDictionary(p => p.i, p => p.p);

試したこと

理解したことは、下記です。

・Whereの中のpは、positionの要素を示している。  つまり、 new int[] {x, y + 2}, new int[] {x, y - 2}, new int[] {x + 2, y}, new int[] {x - 2, y}  のどれかの要素となる。 ・Whereでやっていることは、  pが、!isOutOfRange(p[0], p[1]) && walls[p[0], p[1]] == 0 の条件を満たしているものでフィルタリングしている。

わからないことは下記です。

・.Select((p, i) => new { p, i }) の処理がわかりません。  (p, i)のpとiは何を示しているのか。  new { p, i }は何を生成しているのか。 { }をnewしていますが、この{ }は何を意味しているのか。 ・.ToDictionary(p => p.i, p => p.p);  どんなDictionaryを生成しているのかわかりません。  ToDictionaryの処理そのものはわかります。  p.iをKeyに、p.pをValueにしたDictionaryを生成していることはわかります。  しかし、p.iとp.pが何を示しているのかわかりません。

GetPositionで移動可能なリストを取得するということですが、
GetPositionで取得されるDictionary<int, int[]>は、どういうDictionaryになっているのか、
ご教示お願いします。

あと、所々、

walls[tmpPos[0], tmpPos[1]] = 1; walls[xPos, yPos] = 1;

という記述がありますが、
サイトの図の説明のように、Y座標が下方向、X座標が右方向とするならば、
2次元配列の性質から考えると、

walls[tmpPos[1], tmpPos[0]] = 1; walls[yPos,xPos] = 1;

のほうが合っているような気がしますが、これに関してご教示お願いします。

bochan2👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

LINQの Select 句では、前の行で出力された値 p とint型 i をもつ匿名型に変換しています。
ToDictionary 句では、iKey に、pValue とするDictionaryに変換しています。

シンプルな例に置き換えると以下のとおりです。

using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Linq; public class LinqTest : MonoBehaviour { void Start () { var list = new List<string>(); list.Add ( "AAA" ); list.Add ( "BBB" ); list.Add ( "CCC" ); // (1) で、元のリストの値 x と、int型 i を持つ匿名型(のIEnumerable)に変換 // (2) で、i を Key、x を Value とする Dictionary に変換 var dictionary = list.Select((x, i) => new { x, i} ) // (1) .ToDictionary(d => d.i, d => d.x); // (2) // 確認用 foreach ( var item in dictionary ) { Debug.Log ( $"{item.Key}, {item.Value}" ); } } }

結果は下図のとおりです。
イメージ説明

参考
型推論と匿名型 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
https://ufcpp.net/study/csharp/sp3_inference.html

なお元のコードにおいては、わざわざ持たせた Key を後工程で特に使用していないようですね。

【追記1】
変数がどのような型なのかは、Visual Studioであればその箇所にマウスカーソルを乗せれば表示されますよ。
イメージ説明
LINQのなかでどのように変換されているか、ご自身でたしかめてみるのが早いと思います。

【追記2】
参考として、元のコードをこちらであれこれ書き換えて動かしてみたものを載せておきます。
変数名・メソッド名を含めて弄り回したので読み取りにくいかと思いますが、Dictionaryへの変換を行っていないことだけ確認してください。Listで処理しています。これでも同じように動くと思います。

using UnityEngine; using System.Collections.Generic; using System.Linq; public class MakeDungeon : MonoBehaviour { // 設定する値 public int max = 5; //縦横のサイズ ※必ず奇数にすること public GameObject wall; //壁用オブジェクト public GameObject floor; //床用オブジェクト public GameObject start; //スタート地点に配置するオブジェクト public GameObject goal; //ゴール地点に配置するオブジェクト // 内部パラメータ private enum CellType { Wall, Path }; //セルの種類 private CellType[,] cells; private Vector2Int startPos; //スタートの座標 private Vector2Int goalPos; //ゴールの座標 private void Start () { //マップ状態初期化 cells = new CellType[max, max]; //スタート地点の取得 startPos = GetStartPosition (); //通路の生成 //初回はゴール地点を設定する goalPos = MakeMapInfo ( startPos ); //通路生成を繰り返して袋小路を減らす var tmpStart = goalPos; for ( int i = 0 ; i < max * 5 ; i++ ) { MakeMapInfo ( tmpStart ); tmpStart = GetStartPosition (); } //マップの状態に応じて壁と通路を生成する BuildDungeon (); //スタート地点とゴール地点にオブジェクトを配置する //初回で取得したスタート地点とゴール地点は必ずつながっているので破綻しない var startObj = Instantiate(start, new Vector3(startPos.x, 1, startPos.y), Quaternion.identity); var goalObj = Instantiate(goal, new Vector3(goalPos.x, 1, goalPos.y), Quaternion.identity); startObj.transform.parent = this.transform; goalObj.transform.parent = this.transform; } // スタート地点の取得 private Vector2Int GetStartPosition () { //ランダムでx,yを設定 int randomX = Random.Range(0, max); int randomY = Random.Range(0, max); //x、yが両方共偶数になるまで繰り返す while ( !( randomX % 2 == 0 && randomY % 2 == 0 ) ) { randomX = Mathf.RoundToInt ( Random.Range ( 0, max ) ); randomY = Mathf.RoundToInt ( Random.Range ( 0, max ) ); } return new Vector2Int ( randomX, randomY ); } // マップ生成 private Vector2Int MakeMapInfo ( Vector2Int _startPos ) { //スタート位置配列を複製 var tmpStartPos = _startPos; //移動可能な座標のリストを取得 var movablePositions = GetMovablePositions(tmpStartPos); //移動可能な座標がなくなるまで探索を繰り返す while ( movablePositions != null ) { //移動可能な座標からランダムで1つ取得し通路にする var tmpPos = movablePositions[Random.Range(0, movablePositions.Count)]; cells[tmpPos.x, tmpPos.y] = CellType.Path; //元の地点と通路にした座標の間を通路にする var xPos = tmpPos.x + (tmpStartPos.x - tmpPos.x) / 2; var yPos = tmpPos.y + (tmpStartPos.y - tmpPos.y) / 2; cells[xPos, yPos] = CellType.Path; //移動後の座標を一時変数に格納し、再度移動可能な座標を探索する tmpStartPos = tmpPos; movablePositions = GetMovablePositions ( tmpStartPos ); } //探索終了時の座標を返す return tmpStartPos; } // 移動可能な座標のリストを取得する // private Dictionary<int, Vector2Int> GetPosition ( Vector2Int _startPos ) private List<Vector2Int> GetMovablePositions ( Vector2Int _startPos ) { //可読性のため座標を変数に格納 var x = _startPos.x; var y = _startPos.y; //移動方向毎に2つ先のx,y座標を仮計算 var positions = new List<Vector2Int> { new Vector2Int(x, y + 2), new Vector2Int(x, y - 2), new Vector2Int(x + 2, y), new Vector2Int(x - 2, y) }; //移動方向毎に移動先の座標が範囲内かつ壁であるかを判定する //真であれば、返却用リストに追加する var movablePositions = positions.Where(p => !IsOutOfBounds(p.x, p.y) && cells[p.x, p.y] == CellType.Wall); return movablePositions.Count () != 0 ? movablePositions.ToList () : null; } //与えられたx、y座標が範囲外の場合真を返す private bool IsOutOfBounds ( int x, int y ) => ( x < 0 || y < 0 || x >= max || y >= max ); //パラメータに応じてオブジェクトを生成する private void BuildDungeon () { //縦横1マスずつ大きくループを回し、外壁とする for ( int i = -1 ; i <= max ; i++ ) { for ( int j = -1 ; j <= max ; j++ ) { //範囲外、または壁の場合に壁オブジェクトを生成する if ( IsOutOfBounds ( i, j ) || cells[i, j] == CellType.Wall ) { var wallObj = Instantiate(wall, new Vector3(i, 0, j), Quaternion.identity); wallObj.transform.parent = this.transform; } //全ての場所に床オブジェクトを生成 var floorObj = Instantiate(floor, new Vector3(i, -1, j), Quaternion.identity); floorObj.transform.parent = this.transform; } } } }

上の(書き換えたほうの)コードで動かしてみたGIFです。
イメージ説明

投稿2019/01/14 08:05

編集2019/01/14 16:18
negitama

総合スコア943

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

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

退会済みユーザー

退会済みユーザー

2019/01/14 15:16

ご回答ありがとうございます。 念の為、確認ですが、 list.Select((x, i) => new {x, i} ) とあったら、new {x, i}は、new{IEnumerable型の要素(元のリストの値), IEnumerable型の要素番号} になるということですか? >なお元のコードにおいては、わざわざ持たせた Key を後工程で特に使用していないようですね。 int[] tmpPos = movePos[Random.Range(0, movePos.Count)]; で使用していませんか? movePosの[ ]内がKeyではないですか? 今回、 list.Select((x, i) => new { x, i}) という書き方を初めて知ったのですが、 これを書いたら、この後に続くのは、 .ToDictionary(d => d.i, d => d.x); ぐらいしかないように思えたのですが、後に続くものとして他にもあったりしますか?
negitama

2019/01/14 15:51

回答のほうに参考画像を追加しましたが、LINQのなかの変数の型については、Visual Studio(またはその他のコードエディタ)で実際にどのような型になっているのか確認されるのが早いと思います。私はそれ以上踏み込んで言語仕様を説明できません。 後工程の件は言葉足らずでしたね。たしかに後工程でDictionaryを受け取っているのですが、Valueのint[]のほうしか処理に使用していないという意味です。movePosの[]内でも使用していませんね。数をCountしているだけなので、Dictionaryである必要はありません。 「他にもあるか」の件は、よく分かりません。
negitama

2019/01/14 16:05

参考まで回答のほうに、さきほどこちらで個人的に弄り回していたコードを載せてみました。Unityで動かしてみて、確認いただければと思います。 途中でDictionaryには変換していませんが、(私の勘違いでなければ)元のコードと同じように動作するはずです。
退会済みユーザー

退会済みユーザー

2019/01/15 17:05

ご回答ありがとうございます。 エディタはVisual Studio Codeを使っていたので、Visual Studioのようにオンマウスで変数の型までは表示されなかったです。エディタ選びも重要ですね。 コードのご提示ありがとうございます。 negitama様のListのコードでも動作するのを確認致しました。 Listも要素番号で要素を取得できるので、Dictionaryである必要はないということですね。 まだ今回の迷路生成のコードを読み進めている途中なので、考えて分からないことがあったら、また他でトピックを立てて質問させていただくことがあるかもしれません。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.51%

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

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

質問する

関連した質問