Giao lộ và liên kết của ArrayLists trong Java


130

Có phương pháp nào để làm như vậy không? Tôi đã tìm kiếm nhưng không thể tìm thấy bất kỳ.

Một câu hỏi khác: Tôi cần các phương thức này để tôi có thể lọc các tệp. Một số là ANDcác bộ lọc và một số là ORcác bộ lọc (như trong lý thuyết tập hợp), vì vậy tôi cần lọc theo tất cả các tệp và các ArrayLists unite / giao cắt giữ các tệp đó.

Tôi có nên sử dụng cấu trúc dữ liệu khác để giữ các tệp không? Có bất cứ điều gì khác sẽ cung cấp một thời gian chạy tốt hơn?


1
Nếu bạn không muốn tạo ra một danh sách mới, Vector.retain ALL (Vector) cắt vectơ gốc của bạn thành chỉ giao điểm với vectơ thứ hai.
dùng2808054

@ user2808054 tại sao Vector? Lớp đó đã không được khuyến khích kể từ Java 1.2.
dimo414

@ dimo414 một giao diện mà tôi đang sử dụng (tôi không có tùy chọn) trả về mọi thứ dưới dạng vectơ. Tôi không biết nó đã được khuyến khích! Cảm ơn thông tin .. Không khuyến khích ai? Tôi chưa thấy bất kỳ ghi chú nào về việc nó bị phản đối vì vậy đây là một điều bất ngờ
user2808054

1
Từ Javadocs: " Kể từ nền tảng Java 2 v1.2 ... nên sử dụng ArrayList thay cho Vector. ". Thời gian duy nhất bạn có thể cần Vectorlà cho các tương tác xuyên luồng, nhưng cũng có các cấu trúc dữ liệu an toàn hơn cho các trường hợp sử dụng đó. Xem thêm câu hỏi này . Bất kỳ thư viện nào vẫn sử dụng Vectortrong năm 2016 là rất đáng ngờ theo ý kiến ​​của tôi.
dimo414

@ dimo414 là thư viện của IBM, haha! (Lotus dữ liệu api). Cảm ơn thông tin, rất hữu ích
user2808054 8/11/2016

Câu trả lời:


122

Đây là một triển khai đơn giản mà không cần sử dụng bất kỳ thư viện của bên thứ ba. Ưu điểm chính trên retainAll, removeAlladdAlllà những phương pháp này không thay đổi danh mục ban đầu vào cho các phương pháp.

public class Test {

    public static void main(String... args) throws Exception {

        List<String> list1 = new ArrayList<String>(Arrays.asList("A", "B", "C"));
        List<String> list2 = new ArrayList<String>(Arrays.asList("B", "C", "D", "E", "F"));

        System.out.println(new Test().intersection(list1, list2));
        System.out.println(new Test().union(list1, list2));
    }

    public <T> List<T> union(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<T>();

        set.addAll(list1);
        set.addAll(list2);

        return new ArrayList<T>(set);
    }

    public <T> List<T> intersection(List<T> list1, List<T> list2) {
        List<T> list = new ArrayList<T>();

        for (T t : list1) {
            if(list2.contains(t)) {
                list.add(t);
            }
        }

        return list;
    }
}

16
bạn có thể tạo danh sách mới với các phần tử list1 và sau đó gọi phương thức keep ALL, add ALL
lukastymo

Tại sao bạn sử dụngrictfp trong giải pháp này?
lukastymo

9
Nên sử dụng một HashSetfor intersectionđể hiệu suất trường hợp trung bình là O (n) thay vì O (n ^ 2).
Zong

1
Bài đăng này có thể sử dụng bản cập nhật để chứng minh các lợi ích của API luồng Java 8.
SME_Dev

Tôi gặp lỗi Khi tôi thử gán giá trị này -> Ví dụ: ArrayList <String> Total Total = (ArrayList <String>) giao nhau (list2, list1) ---> không thể truyền java.util.arraylist cho java.util.arraylist < chuỗi>
delive

123

Bộ sưu tập (vì vậy ArrayList cũng có):

col.retainAll(otherCol) // for intersection
col.addAll(otherCol) // for union

