редуктор hadoop не рассматривает два равных пользовательских записываемых объекта как равные

#hadoop #mapreduce #writable

#hadoop #mapreduce создать #доступный для записи

Вопрос:

Я пытаюсь написать программу сокращения карты, которая проверяет наличие общих друзей. Я использую пользовательский объект для записи (FriendPair) в качестве ключа.

Учитывая следующие входные данные

 Tom Jerry,John
John Jerry,Sarah,Tom
  

Он должен выводить Джерри как общего друга для Тома и Джона

 [John,Tom]    Jerry
[John,Sarah]    
[John,Jerry]
[Tom,Jerry] 
  

Вместо этого map reduce выводит следующее

 [John,Tom]  
[John,Sarah]    
[John,Jerry]    
[Tom,John]  
[Tom,Jerry]
  

Ключи [John, Tom] и [Tom,John] считаются неравными.

Ниже приведен код

Настраиваемый записываемый

     public class FriendPair implements WritableComparable<FriendPair> {
        
        Text friend1;
        Text friend2;
        
        public FriendPair() {
            this.friend1 = new Text("");
            this.friend2 = new Text("");
        }
        
        public FriendPair(Text friend1, Text friend2) {
            this.friend1 = friend1;
            this.friend2 = friend2;
        }
        
        public Text getFriend1() {
            return friend1;
        }
        public void setFriend1(Text friend1) {
            this.friend1 = friend1;
        }
        public Text getFriend2() {
            return friend2;
        }
        public void setFriend2(Text friend2) {
            this.friend2 = friend2;
        }
    
        @Override
        public void write(DataOutput out) throws IOException {
            friend1.write(out);
            friend2.write(out);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            friend1.readFields(in);
            friend2.readFields(in);
        }
    
        @Override
        public int compareTo(FriendPair pair2) {
            return ((friend1.compareTo(pair2.getFriend2()) == 0 amp;amp; friend2.compareTo(pair2.getFriend1()) == 0)
                   || (friend1.compareTo(pair2.getFriend1()) == 0 amp;amp; friend2.compareTo(pair2.getFriend2()) == 0)) ? 0 : -1;
        }
    
        @Override
        public boolean equals(Object o) {
            FriendPair pair2 = (FriendPair) o;
            return (friend1.equals(pair2.getFriend2()) amp;amp; friend2.equals(pair2.getFriend1()) 
                    || friend1.equals(pair2.getFriend1()) amp;amp; friend2.equals(pair2.getFriend2()));
        }
        
        @Override
        public String toString() {
            return "["   friend1   ","   friend2   "]";
        }
        
        @Override
        public int hashCode() {
            return friend1.hashCode()   friend2.hashCode();
        }
    
    }
  

Mapper

 public class MutualFriendsMapper extends Mapper<LongWritable, Text, FriendPair, Text> {

    @Override
    public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        String line = value.toString();
        String[] items = line.split("t");

        String name = items[0];
        String friendsList = items[1];
        String[] friends = friendsList.split(",");
        for (String friend : friends) {
            FriendPair fp = new FriendPair(new Text(name), new Text(friend));
            FriendPair fp2 = new FriendPair(new Text(friend), new Text(name));
            context.write(fp, new Text(friendsList));
        }
    }
}
  

Редуктор

 public class MutualFriendsReducer extends Reducer<FriendPair, Text, FriendPair, FriendArray> {

    @Override
    public void reduce(FriendPair key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        
        List<String> allFriends = new ArrayList<String>();
        for(Text value : values) {
            String[] valueArray = value.toString().split(",");
            allFriends.addAll(Arrays.asList(valueArray));
        }
        List<Text> commonFriends = new ArrayList<Text>();
        Set<String> uniqueFriendSet = new HashSet<String>(allFriends);
        for(String friend : uniqueFriendSet) {
            int frequency = Collections.frequency(allFriends, friend);
            if(frequency > 1) {
                commonFriends.add(new Text(friend));
            }
        }
        
        context.write(key, new FriendArray(Text.class, commonFriends.toArray(new Text[commonFriends.size()])));
    }
}
  

FriendArray (Вывод)

 public class FriendArray extends ArrayWritable {

    public FriendArray(Class<? extends Writable> valueClass, Writable[] values) {
        super(valueClass, values);
    }
    
    public FriendArray(Class<? extends Writable> valueClass) {
        super(valueClass);
    }
    
    public FriendArray() {
        super(Text.class);
    }

    @Override
    public Text[] get() {
        return (Text[]) super.get();
    }
    
    @Override
    public void write(DataOutput data) throws IOException {
        for(Text t : get()) {
            t.write(data);
        }
    }
    
    @Override
    public String toString() {
        Text[] friendArray = Arrays.copyOf(get(), get().length, Text[].class);
        String print="";
        
        for(Text f : friendArray) 
            print =f ",";
        
        return print;
    }
}
  

Любая помощь будет с благодарностью принята.

Ответ №1:

На этапе «сортировки» Hadoop не работает с объектами Java, а только с их байтовым представлением (вывод FriendPair.write() метода), поэтому он не может вызвать FriendPair.equals() . Итак, чтобы заставить Hadoop понять, что ключи [John, Tom] и [Tom,John] равны, вы должны убедиться, что их write выходные данные идентичны. Один из способов добиться этого — обеспечить соблюдение порядка друзей в паре, например, отсортировать их по алфавиту (тогда обе пары будут выглядеть [John, Tom] ).

Комментарии:

1. Это сработало, но я до сих пор не понимаю, почему метод compareTo не возвращал 0, когда пары [Tom, John] и [John, Tom] равны. Разве они не должны быть отправлены в один и тот же редуктор?

2. Что ж, вы частично правы. Они отправляются в один и тот же экземпляр редуктора, потому что у них одинаковый хэш-код. Но редуктор видит их как разные ключи, потому что их байтовое представление отличается.