【问题标题】:Find values in ArrayList with specific conditions在具有特定条件的 ArrayList 中查找值
【发布时间】:2021-01-26 11:30:24
【问题描述】:

我正在尝试创建一个函数,它提供一个数组列表和两个整数数组作为输入:

public boolean[] checkMatching(ArrayList<String[]> items, Integer[] arr1, Integer[] arr2){...} 

检查是否至少有两个项目的 arr1 索引值相同,但 arr2 的索引值不同。例如:

ArrayList<String[]> items = new ArrayList<String[]>();
//All arrays in items have the same number of elements
items.add(new String[]{"B","2","5","6","W"}); //item1
items.add(new String[]{"A","2","5","6","X"}); //item2
items.add(new String[]{"A","2","1","3","Y"}); //item3
items.add(new String[]{"N","F","1","2","W"}); //item4
items.add(new String[]{"A","2","5","6","V"}); //item5

int[] arr1 = new int[] {1,2};
int[] arr2 = new int[] {0,3,4};

boolean[] results = checkMatching(items, arr1, arr2)

在前面的例子中,函数应该返回:

[-1, 1, -1]

因为 item1item2item5 对于 arr1 的索引共享相同的值,而它们索引 0,4 具有不同的值,索引 3 具有相同的值。

请注意,该方法返回 3 个整数,因为在 arr2 中有 3 个索引。因此,每个布尔值都引用 arr2 中的一个索引。

这可能是一个利用函数式编程的解决方案:

public class Temp2 {
    
    public static void main(String[] args) {
        ArrayList<String[]> items = null;

        items = new ArrayList<String[]>();
        items.add(new String[]{"B","2","5","6","W"}); //item1
        items.add(new String[]{"A","2","5","6","X"}); //item2
        items.add(new String[]{"A","2","1","3","Y"}); //item3
        items.add(new String[]{"N","F","1","2","W"}); //item4
        items.add(new String[]{"A","2","5","6","V"}); //item5
        
        int[] arr1 = new int[] {1,2};
        int[] arr2 = new int[] {0,3,4};

        Temp2 tmp = new Temp2();
        int[] results = tmp.checkMatching(items, arr1, arr2);
        
        for(int k = 0; k < 5; k++) {
            System.out.print(results[k]+"|");
        }
        System.out.println();
    }

    public int[] checkMatching(ArrayList<String[]> items, int[] arr, int[] arr2){
        int maxIndex = 4;

        Function<String[], String> compositeKey = el -> getFunctionParametersTest(el, arr);

        Optional<int[]> map = items.stream()
                .collect(Collectors.groupingBy(compositeKey, Collectors.toList())).entrySet().stream()
                .filter(entry -> entry.getValue().size() > 1)
                .map(entry -> {
                    List<String[]> values = entry.getValue();
                    int[] generalResponse = new int[maxIndex+1];

                    if (values.size() > 1) {
                        for (int i = 0; i < arr2.length; i++) {
                            String tmp = "";
                            int risposta = -2;
                            for (String[] e : values) {
                                if(tmp.length() > 0) {
                                    if (tmp.compareTo(e[arr2[i]]) != 0) {
                                        risposta = -1;
                                        break;
                                    } else {
                                        risposta = 1;
                                    }
                                }
                                else {
                                    tmp = e[arr2[i]];
                                }
                            }
                            generalResponse[i] = risposta;
                        }
                    }
                    return generalResponse;
                }).reduce((a, b) -> {
                    int[] arrLocal = a;
                    int[] arrarrLocal2 = b;
                    int[] sum = new int[a.length];
                    for (int k = 0; k < arrLocal.length; k++) {
                        if (arrLocal[k] == -1 || arrarrLocal2[k] == -1) {
                            sum[k] = -1;
                        } else if (arrLocal[k] >= arrarrLocal2[k]) {
                            sum[k] = arrLocal[k];
                        } else if (arrLocal[k] < arrarrLocal2[k]) {
                            sum[k] = arrarrLocal2[k];
                        }
                    }
                    return sum;
                });

        int[] finalresults = null;
        if (map.isEmpty() == false) {
            finalresults = map.get();
        } else {
            finalresults = new int[maxIndex+1];
            for (int k = 0; k < maxIndex; k++) {
                finalresults[k] = 1;
            }
        }

        return finalresults;
    }
    
    public String getFunctionParametersTest(String[] item, int[] arr) {
        String values = "";
        for (Integer i : arr) {
            values += item[i] + "-";
        }
        return values;
    }

}

但是效率不是很高,尤其是在物品数量很大的时候。有谁能帮助我吗?我正在尝试开发一个性能非常好的解决方案,但我不知道是否有比函数式编程更快的解决方案。

【问题讨论】:

  • 如果应该返回布尔数组,返回[-1, 1, -1]是什么意思?应该是[true, true, true]
  • @AlexRudenko 不,这意味着:[假,真,假]
  • 请使用truefalse 而不是-11。使代码更容易理解。 (是的,我知道没有BooleanStream...至少使用0/1。)

