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

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

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

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

Q&A

1回答

770閲覧

C#で循環リストのようなデータ構造を実現したい

Motch

総合スコア1

C#

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

0グッド

1クリップ

投稿2023/09/12 08:45

実現したいこと

ループ形状の図の線上にいくつかの点を打ったものを扱うツールを開発しています。
適当な点を原点とし、原点から線を辿った距離を一次元の座標値として点の位置を表現しています。

順番に点を見ていったり、隣接する点を取得したりといった操作をするので、
循環リストのようなものを作れば便利では無いかと考えました。
また、foreachで回せて、どんな順で点を登録しても自動で順に並ぶとより都合が良いと思い、
SortedListを継承してCircularListという自作クラスを実装してみたのですが、
不便さの残る結果となってしまったのでアドバイス頂きたいです。

発生している問題・エラーメッセージ

下記にソースコードを示します。
ある程度思っていた動作はするのですが、
foreach文で回すとその都度Reset()関数を呼ばないとインデックス値が初期化されない、
SortedListベースであるため同じ座標値を登録すると重複で例外が発生してしまうなど微妙な結果となりました。
より良い循環リストの実装方法、もしくは循環リスト以外のより良いアプローチがあればご教示頂きたいです。

該当のソースコード

C#

1public class CircularList<TKey, TValue> : SortedList<TKey, TValue>, IEnumerator<KeyValuePair<TKey, TValue>> 2 { 3 /// <summary> 4 /// 循環する整列済みリスト 5 /// foreachでは原点から一周する(原点は初期値は0で変更可能) 6 /// </summary> 7 /// <returns></returns> 8 9 private int originIndex; 10 private int currentIndex; 11 private int numberOfCall; 12 private bool firstFlag; 13 14 public CircularList() 15 { 16 this.originIndex = 0; 17 this.currentIndex = -1; 18 this.numberOfCall = 0; 19 this.firstFlag = true; 20 } 21 22 public CircularList(IComparer<TKey> comparer) : base(comparer) 23 { 24 this.originIndex = 0; 25 this.currentIndex = -1; 26 this.numberOfCall = 0; 27 this.firstFlag = true; 28 } 29 30 public int OriginIndex 31 { 32 get { return this.originIndex; } 33 set { this.originIndex = value; } 34 } 35 36 public void SetOriginIndex() 37 { 38 this.originIndex = (this.currentIndex + 1) % this.Count; 39 } 40 public new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() 41 { 42 return this; 43 } 44 public KeyValuePair<TKey, TValue> Current 45 { 46 get { return new KeyValuePair<TKey, TValue>(this.Keys[this.currentIndex], this.Values[this.currentIndex]); } 47 } 48 object System.Collections.IEnumerator.Current 49 { 50 get { return this; } 51 } 52 public bool MoveNext() 53 { 54 55 if (this.Count > 0) 56 { 57 this.currentIndex++; 58 this.currentIndex %= this.Count; 59 this.numberOfCall = 0; 60 } 61 62 if (this.currentIndex == this.originIndex && !firstFlag) 63 { 64 this.currentIndex--; 65 if (this.currentIndex < 0) 66 { 67 this.currentIndex = this.Count - 1; 68 } 69 firstFlag = true; 70 return false; 71 } 72 73 firstFlag = false; 74 75 return true; 76 } 77 public void Reset() 78 { 79 this.currentIndex = this.originIndex - 1; 80 if (this.currentIndex < 0) 81 { 82 this.currentIndex = this.Count - 1; 83 } 84 firstFlag = true; 85 } 86 public void Dispose() 87 { 88 89 } 90 91 public KeyValuePair<TKey, TValue> GetNextPair() 92 { 93 this.numberOfCall++; 94 int nextIndex = (this.currentIndex + this.numberOfCall) % this.Count; 95 return new KeyValuePair<TKey, TValue>(this.Keys[nextIndex], this.Values[nextIndex]); 96 } 97 98 public void ChangeKey(TKey oldKey, TKey newKey) 99 { 100 TValue value = this[oldKey]; 101 this.Remove(oldKey); 102 this[newKey] = value; 103 } 104 }

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

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

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

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

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

KOZ6.0

2023/09/12 09:33

用途がよくわからないですが、Enumerator は別クラスにして GetEnumerator が呼ばれるたびにインスタンスを作成する必要があります。 IEnumerator<KeyValuePair<TKey, TValue>> インターフェイスを実装しておけばいいので、内部クラスにしても良いです。
KOZ6.0

2023/09/12 11:44

重複キーを登録できない問題に関しては、SortedList<TKey, TValue> の継承をあきらめるしかなさそうです。 また、複数の同一キーが存在する場合に、 (1) 追加はどの位置に行うのか(前につけるか後ろにつけるか) (2) ChangeKey の振る舞いは?(同一キーすべて交換するのか、どれかひとつなのか、ひとつならどれを選択する?) 等のふるまいについて決定しておく必要があります。
hqf00342

2023/09/14 02:04

「原点」はサンプルソースの`OriginIndex`(int)ですか? TKey値を指定するのではなく、SortedListのインデックスのようですが、挿入のたびに原点値がどんどんずれていく気がします。
KOZ6.0

2023/09/14 04:26

列挙の「起点」であって、「原点」は表現がちょっと違うような気がしますね。
guest

回答1

0

なんだか音沙汰が無いですね。とりあえず、以下の2点

(1) foreach文で回すとその都度Reset()関数を呼ばないとインデックス値が初期化されない。
(2) SortedListベースであるため同じ座標値を登録すると重複で例外が発生してしまう。

について解消したクラスを書いてみました。
あとはメソッドを生やすなり、修正するなりしてみてください。

csharp

1using System; 2using System.Collections; 3using System.Collections.Generic; 4 5public class CircularList<TKey, TValue> 6 : IEnumerable<KeyValuePair<TKey, TValue>> 7{ 8 private readonly IComparer<TKey> comparer; 9 private readonly List<TKey> keys; 10 private readonly List<TValue> values; 11 private int version; 12 private int originIndex; 13 private const int defaultCapacity = 4; 14 15 public CircularList() : this(defaultCapacity, Comparer<TKey>.Default) { } 16 public CircularList(int capacity) : this(capacity, Comparer<TKey>.Default) { } 17 public CircularList(IComparer<TKey> comparer) : this(defaultCapacity, comparer) { } 18 19 public CircularList(int capacity, IComparer<TKey> comparer) { 20 this.comparer = comparer; 21 keys = new List<TKey>(capacity); 22 values = new List<TValue>(capacity); 23 version = int.MaxValue; 24 originIndex = 0; 25 } 26 27 public int OriginIndex { 28 get { 29 return originIndex; 30 } 31 set { 32 if (value > Count || value < 0) { 33 throw new ArgumentOutOfRangeException(); 34 } 35 originIndex = value; 36 } 37 } 38 39 public int Count => keys.Count; 40 41 public TValue this[int index] { 42 get { 43 return values[index]; 44 } 45 set { 46 values[index] = value; 47 } 48 } 49 50 public TValue[] GetValues(TKey key) { 51 var r = GetRange(key); 52 var array = new TValue[r.LastIndex - r.FirstIndex + 1]; 53 for (int i = r.FirstIndex; i <= r.LastIndex; i++) { 54 array[i] = this[i]; 55 } 56 return array; 57 } 58 59 public bool ContainsKey(TKey key) { 60 return (GetIndex(key) >= 0); 61 } 62 63 public void Add(TKey key, TValue value) { 64 AddCore(key, value); 65 version++; 66 } 67 68 public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> keyValues) { 69 foreach (var pair in keyValues) { 70 AddCore(pair.Key, pair.Value); 71 } 72 version++; 73 } 74 75 private void AddCore(TKey key, TValue value) { 76 var index = GetIndex(key); 77 if (index < 0) { 78 index = ~index; 79 } else { 80 index = GetLastIndex(key, index) + 1; 81 } 82 keys.Insert(index, key); 83 values.Insert(index, value); 84 } 85 86 public void RemoveAt(int index) { 87 RemoveCore(index); 88 version++; 89 } 90 91 public void Remove(TKey key) { 92 RemoveCore(key); 93 version++; 94 } 95 96 private TValue RemoveCore(int index) { 97 var value = values[index]; 98 keys.RemoveAt(index); 99 values.RemoveAt(index); 100 return value; 101 } 102 103 private List<TValue> RemoveCore(TKey key) { 104 var removeList = new List<TValue>(); 105 var r = GetRange(key); 106 for (var i = r.LastIndex; i >= r.FirstIndex; i--) { 107 removeList.Insert(0, RemoveCore(i)); 108 } 109 return removeList; 110 } 111 112 public void ChangeKey(TKey oldKey, TKey newKey) { 113 foreach (var value in RemoveCore(oldKey)) { 114 AddCore(newKey, value); 115 } 116 version++; 117 } 118 119 private int GetIndex(TKey key) { 120 return keys.BinarySearch(key, comparer); 121 } 122 123 private struct Range 124 { 125 public int FirstIndex; 126 public int LastIndex; 127 } 128 129 private Range GetRange(TKey key) { 130 var index = GetIndex(key); 131 return new Range() { 132 FirstIndex = GetFirstIndex(key, index), 133 LastIndex = GetLastIndex(key, index) 134 }; 135 } 136 137 private int GetFirstIndex(TKey key, int index) { 138 if (index < 0) return 0; 139 while (index > 0 && 140 comparer.Compare(keys[index - 1], key) == 0) { 141 index--; 142 } 143 return index; 144 } 145 146 private int GetLastIndex(TKey key, int index) { 147 if (index < 0) return -1; 148 while (index < keys.Count - 1 && 149 comparer.Compare(keys[index + 1], key) == 0) { 150 index++; 151 } 152 return index; 153 } 154 155 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { 156 return new Enumerator(this); 157 } 158 159 IEnumerator IEnumerable.GetEnumerator() { 160 return new Enumerator(this); 161 } 162 163 private class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> 164 { 165 private readonly CircularList<TKey, TValue> owner; 166 private readonly int version; 167 private readonly int originIndex; 168 private readonly int count; 169 private int currentIndex; 170 private int numberOfCall; 171 172 public Enumerator(CircularList<TKey, TValue> owner) { 173 this.owner = owner; 174 version = owner.version; 175 originIndex = owner.OriginIndex; 176 count = owner.Count; 177 Reset(); 178 } 179 180 public void Reset() { 181 currentIndex = originIndex - 1; 182 numberOfCall = 0; 183 } 184 185 public KeyValuePair<TKey, TValue> Current { 186 get { 187 var key = owner.keys[currentIndex]; 188 var value = owner.values[currentIndex]; 189 return new KeyValuePair<TKey, TValue>(key, value); 190 } 191 } 192 193 public void Dispose() { } 194 195 public bool MoveNext() { 196 if (owner.version != version) { 197 throw new InvalidOperationException(); 198 } 199 if (numberOfCall >= count) { 200 return false; 201 } 202 numberOfCall++; 203 currentIndex = (currentIndex + 1) % count; 204 return true; 205 } 206 207 object IEnumerator.Current => Current; 208 } 209}

投稿2023/09/14 01:09

KOZ6.0

総合スコア2721

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

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

Motch

2023/09/14 11:12

回答ありがとうございます。提案頂いた内容を基に自分なりに修正を加えてみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問