Sử dụng triển khai Danh sách nếu bạn chấp nhận sự lặp lại, cài đặt Cài đặt nếu bạn không:

Collection<String> col1 = new ArrayList<String>(); // {a, b, c}
// Collection<String> col1 = new TreeSet<String>();
col1.add("a");
col1.add("b");
col1.add("c");

Collection<String> col2 = new ArrayList<String>(); // {b, c, d, e}
// Collection<String> col2 = new TreeSet<String>();
col2.add("b");
col2.add("c");
col2.add("d");
col2.add("e");

col1.addAll(col2);
System.out.println(col1); 
//output for ArrayList: [a, b, c, b, c, d, e]
//output for TreeSet: [a, b, c, d, e]

3
Có một chỉnh sửa được đề xuất rằng liên minh này "không chính xác vì nó sẽ chứa các yếu tố phổ biến hai lần" . Chỉnh sửa đề nghị sử dụng HashSetthay thế.
Kos

5
Trên thực tế, nó đã được chỉnh sửa, xem: "Sử dụng triển khai Danh sách nếu bạn chấp nhận lặp lại, triển khai Cài đặt nếu bạn không:"
lukastymo

7
Không, giữ lại Tất cả không phải là giao điểm cho danh sách. Ở trên, tất cả các thành phần trong col không có trong otherCol đều bị xóa. Giả sử otherCol là {a, b, b, c} và col là {b, b, b, c, d}. Sau đó, col kết thúc bằng {b, b, b, c} không phải là giao điểm của hai. Tôi hy vọng rằng đó là {b, b, c}. Một hoạt động khác nhau đang được thực hiện.
demongolem 18/03/2016

1
Tôi cũng không thấy làm thế nào addAll()là liên minh cho danh sách; nó chỉ nối các danh sách thứ hai vào cuối danh sách thứ nhất. Một hoạt động hợp nhất sẽ tránh thêm một phần tử nếu danh sách đầu tiên đã chứa nó.
dimo414

66

Bài đăng này khá cũ, tuy nhiên nó là bài đầu tiên xuất hiện trên google khi tìm kiếm chủ đề đó.

Tôi muốn cung cấp một bản cập nhật bằng cách sử dụng các luồng Java 8 (về cơ bản) điều tương tự trong một dòng duy nhất:

List<T> intersect = list1.stream()
    .filter(list2::contains)
    .collect(Collectors.toList());

List<T> union = Stream.concat(list1.stream(), list2.stream())
    .distinct()
    .collect(Collectors.toList());

Nếu bất cứ ai có giải pháp tốt hơn / nhanh hơn hãy cho tôi biết, nhưng giải pháp này là một lớp lót đẹp có thể dễ dàng đưa vào một phương thức mà không cần thêm lớp / phương thức trợ giúp không cần thiết mà vẫn giữ được khả năng đọc.


19
Ôi, nó có thể là một lớp lót đẹp nhưng phải mất thời gian O (n ^ 2). Chuyển đổi một trong các danh sách thành Setsau đó sử dụng containsphương thức của tập hợp . Không phải tất cả mọi thứ trong cuộc sống phải được thực hiện với các luồng.
dimo414

31
list1.retainAll(list2) - is intersection

Công đoàn sẽ removeAllvà sau đó addAll.

Tìm thêm trong tài liệu về bộ sưu tập (ArrayList là một bộ sưu tập) http://doad.oracle.com/javase/1.5.0/docs/api/java/util/Collection.html


1
Cả hai retainAll()removeAll()là các hoạt động O (n ^ 2) trong danh sách. Chúng ta có thể làm tốt hơn.
dimo414

1
Tôi đã bỏ phiếu nhưng bây giờ tôi có một câu hỏi. retainAllcủa {1, 2, 2, 3, 4, 5} trên {1, 2, 3} cho kết quả {1, 2, 2, 3}. Không phải là {1, 2, 3} là giao điểm sao?
GyuHyeon Choi

21

Liên hiệp và giao lộ chỉ được xác định cho các bộ, không phải danh sách. Theo như bạn đã đề cập.

