在数组中达到最后一个索引后回到第一个索引

11
在我的for循环中,当数组达到最后一个索引时,我会收到一个异常,提示索引超出范围。 我想让它返回到第一个索引,直到 z 等于 ctr 。 我该如何做?
我的代码:
char res;
int ctr = 10
char[] flames = {'F','L','A','M','E','S'};

for(int z = 0; z < ctr-1; z++){
    res = (flames[z]);
    jLabel1.setText(String.valueOf(res));
}

在访问flames[]时,请使用,例如... flames [z%flames.length()]; - boxed__l
使用模(%)运算符在索引表达式上:res = flames[z%flames.length()]; 或者更好的方法是,在for循环之外有一个不变变量n = flames.length(),然后有res = flames [z%n]; - luis.espinal
2
@Luis.espinal : 编译器不会自动处理吗?在循环中使用 flames.length() 会被编译器优化,对吧/这样做应该没问题吧? - boxed__l
@luis.espinal:看起来你是对的。我在类似的代码上进行了反汇编,没有优化。有一些项目(如ProGuard)可用于进行此优化和更多操作。+1 - boxed__l
我不会依赖那样的产品来优化这样的事情。不变量是不变的,而无谓地重新计算不变量的代码是糟糕的代码。一个“修复”这种糟糕实践的优化器只是把垃圾藏在地毯下。优化器的正确使用是用于检测和改进其他事物,如尾调用优化等。 - luis.espinal
显示剩余2条评论
5个回答

7
你需要使用一个在数组大小范围内的索引。更准确地说,你需要将for循环迭代{0..9}映射到火焰数组{0..flames.length()-1}的有效索引上,这些索引在本例中相同,即{0..5}。
当循环从0到5迭代时,映射是微不足道的。当循环第六次迭代时,你需要将其映射回数组索引0,当它迭代到第七次时,你将其映射到数组索引1,以此类推。
== 幼稚的方法 ==
for(int z = 0, j = 0; z < ctr-1; z++, j++)
{
      if ( j >= flames.length() )
      {
         j = 0; // reset back to the beginning
      }
      res = (flames[j]);
      jLabel1.setText(String.valueOf(res));
}

== 更合适的方式 ==

接下来,您可以通过意识到 flames.length() 是一个不变量,并将其移出 for 循环进行精细调整。

final int n = flames.length();
for(int z = 0, j = 0; z < ctr-1; z++, j++)
{
      if ( j >= n )
      {
         j = 0; // reset back to the beginning
      }
      res = (flames[j]);
      jLabel1.setText(String.valueOf(res));
}

== 如何做到这一点 ==

现在,如果您注意到了,您会发现我们只是对索引进行模算术运算。因此,如果我们使用模(%)运算符,我们可以简化您的代码:

final int n = flames.length();
for(int z = 0; z < ctr-1; z++)
{
      res = (flames[z % n]);
      jLabel1.setText(String.valueOf(res));
}

在处理这样的问题时,考虑函数映射,从一个定义域(在这种情况下为循环迭代)到一个值域(有效的数组索引)。

更重要的是,在开始编码之前,在纸上将其解决。这将帮助您解决此类基本问题。


7

虽然 luis.espinal 的回答在性能方面更好,但我认为你也应该看一下迭代器,因为它们会给你更大的灵活性来回读取。

这意味着你可以像写FLAMESFLAMES一样轻松地写出FLAMESSEMALF等等...

int ctr = 10;
List<Character> flames = Arrays.asList('F','L','A','M','E','S');
Iterator it = flames.iterator();

for(int z=0; z<ctr-1; z++) {
    if(!it.hasNext()) // if you are at the end of the list reset iterator
        it = flames.iterator();

    System.out.println(it.next().toString()); // use the element
}

出于好奇,执行这个循环1M次(100个样本的平均结果)需要花费:

               using modulo: 51ms
            using iterators: 95ms
using guava cycle iterators: 453ms

编辑: 正如lbalazscs所说,循环迭代器更加优雅。它们的价格不菲,而Guava实现则慢了4倍。你可以自己编写实现。

// guava example of cycle iterators
Iterator<Character> iterator = Iterators.cycle(flames);
for (int z = 0; z < ctr - 1; z++) {
    res = iterator.next();
}

更加优雅的方法是使用无限/循环迭代器,就像这样:https://dev59.com/63E85IYBdhLWcg3wwWat - lbalazscs
@lbalazscs 感谢您的评论。我进行了一些测试并进行了更新。 - Frankie

5

您应该使用%来强制索引保持在flames.length范围内,以使其成为有效的索引。

int len = flames.length;
for(int z = 0; z < ctr-1; z++){
      res = (flames[z % len]);
      jLabel1.setText(String.valueOf(res));
}

1
flames.length() 移出 for 循环。这是一个不变量。 - luis.espinal

2
您可以尝试以下方法:
char res;
int ctr = 10
char[] flames = {'F','L','A','M','E','S'};
int n = flames.length();
for(int z = 0; z < ctr-1; z++){
    res = flames[z %n];
    jLabel1.setText(String.valueOf(res));
}

flames.length() 移出 for 循环。这是一个不变量。 - luis.espinal
1
@luis.espinal:喜欢你的第二个想法。我把它加到我的答案里了!谢谢 - Rahul Tripathi

1
这是我会做的方法:
String flames = "FLAMES";
int ctr = 10;

textLoop(flames.toCharArray(), jLabel1, ctr);

textLoop方法:

void textLoop(Iterable<Character> text, JLabel jLabel, int count){
    int idx = 0;
    while(true)
        for(char ch: text){
            jLabel.setText(String.valueOf(ch));
            if(++idx < count) return;
        }
}

编辑:在代码中发现了一个 bug(idx 需要在循环外初始化)。现在已经修复。我还将其重构为一个独立的函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接