8672


「3, 4, 5」という範囲の整数があるとします。カレントロケーションを `5`、インクリメントを `+1` としたとき、`3`
を返してほしいです。もし、インクリメントが `+2` なら `4`、`+7` であれば `3` のように、3, 4, 5, 3, 4, 5, 3...
とインクリメントの結果が循環してほしいです。

**やってみたこと** :

+1、-1 のインクリメント/デクリメントの場合は下記のコードで実装できましたが、+2 や -4 など加減数が 1
を超える場合に対応させようとすると、方法がわからなくなります（下記の関数を加減数ぶんfor文で繰り返し実行することはできますが……）。

実装

[code]

    NSUInteger NSRangeIncrementLocationInRange(NSUInteger location, NSRange range) {
    
      NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange: range];
    
      if (![indexSet containsIndex: location]) {
        return NSNotFound;
      }
    
      NSUInteger index = [indexSet indexGreaterThanIndex: location];
    
      return index != NSNotFound ? index : [indexSet firstIndex];
    }
    
    NSUInteger NSRangeDecrementLocationInRange(NSUInteger location, NSRange range) {
    
      NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange: range];
    
      if (![indexSet containsIndex: location]) {
        return NSNotFound;
      }
    
      NSUInteger index = [indexSet indexLessThanIndex: location];
    
      return index != NSNotFound ? index : [indexSet lastIndex];
    }
    
[/code]

実行例

[code]

    // 4 〜 8の範囲内で8をインクリメントする
    NSLog(@"%zd", NSRangeIncrementLocationInRange(8, NSMakeRange(4, 5))); // 8の次は最小値に戻って4が返る
    
[/code]

**求めているもの** :

理想的なのは下記のような関数で、

[code]

    NSUInteger NSRangeIncrementLocationInRange(NSUInteger location, NSInteger increment, NSRange range);
    
[/code]

`increment` に `3` だの `-8` だの正負どちらでも渡せるようにしたいです。

**質問** :

このようなメソッド、または関数は、Objective-C、またはCのフレームワークやライブラリに存在しますか？
もしない場合、実装するためにどのようなコードが考えられますか？


CC BY-SA 3.0
2015-04-01T19:08:29.047
8665
2015-04-02T18:51:05.313
2015-04-02T02:47:21.980
question
ios objective-c c
ある範囲内で整数のインクリメント・デクリメントを循環させるには？


自己回答です。

**`location` を基点に加減するべき数（距離）を求める** 解法です。

[code]

    NSUInteger NSRangeIncrementLocationInRange(NSUInteger location, NSInteger increment, NSRange range) {
    
      if (!NSLocationInRange(location, range)) {
        return NSNotFound;
      }
    
      BOOL incrementIsPositive = increment >= 0;
      NSUInteger distance = ABS(increment) % range.length;
    
      NSInteger result = location;
      result += incrementIsPositive ? distance : -distance;
    
      if (!NSLocationInRange(result, range)) {
        result += incrementIsPositive ? -range.length : range.length;
      }
    
      return result;
    }
    
[/code]

**解法** :

  * インクリメントの増減値を整理する（周回数を取り除く） : `incrementの絶対値 % range.length`
  * `location` に増減値を加算（インクリメントが正なら加算、負なら減算）
  * `result` が範囲外なら `range.length` を加算または減算して範囲内で1周させる


CC BY-SA 3.0
2015-04-01T21:48:31.553
8668
2015-04-02T18:51:05.313
2015-04-02T18:51:05.313
8665
answer


デクリメントのほうなんですけど、整数値が環状に並んでいるというイメージをしてください。（下図）  
![画像の説明をここに入力](https://i.stack.imgur.com/CMrdv.png)  
2戻るということは、3進むのと同じです。これを、一般化して表現すると、increment **戻る** ということは、range.length -
increment **進む** とおなじであることに気づくはずです。さらに厳密に表現すると、range.length - increment %
range.length進むと同じです。これをそのままインクリメント（increment >
0）のときの式に代入してやります。具体的にコードにすると、こうなります。

[code]

    #import <Foundation/Foundation.h>
    
    NSUInteger NSRangeIncrementLocationInRange(NSInteger location, NSInteger increment, NSRange range) {
        NSUInteger result;
        // locationがrangeの範囲外なら、NSNotFoundを返す。
        if (!NSLocationInRange(location, range)) return NSNotFound;
        if (increment > 0) { // incrementが正数の場合。
            result = (location - range.location + (NSUInteger)increment) % range.length + range.location;
        } else if (increment == 0) { // incrementが0の場合、locationをそのまま返す。
            result = location;
        } else { // incrementが負数の場合。range.lengthとincrementの差を、locationに加えるという計算をする。
            result = (location - range.location + (NSUInteger)((NSInteger)range.length + increment % (NSInteger)range.length)) % range.length + range.location;
    // ((NSInteger)range.length + increment)) % range.length + range.location; 修正しました。
        }
    
        return result;
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSUInteger inc = NSRangeIncrementLocationInRange(5, -8, NSMakeRange(4, 5));
            NSLog(@"Result = %ld", inc);
        }
        return 0;
    }
    
[/code]

編集しました。  
引数incrementの型を、NSUIntegerからNSIntegerに変えました。きわめて初歩的な間違いですね。ご容赦。暗黙的に型キャストしてくれるであろう箇所も、明示的に型キャストしました。

二度目の編集をしました。負の向きに1巡以上するincrementで、正しい結果にならないという指摘を受けて、修正しました。  
「increment **戻る** ということは、range.length - increment **進む** とおなじ」を、  
「increment **戻る** ということは、range.length - increment % range.length **進む**
とおなじ」に変更しました。

三度目の編集をしました。  
「整数値を環状に並べる」を、図にして、わかりやすくしました。


CC BY-SA 3.0
2015-04-01T23:43:10.967
8670
2015-04-02T09:11:40.117
2015-04-02T09:11:40.117
7362
7362
8665
answer


Cっぽく書けば下記コードで算出できます。一度`range`で剰余(`%`)を求めたあと、負値に対処するため再び`(N + range) %
range`を行っています。

[code]

    // 値(value+diff)を範囲[rl, rh)にマッピング
    int f(int value, int diff, int rl, int rh)
    {
      int range = rh - rl;
      return (((value + diff - rl) % range) + range) % range + rl;
    }
    
[/code]

入力値`value`の範囲チェックはしていませんので、必要ならば別途追加してください（要不要は要件によると思います）。

LiveDemo: <http://melpon.org/wandbox/permlink/4gUve20aihTOVhuz>


CC BY-SA 3.0
2015-04-02T00:55:58.440
8672
2015-04-02T02:26:28.487
2015-04-02T02:26:28.487
49
49
8665
answer