Kiểm tra thư viện ổi cho các bộ lọc. Ngoài ra ổi cung cấp các giao lộ và công đoàn thực sự

 static <E> Sets.SetView<E >union(Set<? extends E> set1, Set<? extends E> set2)
 static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)

12

Bạn có thể sử dụng CollectionUtilstừ apache commons .


7
Trong trường hợp ai đó tìm thấy câu trả lời này hơi ngắn: 'CollectionUtils.containsAny' và 'CollectionUtils.contains ALL' là các phương thức.
Sebastian

2
Thật kỳ lạ khi CollectionUtils từ apache commons không hỗ trợ thuốc generic
Vasyl Sarzhynskyi

7

Các giải pháp được đánh dấu là không hiệu quả. Nó có độ phức tạp thời gian O (n ^ 2). Những gì chúng ta có thể làm là sắp xếp cả hai danh sách và thực hiện thuật toán giao nhau như dưới đây.

private  static ArrayList<Integer> interesect(ArrayList<Integer> f, ArrayList<Integer> s) { 
    ArrayList<Integer> res = new ArrayList<Integer>();

    int i = 0, j = 0; 
    while (i != f.size() && j != s.size()) { 

        if (f.get(i) < s.get(j)) {
            i ++;
        } else if (f.get(i) > s.get(j)) { 
            j ++;
        } else { 
            res.add(f.get(i)); 
            i ++;  j ++;
        }
    }


    return res; 
}

Cái này có độ phức tạp là O (n log n + n) nằm trong O (n log n). Liên minh được thực hiện theo cách tương tự. Chỉ cần đảm bảo rằng bạn thực hiện các sửa đổi phù hợp trên các câu lệnh if-otherif-other.

Bạn cũng có thể sử dụng các trình vòng lặp nếu bạn muốn (Tôi biết rằng chúng hiệu quả hơn trong C ++, tôi không biết điều này có đúng trong Java không).


1
Không đủ chung chung, T có thể không thể so sánh được và trong một số trường hợp so sánh là đắt đỏ ...
Boris Churzin

Không chung chung, tôi hoàn toàn đồng ý. So sánh có đắt không? Làm thế nào bạn sẽ giải quyết điều đó?
AJed

Đáng buồn thay - sẽ rẻ hơn khi làm điều đó trong O (n ^ 2) :) Đối với số, giải pháp này rất tốt ...
Boris Churzin

Đáng buồn thay - bạn đã không trả lời câu hỏi của tôi. Hãy để tôi viết lại nó, làm thế nào tốt hơn O (n ^ 2) với chức năng so sánh của chi phí c (n)?
AJed

1
Chuyển đổi một đầu vào thành một tập hợp và gọi contains()trong một vòng lặp (như Devenv là gợi ý) sẽ mất thời gian O (n + m). Sắp xếp rất phức tạp và mất thời gian O (n log n + m log n + n). Được cho là giảm thời gian O (n log n), nhưng điều đó vẫn tệ hơn thời gian tuyến tính, và phức tạp hơn nhiều.
dimo414

4

Tôi nghĩ bạn nên sử dụng a Setđể giữ các tệp nếu bạn muốn thực hiện giao cắt và hợp nhất với chúng. Sau đó, bạn có thể sử dụng ổi 's Sets lớp để làm union, intersectionvà lọc theo Predicatelà tốt. Sự khác biệt giữa các phương thức này và các đề xuất khác là tất cả các phương thức này tạo ra các khung nhìn lười biếng về sự kết hợp, giao nhau, v.v. của hai bộ. Apache Commons tạo ra một bộ sưu tập mới và sao chép dữ liệu vào nó. retainAllthay đổi một trong các bộ sưu tập của bạn bằng cách loại bỏ các yếu tố khỏi nó.


4

Đây là cách bạn có thể thực hiện giao lộ với các luồng (hãy nhớ rằng bạn phải sử dụng java 8 cho các luồng):

List<foo> fooList1 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<foo> fooList2 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
fooList1.stream().filter(f -> fooList2.contains(f)).collect(Collectors.toList());

