Flutter - 检查 List 中是否存在索引

30

假设我有一个列表:

List<int> numbers = [1, 2, 3, 4, 5,];

如何检查列表中的对象是否存在于特定索引

例如:

if (numbers[6] != null) {
  print('Exists');
}

我本来希望可以做类似那样的事情,但显然它不起作用。


7
在Dart语言中,var isIndexValid = array.asMap().containsKey(index)的意思是判断数组array是否包含索引为index的值,并将结果赋值给变量isIndexValid - Crazy Lazy Cat
asMap() 函数的作用是什么? - Yudhishthir Singh
我们可不可以直接在数组上使用.contains,而不是将其转换为映射然后查找键? - Yudhishthir Singh
@CrazyLazyCat 谢谢。正是我想要的。 - Shalugin
1
@YudhishthirSingh 这不一样。.contains 是检查元素是否存在于数组中。问题是如何检查元素是否存在于特定索引或不存在。 - Shalugin
我知道了,我误读了你的问题。对此感到抱歉。 - Yudhishthir Singh
5个回答

40
你可以将List转换为Map并检查键:
List<int> numbers = [1, 2, 3, 4, 5,];

//Check if index 7 is valid
if (numbers.asMap().containsKey(7)) {
  print('Exists');
} else {
  print('Doesn\'t exist');
}

//[EDIT] Check if item at index 4 exists and is 5
if (numbers.asMap()[4] == 5) {
  print("Index 4 = 5");
} else {
  print("Index 4 != 5");
}

第二部分有点过度了。只需要这样写:if(numbers[4] == 5) - Crazy Lazy Cat
2
直到你使用超出范围的索引,它才会出现问题。在这个例子中,(numbers[9] == 5)将触发一个异常,但是(numbers.asMap()[9]==5)将返回false。 - Carlos Javier Córdova Reyes
1
这个条件 if (numbers.asMap()[10] == null) 怎么样?在你的情况下它不会起作用。 - mezoni
没错。如果你要检查一个空值,你也需要使用第一部分。例如 if ((numbers.asMap().containsKey(10)) && (numbers.asMap()[10] == null)) - Carlos Javier Córdova Reyes
如果列表 List<int> numbers = [0,1, 2, 3, 4, 5,6,7,8,9,10]; 中搜索 0,则会返回最后一项 10 的键。 - giorgio79
显示剩余2条评论

32

我对其他提供的答案感到困惑。从问题文本来看,所有这些辅助函数和将列表转换为映射等操作都是过度的杀伤力。用一个简单的if检查有什么问题吗?

var list = [1, 2, 3, 4, 5];
var index = 4;
var value = 5;

if (list.length > index && list[index] == value) {
  ...
}

优雅在于简单,而不是某种代码格式。将列表转换为映射需要工作量,而且你会复制数据,这样做没有任何理由。如今,代码的可读性可能比性能更重要,但这并不是使用明知不好的做法来获得如此微小的美学收益的理由。
即使上述方法真的让你感到很困扰,你也可以将其包装在扩展方法中:
extension ListExtensions<T> on List<T> {
  bool containsAt(T value, int index) {
    assert(this != null);
    return index >= 0 && this.length > index && this[index] == value;
  }
}

// Usage

var list = [1, 2, 3, 4, 5];
var index = 4;
var value = 5;

if(list.containsAt(value, index)) {
  ...
}

编辑:在 Swift 中,array.indices 字段是一个 Range,调用 contains 方法会检查一个值是否在该范围内。由于 Range 的工作方式,检查值是否在其中是一个常数时间操作,这就是它如此高效的原因。事实上,在 Swift 中,以下方法的性能几乎相同:

let array = [1, 2, 3, 4, 5]
let idx = 2

// array.indices approach
if array.indices.contains(idx) {
  ...
}

// Manual check approach
if idx >= 0 && idx < array.count {
  ...
}

Flutter没有Range类型,因此尝试进行代码杂技以获得等效的代码结果会导致一种极其低效的方式来简单地检查列表中是否存在索引。例如,以下是所选答案中list.asMap().contains(idx)方法与其纯代码等效方法之间的比较:

var list = [1, 2, 3, 4, 5];
var idx = 2;

// asMap approach
if (list.asMap().containsKey(idx)) {
  ...
}

// Manual conversion and check approach
Map<int, int> map = {};
for (var i = 0; i < list.length; i++) {
  map[i] = list[i];
} 
if (map.containsKey(idx)) {
  ...
}