标签: java functional-programming


【解决方案1】:

根据我的理解,我提出了以下解决方案。但是,我不确定是否有任何与 arr1 匹配的列表是您想要比较 arr2 的。您可能需要更改 findAny() 部分以防万一。

public static Boolean[] checkMatching(ArrayList<String[]> items, final int[] arr1, final int[] arr2) {
    // Gets a list containing items that match arr1.
    List<String[]> list = items.stream().collect(Collectors.groupingBy(item ->
            // Groups items together which match in arr1 indexes.
            // Get a stream of indexes contained in arr1.
            Arrays.stream(arr1)
                    // Get a stream of Strings in those indexes.
                    .mapToObj(i -> Arrays.stream(item).collect(Collectors.toList()).get(i))
                    // A list of these Strings is the groupingBy classifier.
                    .collect(Collectors.toList()) 
        )).values().stream().filter(l -> l.size() > 1) // Just get those groups that have more than 1 element.
        .findAny() // Would you want any list that matches arr1?
        .get();

    // Converts arr2 to a Boolean array which says if each index matches all the items in "list".
    Boolean[] matching = Arrays.stream(arr2).mapToObj(i -> list.stream().map(item -> item[i]).distinct().limit(2).count() < 2).toArray(Boolean[]::new);
    return matching;
}

【讨论】:

    【解决方案2】:

    这是我的实现:

    import java.util.*;
    import java.util.function.*;
    
    import static java.util.stream.Collectors.*;
    
    public class Test {
        private static final int LIMIT = 2;
        
        // Implementation ↓
        private static int[] checkMatching(ArrayList<String[]> items,
                int[] keyIndices, int[] valueIndices) {
    
            Function<String[], String> keyFn = (item) -> Arrays.stream(keyIndices)
                    .mapToObj(i -> item[i])
                    .collect(joining());
    
            List<List<String[]>> list = items.stream()
                    .collect(groupingBy(keyFn, toList())).values().stream()
                    .filter(e -> e.size() >= LIMIT)
                    .collect(toList());
    
            return Arrays.stream(valueIndices)
                    .map(i -> list.stream()
                            .anyMatch(lists -> lists.stream()
                                    .map(item -> item[i])
                                    .collect(toSet()).size() == 1) ? 1 : -1)
                    .toArray();
        }
    
        // Test code ↓
        public static void main(String[] args) {
            ArrayList<String[]> items = new ArrayList<>();
            items.add(new String[] {"B", "2", "5", "6", "W"});
            items.add(new String[] {"A", "2", "5", "6", "X"});
            items.add(new String[] {"A", "2", "1", "3", "Y"});
            items.add(new String[] {"N", "F", "1", "2", "W"});
            items.add(new String[] {"A", "2", "5", "6", "V"});
    
            int[] arr1 = new int[] {1, 2};
            int[] arr2 = new int[] {0, 3, 4};
            int[] results = checkMatching(items, arr1, arr2);
    
            System.out.println(Arrays.toString(results));
        }
    }
    

    生成以下输出:

    [-1, 1, -1]
    

    我的做法如下:

    • arr1 的索引匹配的字符串数组列表构建一个映射;
    • 过滤掉没有至少两个元素的列表;
    • arr2 映射到一个布尔列表,以表示映射中至少一个条目的所有值是否相同(使用Set 来检查不同的值)。

    最后一步是优化。您可以从找到一个不同值的那一刻起尽早返回,而不是计算不同值的数量,但这会导致代码稍微不那么优雅和功能更少:

    return Arrays.stream(valueIndices)
            .map(i -> list.stream()
                    .anyMatch(arrays -> {
                        for (int j = 1; j < arrays.size(); j++) {
                            if (!arrays.get(j)[i].equals(arrays.get(j - 1)[i])) {
                                return false;
                            }
                        }
                        return true;
                    }) ? 1 : -1)
            .toArray();
    }
    

    【讨论】:

    • 感谢您的建议。但是,您提出建议的时间比我的要长。我认为单个管道可以加快操作速度。下面我报告了我们提案的相同时间: 执行时间以纳秒为单位:27711000 ns 以毫秒为单位的执行时间:27 ms 以纳秒为单位的运行时间(我的函数):2603700 ns 以毫秒为单位的执行时间)我的函数):2 ms跨度>
    • 是的,我接受。全功能方法往往速度较慢,除非您可以在多个内核上并行执行。应该可以编写一个性能更好的非功能解决方案,特别是如果您考虑将输入替换为二维char[][] 数组。
    • 我已经收紧了我的实施并将我的执行时间减少了大约 40-50%。我不知道你是如何运行测试的,但是你的代码给了我输出 [-1, 1, -1, 0, 0] 并且在我的测试中运行速度比我当前的代码慢 2 到 3 倍。您是否有用于测试的更大数据集?还是数据格式不同?
    猜你喜欢
    • 2018-02-02
    • 1970-01-01
    • 2022-01-15
    • 2019-10-23
    • 2022-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    相关资源
    最近更新 更多