Một ví dụ cho danh sách với các loại khác nhau. Nếu bạn có một cảnh giới giữa foo và bar và bạn có thể lấy một đối tượng thanh từ foo hơn là bạn có thể sửa đổi luồng của mình:

List<foo> fooList = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<bar> barList = new ArrayList<>(Arrays.asList(new bar(), new bar()));

fooList.stream().filter(f -> barList.contains(f.getBar()).collect(Collectors.toList());

3
  • giữ lại tất cả sẽ sửa đổi danh sách của bạn
  • Quả ổi không có API cho Danh sách (chỉ dành cho bộ)

Tôi thấy ListUtils rất hữu ích cho trường hợp sử dụng này.

Sử dụng ListUtils từ org.apache.commons.collections nếu bạn không muốn sửa đổi danh sách hiện có.

ListUtils.intersection(list1, list2)


3

Bạn có thể sử dụng commons-sưu tập4 CollectionUtils

Collection<Integer> collection1 = Arrays.asList(1, 2, 4, 5, 7, 8);
Collection<Integer> collection2 = Arrays.asList(2, 3, 4, 6, 8);

Collection<Integer> intersection = CollectionUtils.intersection(collection1, collection2);
System.out.println(intersection); // [2, 4, 8]

Collection<Integer> union = CollectionUtils.union(collection1, collection2);
System.out.println(union); // [1, 2, 3, 4, 5, 6, 7, 8]

Collection<Integer> subtract = CollectionUtils.subtract(collection1, collection2);
System.out.println(subtract); // [1, 5, 7]

2

Trong Java 8, tôi sử dụng các phương thức trợ giúp đơn giản như thế này:

public static <T> Collection<T> getIntersection(Collection<T> coll1, Collection<T> coll2){
    return Stream.concat(coll1.stream(), coll2.stream())
            .filter(coll1::contains)
            .filter(coll2::contains)
            .collect(Collectors.toSet());
}

public static <T> Collection<T> getMinus(Collection<T> coll1, Collection<T> coll2){
    return coll1.stream().filter(not(coll2::contains)).collect(Collectors.toSet());
}

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

1

Nếu các đối tượng trong danh sách có thể băm (nghĩa là có hàm hashCode và hàm bằng), cách tiếp cận nhanh nhất giữa các bảng xấp xỉ. kích thước> 20 là để xây dựng Hashset cho danh sách lớn hơn trong hai danh sách.

public static <T> ArrayList<T> intersection(Collection<T> a, Collection<T> b) {
    if (b.size() > a.size()) {
        return intersection(b, a);
    } else {
        if (b.size() > 20 && !(a instanceof HashSet)) {
            a = new HashSet(a);
        }
        ArrayList<T> result = new ArrayList();
        for (T objb : b) {
            if (a.contains(objb)) {
                result.add(objb);
            }
        }
        return result;
    }
}

1

Tôi cũng đang làm việc với tình huống tương tự và đến đây để tìm kiếm sự giúp đỡ. Đã kết thúc việc tìm giải pháp của riêng tôi cho Mảng. ArrayList absentDates = new ArrayList (); // Sẽ lưu trữ Array1-Array2

Lưu ý: Đăng bài này nếu nó có thể giúp ai đó truy cập trang này để được giúp đỡ.

ArrayList<String> AbsentDates = new ArrayList<String>();//This Array will store difference
      public void AbsentDays() {
            findDates("April", "2017");//Array one with dates in Month April 2017
            findPresentDays();//Array two carrying some dates which are subset of Dates in Month April 2017

            for (int i = 0; i < Dates.size(); i++) {

                for (int j = 0; j < PresentDates.size(); j++) {

                    if (Dates.get(i).equals(PresentDates.get(j))) {

                        Dates.remove(i);
                    }               

                }              
                AbsentDates = Dates;   
            }
            System.out.println(AbsentDates );
        }

1

Giao lộ của hai danh sách các đối tượng khác nhau dựa trên khóa chung - Java 8

 private List<User> intersection(List<User> users, List<OtherUser> list) {

        return list.stream()
                .flatMap(OtherUser -> users.stream()
                        .filter(user -> user.getId()
                                .equalsIgnoreCase(OtherUser.getId())))
                .collect(Collectors.toList());
    }

Làm thế nào về sự khác biệt được thiết lập giữa 2 danh sách?
jean

1
public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
    Set<T> set1, set2;
    if (col1 instanceof Set) {
        set1 = (Set) col1;
    } else {
        set1 = new HashSet<>(col1);
    }

    if (col2 instanceof Set) {
        set2 = (Set) col2;
    } else {
        set2 = new HashSet<>(col2);
    }

    Set<T> intersection = new HashSet<>(Math.min(set1.size(), set2.size()));

    for (T t : set1) {
        if (set2.contains(t)) {
            intersection.add(t);
        }
    }

    return intersection;
}

