【问题标题】:String list get an item starting without loop字符串列表获取一个没有循环开始的项目
【发布时间】:2016-10-15 12:39:55
【问题描述】:

我有一个包含数千条数据的数组列表。

例如:

List<String> custNames = new ArrayList<String>();
custNames.add("John");
custNames.add("Tom");
custNames.add("Bart");
custNames.add("Tim");
custNames.add("Broad");

现在我想计算仅以“T”开头的名称。我的解决方案使用了循环机制。

List<String> filterNames = new ArrayList<String>();
String nameStarts="T";
for(int i=0;i<custNames.size();i++)
{
    if(custNames.get(i).toLowerCase().startsWith(nameStarts.toLowerCase()))
    {
        filterNames.add(custNames.get(i));
    }
}
System.out.println(filterNames.size());

但我在这个 custNames 列表中有大量数据。 不使用循环有什么不同的解决方案吗?

谢谢。

【问题讨论】:

    标签: java arraylist collections


    【解决方案1】:

    Java 8 为您的问题提供了非常好的解决方案。

    试试这个,

    long filterNameCount = custNames
            .stream()
            .parallel() 
            .filter((s) -> s.startsWith(nameStarts.toLowerCase()))
            .count();
    
    System.out.println(filterNameCount);
    

    【讨论】:

    • 使用 .stream().parallel() 获得显着的性能提升
    • 对此要非常非常小心。如果你的输入不是特别大,使用 parallel() 会严重影响性能并使代码变慢。
    • 我认为您在调用 .parallel() 后缺少一个 .map(String::toLowerCase)
    【解决方案2】:

    如果您愿意使用第三方库,您可以使用Eclipse Collections 使用一些有趣的选项。

    如果您使用上面的ArrayList,您可以使用LazyIterate 实用程序,如下所示:

    int count = LazyIterate.collect(custNames, String::toLowerCase)
            .countWith(String::startsWith, nameStarts.toLowerCase());
    Assert.assertEquals(2, count);
    

    如果您使用 Eclipse Collections 替代 ArrayList,您可以使用直接在 MutableList 上提供的丰富功能协议:

    MutableList<String> custNames =
            Lists.mutable.with("John", "Tom", "Bart", "Tim", "Broad");
    String nameStarts= "T";
    int count = custNames.asLazy()
            .collect(String::toLowerCase)
            .countWith(String::startsWith, nameStarts.toLowerCase());
    System.out.println(count);
    Assert.assertEquals(2, count);
    

    Eclipse Collections 中的串行 API 默认是 Eager-by-default,这就是我首先调用 asLazy() 的原因。否则 collect 方法会创建另一个 MutableList

    如果您使用完整的数据集对代码进行基准测试,则以下代码的并行版本可能会更高效:

    MutableList<String> custNames =
            Lists.mutable.with("John", "Tom", "Bart", "Tim", "Broad");
    String nameStarts= "T";
    int processors = Runtime.getRuntime().availableProcessors();
    int batchSize = Math.max(1, custNames.size() / processors);
    ExecutorService executor = Executors.newFixedThreadPool(processors);
    int count = custNames.asParallel(executor, batchSize)
            .collect(String::toLowerCase)
            .countWith(String::startsWith, nameStarts.toLowerCase());
    executor.shutdown();
    Assert.assertEquals(2, count);
    

    Eclipse Collections 中的asParallel() API 默认是惰性的。 API 强制您传入 ExecutorServiceint batchSize。这使您可以完全控制并行度。

    您还可以将 Stream API 与 Eclipse Collections 中的所有 MutableCollections 一起使用,因为它们扩展了 java.util.Collection

    注意:我是 Eclipse Collections 的提交者。

    【讨论】:

      【解决方案3】:

      您还可以使用树存储:对于这种搜索,它会非常有效。如果你被一个列表困住了,前面的回答是一种方法。

      【讨论】:

        【解决方案4】:

        像这样删除所有不以“T”开头的项目:

        custNames.removeIf(p->!p.startsWith("T"));
        

        您可以从列表中复制并删除不以“T”开头的项目。

        【讨论】:

          【解决方案5】:

          首先,您可以使用Arrays.asList(T) 缩短初始化时间;其次,我将使用一个简单的循环来构建计数表once,然后使用它来确定后续查询。比如,

          List<String> custNames = new ArrayList<String>(Arrays.asList("John", "Tom", 
                  "Bart", "Tim", "Broad"));
          int[] counts = new int[26];
          for (String name : custNames) {
              char ch = Character.toLowerCase(name.charAt(0));
              counts[ch - 'a']++;
          }
          for (int i = 0; i < counts.length; i++) {
              if (counts[i] > 0) {
                  System.out.printf("There are %d words that start with %c%n", 
                          counts[i], (char) ('a' + i));
              }
          }
          

          哪些输出

          There are 2 words that start with b
          There are 1 words that start with j
          There are 2 words that start with t
          

          或者,在特定情况下 - counts['t' - 'a'] 是以 t 开头的字数。

          【讨论】:

            【解决方案6】:

            如果您有或多或少的静态列表并经常执行搜索操作,您可以对列表进行排序或使用 TreeMap。

            您也不需要创建新列表并获取其大小。您可以简单地创建一个计数器变量并将其递增。

            【讨论】:

              【解决方案7】:

              您可以创建自己的排序和查找实现。

              考虑以下几点:

              public class ContainingArrayList<E> extends ArrayList<E> {
                  private Comparator<E> comparator;
              
                  public ContainingArrayList(Comparator<E> comparator) {
                      this.setComparator(comparator);
                  }
              
                  @Override
                  public boolean add(E e) {
                      // If the collection is empty or the new element is bigger than the last one, append it to the end of the collection
                      if(size() == 0 || comparator.compare(e, get(size()-1)) >= 0)
                          return super.add(e);
                      else {
                          for (int i = 0; i < size(); i++) {
                              int result = comparator.compare(e, get(i));
                              // If the new element is bigger than the current element, continue with the next element
                              if (result > 0) continue;
                              // If the new element is equal to the current element, no need to insert (you might insert of course)
                              if (result == 0) return false;
                              // Otherwise the new element is smaller than the current element, so insert it between the previous and the current element
                              super.add(i, e);
                              return true;
                          }
                          return super.add(e);
                      }
                  }
              
                  public E get(E containingElement) {
                      int start = 0;
                      int end = size()-1;
                      // If the element is the first one, return the first element
                      if(comparator.compare(containingElement, super.get(start)) == 0)
                          return super.get(start);
                      // If the element is the last one, return the last element
                      if(comparator.compare(containingElement, super.get(end)) == 0)
                          return super.get(end);
              
                      // Otherwise do a binary search
                      while(start != end) {
                          // Get the element between start and end positions
                          E mid = super.get(start + (end/2));
                          // Compare the two elements
                          int result = comparator.compare(containingElement, mid);
                          // If the middle element compared to the containing element is equal, return the middle element
                          if(result == 0) {
                              return mid;
                          }
                          // If the containing element is smaller than the middle, halve the end position
                          else if(result < 0) {
                              end = start + (end/2);
                          }
                          // If the containing element is bigger than the middle, set the start position to the middle position
                          else if(result > 0) {
                              start = start + (end/2);
                          }
                      }
                      return null;
                  }
              
              
                  public Comparator<E> getComparator() {
                      return comparator;
                  }
              
                  public void setComparator(Comparator<E> comparator) {
                      this.comparator = comparator;
                  }
              }
              

              自定义比较器用于对元素进行排序并查找以特定字符开头的元素。这意味着您可以随时根据需要更改比较器实现,也可以创建更动态的查找解决方案。

              测试:

              public class SortFindTest {
              
                  public SortFindTest() {
                      ContainingArrayList<String> t = new ContainingArrayList<String>(new MyComparator());
                      t.add("John");
                      t.add("Tom");
                      t.add("Bart");
                      t.add("Tim");
                      t.add("Broad");
              
                      System.out.println(t.get("T"));
                  }
              
                  class MyComparator implements Comparator<String> {
                      @Override
                      public int compare(String o1, String o2) {
                          int o1c = o1.charAt(0);
                          int o2c = o2.charAt(0);
                          if(o1c == o2c)
                              return 0;
                          if(o1c > o2c)
                              return 1;
                          return -1;
                      }
              
                  }
              
                  public static void main(String[] args) {
                      new SortFindTest();
                  }
              }
              

              我不确定这是否会比 Java 8 Stream API 更快,但值得一试。

              【讨论】:

                【解决方案8】:

                如果存储项目的顺序无关紧要,您可以将名称存储在 HashMap 中,其中每个名称的第一个字符是键,而具有该第一个字符的名称的 ArrayList 是值。然后你需要做的就是,假设 HashMap 被命名为 customerList,就是 customerList.get("T").size()。

                初始化 HashList 并添加客户

                HashMap<Character, ArrayList<String>> customerList = new HashMap<Character, ArrayList<String>>();
                int NUM_ALPHABETS = 26;
                int ascii_char = 97;
                for(int i = 0; i < NUM_ALPHABETS; i++){
                  char c = (char) ascii_char;
                  customerList.add(c, new ArrayList<String>());
                  ascii_char++;
                }
                
                customerList.get("t").add("Tony");
                customerList.get("a").add("Alice");
                customerList.get("b").add("Ben");
                

                获取以“t”开头的客户数

                 int num_t = customerList.get("t").size();
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-08-31
                  • 2022-08-14
                  • 2015-06-11
                  • 2014-08-21
                  • 2016-01-12
                  • 1970-01-01
                  • 2020-12-07
                  相关资源
                  最近更新 更多