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

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

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

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

Unity

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

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

Q&A

解決済

2回答

2328閲覧

LINQで2次元配列で最後に見つかった条件に合う要素のインデックスの取得。

退会済みユーザー

退会済みユーザー

総合スコア0

C#

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

Unity

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

LINQ

LINQとはLanguage INtegrated Queryの略で、「統合言語クエリ」という意味です。C#やVisual Basicといった言語のコード内に記述することができるクエリです。

0グッド

0クリップ

投稿2019/02/03 14:51

前提・実現したいこと

下記のような2次元配列があったとして、

C#

1 2 var cells = new int[,] 3 { 4 { 0, 0, 0}, 5 { 0, 0, 0}, 6 { 1, 1, 0}, 7 { 0, 0, 0}, 8 { 1, 1 1}, 9 };

この要素の配列の中で、最後に見つかった全て0の要素で埋まっている配列の1次元目のインデックスを
LINQで取得する方法のご教示をお願い致します。

C#

1 var cells = new int[,] 2 { 3 { 0, 0, 0}, //0で埋まっているけど最後に見つかっていない。 4 { 0, 0, 0}, //0で埋まっているけど最後に見つかっていない。 5 { 1, 1, 0}, 6 { 0, 0, 0}, //最後に見つかった0で埋まっている配列要素。この1次元目のインデックス:3 7 { 1, 1 1} 8 };

今回の例ですと、インデックス3を取得したいです。

ただし、下記のように条件に合う配列要素がなかった場合は、-1を取得するようにしたいです。

C#

1 var cells = new int[,] 2 { 3 { 1, 0, 0}, //0で埋まっていない。 4 { 0, 1, 0}, //0で埋まっていない。 5 { 1, 1, 0}, //0で埋まっていない。 6 { 0, 0, 1}, //0で埋まっていない。 7 { 1, 1 1} //0で埋まっていない。 8 };

試したこと

C#

1 2 int[,] cells = new int[,] 3 { 4 { 0, 0, 0}, 5 { 0, 0, 0}, 6 { 1, 1, 0}, 7 { 0, 0, 0}, 8 { 1, 1, 1} 9 }; 10 11 var comparison = new int[] { 0, 0, 0 }; 12 13 int lastIndex = Enumerable 14 .Range(0, cells.GetLength(0)) 15 .Select(a => Enumerable 16 .Range(0, cells.GetLength(1)) 17 .Select(b => cells[a, b])) 18 .Select((a, i) => a.SequenceEqual(comparison) ? i : -1) 19 .Last(a => a != -1); 20 21 Debug.Log(lastIndex); //3

この場合はインデックス3がとれますが、
条件に合わない場合(2次元配列cellsの要素に0で埋まっている配列がない場合)、
-1を返すようにする方法がわかりません。

調べたのですが、
DefaultIfEmptyというものがあるらしいですが、これは配列やリストで返すみたいなので、
できなさそうだと思いました。
また、LastOrDefaultだと既定値の0が返ってきてしまうので、これもできないと思いました。

あと、最後から2行目、

C#

1.Select((a, i) => a.SequenceEqual(comparison) ? i : -1)

の記述ですが、もしかしたら、ここはSelectじゃなく、Lastとか使って、
もっとLINQを短くすることができるかもしれないと思い、考えたのですが、わかりませんでした。
このSelectの箇所をLastとか使って短くすることは可能ですか?

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

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

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

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

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

guest

回答2

0

ベストアンサー

後ろから見ていって最初に見つかったものを返してください。
データをインデックスに変換しているので Select でなければいけませんが、見つからなかった場合に null を返すようにすれば DefaultIfEmpty が null を返すので -1 に変換すればいいでしょう。

前の質問から結局何がしたいのかわかりませんが、手に余るなら LINQ はあきらめて素直に for を使うのがいいと思います。応用が利かないということは保守できないということです。

追記

LINQ を使うなら相性が悪いとわかっている二次元配列をわざわざ使わなくても別のものに変換すれば簡単になります。

C#

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Linq; 5 6namespace ConsoleApp1 7{ 8 public static class Program 9 { 10 public static void Main(string[] args) 11 { 12 var cells = new int[,] 13 { 14 { 0, 0, 0 }, 15 { 0, 0, 0 }, 16 { 1, 1, 0 }, 17 { 0, 0, 0 }, 18 { 1, 1, 1 }, 19 }; 20 int index = cells 21 .Cast<int>() 22 .ToMap(new Xy(cells.GetLength(1), cells.GetLength(0))) 23 .GetRows() 24 .Select((a, i) => a.All(b => b == 0) ? new int?(i) : null) 25 .LastOrDefault(a => a != null) 26 ?? -1; 27 Console.WriteLine(index); 28 Console.ReadKey(); 29 } 30 } 31 32 #region Xy 33 public static class XyExtension 34 { 35 public static Xy ToXy(this IEnumerable<int> source) 36 { 37 var list = source as IReadOnlyList<int> ?? source.ToList(); 38 if (list.Count != 2) throw new ArgumentException(); 39 return new Xy(list[0], list[1]); 40 } 41 } 42 43 public struct Xy : IReadOnlyList<int> 44 { 45 public Xy(int x, int y) 46 { 47 X = x; 48 Y = y; 49 hashCode = null; 50 area = null; 51 directions = null; 52 distance = null; 53 } 54 55 public int X { get; private set; } 56 public int Y { get; private set; } 57 58 public int Count { get { return 2; } } 59 60 public int this[int index] 61 { 62 get 63 { 64 switch (index) 65 { 66 case 0: 67 return X; 68 case 1: 69 return Y; 70 default: 71 throw new IndexOutOfRangeException(); 72 } 73 } 74 } 75 76 public override bool Equals(object obj) 77 { 78 return base.Equals(obj); 79 } 80 81 private int? hashCode; 82 public override int GetHashCode() 83 { 84 if (hashCode == null) 85 { 86 hashCode = this.Aggregate(1861411795, (a, b) => a * -1521134295 + b); 87 } 88 return hashCode.Value; 89 } 90 91 public IEnumerator<int> GetEnumerator() 92 { 93 yield return X; 94 yield return Y; 95 } 96 97 IEnumerator IEnumerable.GetEnumerator() 98 { 99 return GetEnumerator(); 100 } 101 102 public Xy Square() 103 { 104 return this * this; 105 } 106 107 public double Distance(Xy xy) 108 { 109 return (this - xy).Distance(); 110 } 111 112 private double? distance; 113 public double Distance() 114 { 115 if (distance == null) 116 { 117 distance = Math.Sqrt(Square().Sum()); 118 } 119 return distance.Value; 120 } 121 122 private int? area; 123 public int Area 124 { 125 get 126 { 127 if (area == null) area = X * Y; 128 return area.Value; 129 } 130 } 131 132 public override string ToString() 133 { 134 return string.Format("({0},{1})", X, Y); 135 } 136 137 private static List<Xy> directions; 138 private static IEnumerable<Xy> Spin(Xy source) 139 { 140 var item = source; 141 for (int i = 0; i < 4; i++) 142 { 143 yield return item; 144 item = new Xy(-item.Y, item.X); 145 } 146 } 147 public static IEnumerable<Xy> Directions 148 { 149 get 150 { 151 if (directions == null) 152 { 153 directions = Spin(new Xy(1, 0)) 154 .Concat(Spin(new Xy(1, 1))) 155 .ToList(); 156 } 157 return directions; 158 } 159 } 160 161 public static implicit operator Xy(int i) 162 { 163 return new Xy(i, i); 164 } 165 166 public static bool operator ==(Xy xy1, Xy xy2) 167 { 168 return EqualityComparer<Xy>.Default.Equals(xy1, xy2); 169 } 170 171 public static bool operator !=(Xy xy1, Xy xy2) 172 { 173 return !(xy1 == xy2); 174 } 175 176 public static Xy operator +(Xy xy1, Xy xy2) 177 { 178 return new Xy(xy1.X + xy2.X, xy1.Y + xy2.Y); 179 } 180 181 public static Xy operator -(Xy xy1, Xy xy2) 182 { 183 return new Xy(xy1.X - xy2.X, xy1.Y - xy2.Y); 184 } 185 186 public static Xy operator *(Xy xy1, Xy xy2) 187 { 188 return new Xy(xy1.X * xy2.X, xy1.Y * xy2.Y); 189 } 190 191 public static Xy operator /(Xy xy1, Xy xy2) 192 { 193 return new Xy(xy1.X / xy2.X, xy1.Y / xy2.Y); 194 } 195 196 public static Xy operator %(Xy xy1, Xy xy2) 197 { 198 return new Xy(xy1.X % xy2.X, xy1.Y % xy2.Y); 199 } 200 } 201 #endregion 202 203 #region Map 204 public static class MapExtension 205 { 206 public static Map<T> ToMap<T>(this IEnumerable<T> source, Xy size) 207 { 208 return new Map<T>(source, size); 209 } 210 } 211 public class Map<T> : IReadOnlyList<T> 212 { 213 public Map(T[] source, Xy size) 214 { 215 if (source.Length != size.Area) throw new ArgumentException(); 216 Source = new T[source.Length]; 217 source.CopyTo(Source, 0); 218 Size = size; 219 } 220 public Map(IReadOnlyList<T> source, Xy size) 221 : this(source as T[] ?? (source == null ? new T[0] : source.ToArray()), size) { } 222 public Map(IEnumerable<T> source, Xy size) 223 : this(source as IReadOnlyList<T> ?? (source == null ? new T[0] : source.ToArray()), size) { } 224 225 protected T[] Source { get; private set; } 226 public Xy Size { get; private set; } 227 228 public int Count { get { return Source.Length; } } 229 230 public Xy GetIndices(int index) 231 { 232 if (!Includes(index)) throw new ArgumentOutOfRangeException(); 233 return new Xy(index % Size.X, index / Size.X); 234 } 235 public int GetIndex(Xy indices) 236 { 237 if (!Includes(indices)) throw new ArgumentOutOfRangeException(); 238 return indices.Y * Size.X + indices.X; 239 } 240 241 public bool Includes(Xy indices) 242 { 243 return indices.X >= 0 244 && indices.Y >= 0 245 && indices.X < Size.X 246 && indices.Y < Size.Y; 247 } 248 public bool Includes(int index) 249 { 250 return index >= 0 && index < Count; 251 } 252 253 public T this[int index] 254 { 255 get { return Source[index]; } 256 set 257 { 258 Source[index] = value; 259 hashCode = null; 260 } 261 } 262 public T this[Xy indices] 263 { 264 get { return this[GetIndex(indices)]; } 265 set { this[GetIndex(indices)] = value; } 266 } 267 268 public IEnumerable<IReadOnlyList<T>> GetRows() 269 { 270 for (int y = 0; y < Size.Y; y++) 271 { 272 var result = new T[Size.X]; 273 Array.Copy(Source, Size.X * y, result, 0, Size.X); 274 yield return result; 275 } 276 } 277 278 public IEnumerable<IReadOnlyList<T>> GetColumns() 279 { 280 for (int x = 0; x < Size.X; x++) 281 { 282 var result = new T[Size.Y]; 283 int index = x; 284 for (int y = 0; y < Size.Y; y++) 285 { 286 result[y] = Source[index]; 287 index += Size.X; 288 } 289 yield return result; 290 } 291 } 292 293 public IEnumerator<T> GetEnumerator() 294 { 295 foreach (var item in Source) yield return item; 296 } 297 298 IEnumerator IEnumerable.GetEnumerator() 299 { 300 return GetEnumerator(); 301 } 302 303 public override string ToString() 304 { 305 return string.Join("\n", GetRows().Select(a => string.Join(" ", a))); 306 } 307 308 public override bool Equals(object obj) 309 { 310 var d = obj as Map<T>; 311 return d != null && Source.SequenceEqual(d.Source) && Size == d.Size; 312 } 313 314 int? hashCode; 315 public override int GetHashCode() 316 { 317 if (hashCode == null) 318 { 319 hashCode = Source.Aggregate(262889186, (a, b) => a * -1521134295 + b.GetHashCode()); 320 hashCode = hashCode * -1521134295 + Size.GetHashCode(); 321 } 322 return hashCode.Value; 323 } 324 325 public static bool operator ==(Map<T> d1, Map<T> d2) 326 { 327 return EqualityComparer<Map<T>>.Default.Equals(d1, d2); 328 } 329 330 public static bool operator !=(Map<T> d1, Map<T> d2) 331 { 332 return !(d1 == d2); 333 } 334 } 335 #endregion 336}

追記

質問とは直接関係ありませんが、null を代入できる構造体の例。

C#

1using System; 2using System.Collections.Generic; 3 4namespace ConsoleApp1 5{ 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 MyNullable<int> a = null; 11 Console.WriteLine(a == null); 12 MyNullable<int> b = 1; 13 Console.WriteLine(b == null); 14 Console.WriteLine(b == 1); 15 Console.ReadKey(); 16 // 結果は True, False, True になる。 17 } 18 } 19 20 struct MyNullable<T> 21 { 22 public MyNullable(T value) 23 { 24 HasValue = true; 25 Value = value; 26 } 27 28 public T Value { get; private set; } 29 30 public bool HasValue { get; private set; } 31 32 public override bool Equals(object obj) 33 { 34 switch (obj) 35 { 36 case null: 37 return !HasValue; 38 case T comparison: 39 return EqualityComparer<T>.Default.Equals(comparison, Value); 40 case MyNullable<T> comparison: 41 if (!comparison.HasValue && !HasValue) return true; 42 if (!comparison.HasValue || !HasValue) return false; 43 return EqualityComparer<T>.Default.Equals(comparison.Value, Value); 44 default: 45 return false; 46 } 47 } 48 49 public override int GetHashCode() 50 { 51 var hashCode = 1816676634; 52 hashCode = hashCode * -1521134295 + EqualityComparer<T>.Default.GetHashCode(Value); 53 hashCode = hashCode * -1521134295 + HasValue.GetHashCode(); 54 return hashCode; 55 } 56 57 public static bool operator ==(MyNullable<T> nullable1, MyNullable<T> nullable2) 58 { 59 return Equals(nullable1, nullable2); 60 } 61 62 public static bool operator !=(MyNullable<T> nullable1, MyNullable<T> nullable2) 63 { 64 return !(nullable1 == nullable2); 65 } 66 67 public static implicit operator MyNullable<T>(Dummy dummy) 68 { 69 if (dummy != null) throw new InvalidCastException(); 70 return default(MyNullable<T>); 71 } 72 73 public static implicit operator MyNullable<T>(T value) 74 { 75 return new MyNullable<T>(value); 76 } 77 78 public static explicit operator T(MyNullable<T> nullable) 79 { 80 return nullable.Value; 81 } 82 } 83 84 public class Dummy { } 85}

投稿2019/02/03 21:38

編集2019/02/07 03:51
Zuishin

総合スコア28660

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

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

wwbQzhMkhhgEmhU

2019/02/03 23:26

手に余るとか言い方はどうかと思いますが、多次元配列にするならLINQにしない方が速そうだし、メリットがないように思います。 それでも保守性ガン無視で突き進むなら、最後を .Select((a, i) => a.SequenceEqual(comparison) ? i +1: 0) .(a => a != 0) .LastOrDefault() - 1; にすると更に汚くできます。
Zuishin

2019/02/04 00:15 編集

null と LastOrDefault を使うイメージを追記しました。二次元配列から別のものに変換しているので要件には合いませんが、二次元配列に対応させるのも難しくないはずです。 +1 -1 するより null を使う方が好みです。 LastOrDefault を使う場合、無関係の行まで Select で変換してしまうので、後ろから見ていって最初に null でない行を見つける方が良さそうに思います。
wwbQzhMkhhgEmhU

2019/02/04 04:37

あわわ、回答者さんに言ったのではなくて、質問者さんに言ったつもりでした。 ここに書いてしまうとそう捉えてしまいますよね。至らずすみませんでした。 コードはなかなか方向性の違う試みにさらに一歩進ませてしまったようで、すみません。 二次元なので、X,Y座標から値をマップして走査する形なんですね。しかも専用のマップ。 元のコードのロジックの傾向を引き継ぐLINQ化。。。斬新です。 まあ、回答者さんのお気持ちはよく分かりました。 というわけで質問者さんは好きなように取り組んでください。 オススメは後方からのforによる走査、ということでした。
退会済みユーザー

退会済みユーザー

2019/02/04 15:33

ご回答ありがとうございます。 他の方々もおっしゃっているように多次元配列にLINQを使うべきでないことがわかりました。 今回は2次元配列を使うことは前提として、LINQについて勉強したかった為、保守性無視で質問致しました。forの走査が賢明と心得ました。 a.All(b => b == 0) ? new int?(i) : null のコードについて質問させていただきたいのですが、 a.All(b => b == 0)がtrueのときに、new int?(i)が通り、falseのときにnullが通ると認識しているのですが、このnew int?(i)がどういったコードかわかりませんでした。 このintの後の?は何かの処理になりますか? Mapクラス等のご教示ありがとうございます。 こちらは時間をかけて勉強していこうと思います。
退会済みユーザー

退会済みユーザー

2019/02/05 14:19 編集

ご回答ありがとうございます。 ご提示のURLで?がNULL許容型とわかりました、ありがとうございます。 new int?(i) これのnewの意味がわからなく、下記URLでnew演算子であることはわかったのですが、 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/new-operator int i = new int(); だとコードは通るのですが、 int i = new int(1); や int i=0; int j = new int(i); だとコードが通らず、 int i=2; int? k = new int?(i); でコードが通りました。 これより、「new int?(i)」は、「NULL許容型のint値」と解釈しようとしたのですが、 int? k = new int?(null); や int? i = null; int? k = new int?(i); を書くとエラーになってしまい、わかりませんでした。 「new int?(i)」は、「NULL許容型のint値」ですか? どういったものかご教示いただけませんか?
Zuishin

2019/02/05 14:24

リンク先に書いてあるように、int? は Nullable<int> と同じものです。 なので new int?(i) は new Nullable<int>(i) と同じです。 型にはそれぞれコンストラクタがあります。 int 型と Nullable<int> 型は別の型なのでコンストラクタもメソッドもプロパティも全部違います。 その型にどのようなメンバーがあるかは次を参照し、検索窓に型名を入れてください。 https://docs.microsoft.com/ja-jp/dotnet/api/
退会済みユーザー

退会済みユーザー

2019/02/06 17:06

ご回答ありがとうございます。 理解力がなくて申し訳ないです。 まず、ご教示いただいて Nullable<int>は、int型ではないと理解したつもりですが、 疑問点が湧いてしまいました。 Nullable<T>(T) Constructorは、 public Nullable (T value); で、valueのTは値型と書かれてたので、nullは値型でないので(nullは値型ではないですか?)、 これのコンストラクタにnullは入れられないということですか? そうすると、new Nullable<int>(i)のiには、nullは入れらず、普通のintしか入れられないということですか? そうすると、new Nullable<int>(i)の値と普通のintのiの違いは何なのかわからなくなってしまいました。 int? k = new int?(2); とは書けるものの、 int? k = new int?(null); とは書けないので、 結局kには普通のint値しか入らないのではないか?という疑問です。
Zuishin

2019/02/06 22:26

int? k は Nullable<int> k と同じものです。 リンク先に書いてありますが、Nullable<T> は値型なので null にはなりません。また同じく T も値型なので null にはなりません。ただし、k == null のように、Nullable<T> と null を比較することはできます。 なぜ k が null になり得ないのに k == null が true になることがあるのかは次を参照してください。 https://qiita.com/Zuishin/items/62c5b726bfa589b3fb9b また int? k = null のように Nullable<T> 型の変数に null を代入することができます。この時に k は null にはなりません。Nullable<T> は値型なので null にはならないからです。しかし k == null は true になります。この時、`k.` と打って Ctrl+Space を押してみてください。null ならメンバーは無いので `null.` と打っても補完されませんが、`k.` の場合は補完されます。k は null ではなく、null と値が等しい別の物というにすぎません。具体的に言うと HasValue が false の時、Nullable<T> は Equals メソッドおよび == のオーバーロードによって自らの値が null に等しいと言っているだけにすぎません。
Zuishin

2019/02/07 03:25 編集

ここからわかるように、k には普通の int 値も入りませんし、null も入りません。 入るのは Nullable<int> だけです。 Nullable<int> 型の変数に int 値や null を代入する時、それは Nullable<int> にキャストされます。そしてキャスト演算のオーバーロードにより、(Nullable<int>)1 は new Nullable<int>(1) と同じものであり、(Nullable<int>)null は new Nullable() と同じものになります。
tamoto

2019/02/07 00:15

nullableはC#の「構文上」めちゃくちゃ特別扱い(structなのにnullを代入するような式が「特別に」書ける)なので、一般的な値型とは扱いが異なるものと覚えてしまうのがよいです(内部的にはただの値型なのです) というかこれ別の質問に起こしたほうが良かったのでは……
Zuishin

2019/02/07 03:53 編集

構文上特別なのは T? になるからで、null を代入できる struct は書けますよ。 追記します。
tamoto

2019/02/07 05:19

なるほど。implicit conversionなら確かに同じ見た目を実現できますね。発想が強い…… しかし、やはり実際のNullable<T>にはそのようなHackは使われていない(はず)ので、コンパイラによる特別対応でnull代入を実現しているということには違いはないと思ってます。nullを代入するとdefaultを代入した扱いでコンパイルされるだけなんですけどね。
Zuishin

2019/02/07 05:46

ソースをよく見ると確かにこのままでは null を代入できませんね。四則演算もオーバーロードされてないので言語レベルで特別扱いされているようです。勘違いしていました。
退会済みユーザー

退会済みユーザー

2019/02/07 16:23

ご回答ありがとうございます。 おそらく、理解できたと思いますが、もしまたわからないことがあれば、ご指摘の通り、 別の質問に起こさせていただきたいと思います。 Zuishin様、ご教示ありがとうございました。 wwbQzhMkhhgEmhU様もtamoto様もありがとうございました。
guest

0

(これは回答ではないです。)

前の質問にも目を通してきましたが、Linqと多次元配列は「相性が悪い」なんてものではなく、Linqは「多次元配列は一切サポートしていない」と言い切ってしまって良いくらい、多次元配列のことは考慮していないです。
無理やりにLinqを使うことは、「無理やりに再帰を使って書かれたロジック」並にどうしようもないコードになります。
直列的な元データからジャグ配列の形を経て「最終的に多次元配列にまとめる」という話なら採用の余地はありますが、元々の多次元配列に対してLinqで処理を行おうというのは一切避けるべき手法です。もしコードレビューの文化があるなら100%リジェクトされるでしょうね。

多次元配列に、Linqを使うのを、諦めてください。

投稿2019/02/04 09:42

tamoto

総合スコア4103

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

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

退会済みユーザー

退会済みユーザー

2019/02/04 14:55

ご助言ありがとうございます。 多次元配列にLINQを使うべきでないということがわかりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問