JDK8 + (Có lẽ là hiệu suất tốt nhất)

public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
    boolean isCol1Larger = col1.size() > col2.size();
    Set<T> largerSet;
    Collection<T> smallerCol;

    if (isCol1Larger) {
        if (col1 instanceof Set) {
            largerSet = (Set<T>) col1;
        } else {
            largerSet = new HashSet<>(col1);
        }
        smallerCol = col2;
    } else {
        if (col2 instanceof Set) {
            largerSet = (Set<T>) col2;
        } else {
            largerSet = new HashSet<>(col2);
        }
        smallerCol = col1;
    }

    return smallerCol.stream()
            .filter(largerSet::contains)
            .collect(Collectors.toSet());
}

Nếu bạn không quan tâm đến hiệu suất và thích mã nhỏ hơn, chỉ cần sử dụng:

col1.stream().filter(col2::contains).collect(Collectors.toList());

0

Giải pháp cuối cùng:

//all sorted items from both
public <T> List<T> getListReunion(List<T> list1, List<T> list2) {
    Set<T> set = new HashSet<T>();
    set.addAll(list1);
    set.addAll(list2);
    return new ArrayList<T>(set);
}

//common items from both
public <T> List<T> getListIntersection(List<T> list1, List<T> list2) {
    list1.retainAll(list2);
    return list1;
}

//common items from list1 not present in list2
public <T> List<T> getListDifference(List<T> list1, List<T> list2) {
    list1.removeAll(list2);
    return list1;
}

0

Đầu tiên, tôi sao chép tất cả các giá trị của mảng vào một mảng sau đó tôi sẽ loại bỏ các giá trị trùng lặp vào mảng. Dòng 12, giải thích nếu cùng một số xảy ra nhiều hơn thời gian thì đặt một số giá trị rác bổ sung vào vị trí "j". Cuối cùng, di chuyển từ đầu-cuối và kiểm tra nếu cùng một giá trị rác xảy ra sau đó loại bỏ.

public class Union {
public static void main(String[] args){

    int arr1[]={1,3,3,2,4,2,3,3,5,2,1,99};
    int arr2[]={1,3,2,1,3,2,4,6,3,4};
    int arr3[]=new int[arr1.length+arr2.length];

    for(int i=0;i<arr1.length;i++)
        arr3[i]=arr1[i];

    for(int i=0;i<arr2.length;i++)
        arr3[arr1.length+i]=arr2[i];
    System.out.println(Arrays.toString(arr3));

    for(int i=0;i<arr3.length;i++)
    {
        for(int j=i+1;j<arr3.length;j++)
        {
            if(arr3[i]==arr3[j])
                arr3[j]=99999999;          //line  12
        }
    }
    for(int i=0;i<arr3.length;i++)
    {
        if(arr3[i]!=99999999)
            System.out.print(arr3[i]+" ");
    }
}   
}

1
Chào mừng bạn đến với Stack Overflow! Xin lưu ý rằng câu hỏi là về ArrayList. Ngoài ra, tôi sợ việc triển khai cụ thể này khiến mọi thứ được mong muốn. Giá trị 99999999, được sử dụng làm trọng tâm, có thể xảy ra trong đầu vào. Sẽ tốt hơn nếu sử dụng một cấu trúc động, như ArrayList, để lưu trữ kết quả của liên minh.
SL Barth - Phục hồi Monica

1
Vui lòng giải thích mã bạn đã trình bày thay vì chỉ một câu trả lời mã.
tmarois

