减少 Dart 中的嵌套 for 循环

时间:2021-03-11 19:36:33

标签: for-loop dart

我正在尝试将天数附加到日历中的现有月份。我有一个名为“Calendar”的对象,它有一个“CalendarMonths”列表,每个月都有一个“CalandarDays”列表。下面的代码可以将一天添加到现有月份,但我不喜欢嵌套的 for 循环......我需要一些帮助和建议,是否有可以减少我的问题的函数或算法?

for (var month in calendar.months) {
  CalendarMonth calendarMonth = getExistingCalendarMonth(month, oldCalendar);
  if (CalendarMonth != null) {
    for (var day in month.days) {
      CalendarDay calendarDay = getExistingCalendarDay(day, calendarMonth);
      if (calendarDay != null) {
        for (var item in day.calendarItems) {
          calendarDay.calendarItems.add(item);
        }
      } else {
        calendarMonth.days.add(day);
      }
    }
  } else {
    oldCalendar.months.add(month);
  }
}

CalendarMonth getExistingCalendarMonth(CalendarMonth month, Calendar old) {
for (var calendarMonth in old.months) {
  if (calendarMonth.title == month.title) {
    return calendarMonth;
  }
}
return null;}

CalendarDay getExistingCalendarDay(CalendarDay day, CalendarMonth old) {
for (var calendarDay in old.days) {
  if (calendarDay.day == day.day) {
    return calendarDay;
  }
}
return null;}

1 个答案:

答案 0 :(得分:1)

问题的主要症结似乎来自于您选择将日历的月份和日期表示为列表的事实,这迫使您遍历整个列表以找到具有特定值的对象。当您尝试通过列表中的索引以外的其他方式查找列表中的元素时,您使用的数据结构是错误的。

您需要使用的是 Map。这个数据结构在很多地方都有一个不同的名字,比如 HashMapHashTableRecordDictionary(所有这些都对它们如何产生微妙的不同影响使用,但它们在一般情况下是可以互换的)。这个想法是您使用键将数据存储在表中,然后您可以使用该键在恒定时间内引用该数据。例如:

Map<String, int> map = {
  'a': 123,
  'b': 456,
  'c': 789,
};
print(map['b']);
// Output: 456

我创建了一个键类型为 String 和值类型为 int 的映射,这意味着我在映射中存储了一堆整数并使用字符串值来引用它们。之后,我使用与列表用于检索数据的索引符号相同的索引表示法,但我没有提供整数索引,而是使用字符串键并告诉地图给我存储在该键下的数据。在这种情况下,我将提取存储在键“b”中的值,即整数值 456

在您的情况下,您的 Calendar 类将 months 定义为一个对象列表,每个对象存储其名称和另一个 days 列表。相反,使用 months 的地图,然后您可以将其与该月份的名称一起使用以直接获取月份:

class Calendar {
  ...
  final months = <String, CalendarMonth>{};
}

class CalendarMonth {
  ...
  final String title;
  final days = <int, CalendarDay>{};
}

class CalendarDay {
  ...
  final int monthIndex;
  final calendarItems = <CalendarItem>[];
}

这里我使用 Dart 的速记语法来创建映射 <String, CalendarMonth>{},这意味着是键类型为 String 和值类型为 CalendarMonth 的空映射。我还使用 int 作为 CalendarMonth.days 的键,这可能看起来很奇怪,因为使用整数键似乎使用带有额外步骤的列表,但这里的主要区别在于映射的整数键不需要表示列表中存在的索引,因此可以是您想要的任何整数。 (它们甚至不需要是连续的。)

现在你可以像这样重构你的插入循环:

for (var month in calendar.months.values) {
  CalendarMonth calendarMonth = oldCalendar.months[month.title];
  if (CalendarMonth != null) {
    for (var day in month.days.values) {
      CalendarDay calendarDay = calendarMonth.days[day.monthIndex];
      if (calendarDay != null) {
        calendarDay.calendarItems.addAll(dat.calendarItems);
      } else {
        calendarMonth.days[day.monthIndex] = day;
      }
    }
  } else {
    oldCalendar.months[month.title] = month;
  }
}

请注意,由于您要迭代新的月份和日期中的值,因此您无法完全避免使用 for 循环(因为迭代的整个概念就是循环的设计目的)。如果需要,您可以通过将逻辑移动到 CalendarCalendarMonthCalendarDay 类本身来清理它:

class Calendar {
  ...
  
  void merge(Calendar other) {
    for (var month in other.months.values) {
      if (months.containsKey(month.title)) {
        months[monthTitle].merge(month);
      } else {
        months[monthTitle] = month;
      }
    }
  }
}

class CalendarMonth {
  ...

  void merge(CalendarMonth other) {
    for (var day in other.days.values) {
      if (days.containsKey(day.monthIndex)) {
        days[day.monthIndex].merge(day);
      } else {
        days[day.monthIndex] = day;
      }
    }
  }
}

class CalendarDay {
  ...

  void merge(CalendarDay other) {
    calendarItems.addAll(other.calendarItems);
  }
}

这将使您的原始代码如下所示:

oldCalendar.merge(calendar);