正如您所看到的,将列表转换为Map是一个线性过程,而不是一个恒定的过程,因此如果列表很长,这可能需要相当长的时间。不仅如此,您还会创建一个完全多余的Map对象,该对象包含列表的所有元素以及索引作为其键,因此您实际上已经将内存占用量增加了一倍(或甚至三倍,考虑到映射同时存储键和值)。希望您能看出,相对于“正常”方式,检查列表是否包含索引的这种方法在各方面都更差。(并且在评论中,他建议调用 asMap 两次???)
在Dart中制作Range类型并不那么难,使用上述扩展方法的方法,您可以实现完全相同的语法和性能:
class Range extends Iterable<int> {
  const Range(this.start, this.end) : assert(start <= end);
  const Range.fromLength(int length) : this(0, length - 1);

  final int start;
  final int end;

  int get length => end - start + 1;

  @override
  Iterator<int> get iterator => Iterable.generate(length, (i) => start + i).iterator;

  @override
  bool contains(Object? index) {
    if (index == null || index is! int) return false;
    return index >= start && index <= end;
  }

  @override
  String toString() => '[$start, $end]';
}

(list_extensions.dart)

import 'range.dart';

extension ListExtensions on List {
  Range get indices => Range.fromLength(this.length);
}

(main.dart)

import 'list_extensions.dart';

main() {
  final list = [1, 2, 3, 4, 5];
  print(list.indices);              // [0, 4]
  print(list.indices.contains(3));  // true
  print(list.indices.contains(5));  // false
  print(list.indices.contains(-1)); // false
}

说了这么多,你提出的问题的第二个方面并没有得到解决,你仍然需要检查索引本身的值。(请注意,你也必须在Swift中执行此操作)
if (list.indices.contains(index) && list[index] == value) {
  // `value` exists in the list at `index`
  ...
}

你说得对。我喜欢“map”答案的原因是我来自Swift,在Swift中,要检查索引,你宁愿做array.indices.contains(index)而不是检查数组长度。所以,带有.containsKey(index)的map答案对我来说更加熟悉。我同意它会做一些不必要的额外工作。那么我的问题是,为什么在Flutter中不能像在Swift中那样访问列表的索引。难道Swift在幕后也执行了相同的列表到映射转换工作吗? - Shalugin
@Shalugin 我已经对我的答案进行了编辑,回答了这些问题。 - Abion47

1

有一个强大的原生包用于处理集合,在解决这个问题的同时,您可以在其中找到很多有趣的东西。

import 'package:collection/collection.dart';

...

List<int> numbers = [1, 2, 3, 4, 5,];

int element = numbers.firstWhereIndexedOrNull((i, _) => i == 6);

if (element != null) {
  print('Exists');
}

1
集合包很棒。我一直使用firstWhereOrNull,因为它可以清理掉我的代码中的许多一次性函数,就像其他建议一样。 - Nick N

1

方法1

List<int> numbers = [1, 2, 3, 4, 5,];
      int index = 3,find = 4;

      //case 1 
      if(index<numbers.length && numbers[index] == find)
        print('$find exists at index $index');
      else
        print('$find not found at index $index');


      //case 2
      index = 7;

      if(index<numbers.length &&numbers[index] == find)
        print('$find exists at index $index');
      else
        print('$find not found at index $index');

方法二(编辑#已添加)
int index = 3, find = 4, foundAt;

  foundAt = numbers.indexOf(find);

  //case 1
  if (index == foundAt)
    print('Index of $find is $foundAt');
  else
    print('Value not found at $index');

  //case 2
  index = 7;
  if (index == foundAt)
    print('Index of $find is $foundAt');
  else
    print('Value not found at $index');

我会点赞,因为它有效。虽然我希望能找到更优雅的解决方案。就像这个Swift的解决方案一样 https://dev59.com/SF8e5IYBdhLWcg3wNINp#35512668 - Shalugin
@Shalugin,如果“优雅”的版本是一个可怕的坏实践,那么谁在乎它是否不够优美?如果可见的语法让你感到不舒服,把它包装在一个辅助方法中,这样你就永远不用看它了。 - Abion47

-1

最简单的方法是 =>

List<int> numbers = [1, 2, 3, 4, 5,];    
if ($(numbers,6) != null) {
    print('Exists');
}

据我所知,$符号用于字符串插值。然而,提出的解决方案会导致分析器错误(“函数'$'未定义”)。 - mr_mmmmore

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