Tôi chỉ đưa ra một đầu mối mà bạn phải đặt bất kỳ giá trị rác nào
Ashutosh

Tôi rất vui khi thấy bạn thêm một lời giải thích. Thật không may, câu trả lời chính nó vẫn là xấu. Không có lý do để sử dụng mảng. Bạn nên sử dụng một cấu trúc động như ArrayList. Nếu (vì một số lý do) bạn phải sử dụng mảng, bạn nên xem xét sử dụng một mảng Integerchứ không phải int. Sau đó, bạn có thể sử dụng nullthay vì "giá trị rác" của bạn. "Giá trị rác" hoặc "giá trị sentinel" thường là một ý tưởng tồi, bởi vì những giá trị này vẫn có thể xảy ra trong đầu vào.
SL Barth - Phục hồi Monica

0

Sau khi thử nghiệm, đây là cách tiếp cận giao lộ tốt nhất của tôi.

Tốc độ nhanh hơn so với Phương pháp tiếp cận Hashset thuần túy. Hashset và HashMap bên dưới có hiệu suất tương tự cho các mảng có hơn 1 triệu bản ghi.

Đối với cách tiếp cận Luồng Java 8, tốc độ khá chậm đối với kích thước mảng lớn hơn 10k.

Hy vọng điều này có thể giúp đỡ.

public static List<String> hashMapIntersection(List<String> target, List<String> support) {
    List<String> r = new ArrayList<String>();
    Map<String, Integer> map = new HashMap<String, Integer>();
    for (String s : support) {
        map.put(s, 0);
    }
    for (String s : target) {
        if (map.containsKey(s)) {
            r.add(s);
        }
    }
    return r;
}
public static List<String> hashSetIntersection(List<String> a, List<String> b) {
    Long start = System.currentTimeMillis();

    List<String> r = new ArrayList<String>();
    Set<String> set = new HashSet<String>(b);

    for (String s : a) {
        if (set.contains(s)) {
            r.add(s);
        }
    }
    print("intersection:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
    return r;
}

public static void union(List<String> a, List<String> b) {
    Long start = System.currentTimeMillis();
    Set<String> r= new HashSet<String>(a);
    r.addAll(b);
    print("union:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
}

0

Phương thức retAll () sử dụng để tìm phần tử chung..ie; giao lộ list1.retain ALL (list2)


-1

Nếu bạn có dữ liệu của mình trong Bộ, bạn có thể sử dụng Setslớp của Guava .


-1

Nếu số trùng khớp với số tôi đang kiểm tra thì nó có xuất hiện lần đầu hay không với sự trợ giúp của "indexOf ()" nếu số đó khớp với lần đầu tiên sau đó in và lưu thành một chuỗi sao cho khi lần sau cùng số đó khớp với nó thì nó sẽ thắng ' t in vì điều kiện "indexOf ()" sẽ sai.

class Intersection
{
public static void main(String[] args)
 {
  String s="";
    int[] array1 = {1, 2, 5, 5, 8, 9, 7,2,3512451,4,4,5 ,10};
    int[] array2 = {1, 0, 6, 15, 6, 5,4, 1,7, 0,5,4,5,2,3,8,5,3512451};


       for (int i = 0; i < array1.length; i++)
       {
           for (int j = 0; j < array2.length; j++)
           {
               char c=(char)(array1[i]);
               if(array1[i] == (array2[j])&&s.indexOf(c)==-1)
               {    
                System.out.println("Common element is : "+(array1[i]));
                s+=c;
                }
           }
       }    
}

}


2
Đừng chỉ đăng mã dưới dạng câu trả lời, hãy giải thích một chút về những gì bạn đang làm
Brandon Zamudio

đó là chương trình đầu tiên của tôi mà tôi đã tải lên
Ashutosh

2
Mặc dù mã này có thể giúp giải quyết vấn đề, nhưng nó không giải thích được tại sao và / hoặc cách nó trả lời câu hỏi. Cung cấp bối cảnh bổ sung này sẽ cải thiện đáng kể giá trị lâu dài của nó. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích, bao gồm những hạn chế và giả định được áp dụng.
Toby Speight
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.