0

I have a list of words: List<Word> words

The bean Word is:

public class Word {
    private String name;
    private String meaning;
...
}

I would like to shuffle the elements to have the following:

From:

{ name: "day", meaning: "giorno"},
{ name: "year", meaning: "anno"},
{ name: "hour", meaning: "ora"}

To:

{ name: "day", meaning: "ora"},
{ name: "year", meaning: "giorno"},
{ name: "hour", meaning: "anno"}

I've tried this but I think there is a more elegant solution:

private List<Word> shuffle(List<Word> words) {
        List<String> names = words.stream()
                                .map(word -> word.getName())
                                .collect(Collectors.toList());
        List<String> meanings = words.stream()
                                .map(word -> word.getMeaning())
                                .collect(Collectors.toList());
        Collections.shuffle(meanings);

        words = new ArrayList<Word>();
        for(int i = 0; i<names.size(); i++) {
            words.add(new Word(names.get(i), meanings.get(i)));
        }

        return words;
    }
Holger
  • 285,553
  • 42
  • 434
  • 765
NikNik
  • 2,191
  • 2
  • 15
  • 34

2 Answers2

2

You can do in-place shuffling. For that, you don't need to create extra Word objects.

private void shuffle(List<Word> words) {
    List<String> meanings = words.stream()
            .map(Word::getMeaning)
            .collect(Collectors.toList());
    Collections.shuffle(meanings);

    for(int i = 0; i < words.size(); i++) {
        words.get(i).setMeaning(meanings.get(i));
    }
}
Mushif Ali Nawaz
  • 3,707
  • 3
  • 18
  • 31
  • 1
    Clever solution! – NikNik Sep 27 '19 at 09:52
  • 1
    You could take this a step further by creating a wrapper list, like `class MeaningList extends AbstractList implements RandomAccess { public String get(int index) { return words.get(index).getMeaning(); } public String set(int index, String newMeaning) { Word w = words.get(index); String old = w.getMeaning(); w.setMeaning(newMeaning); return old; } public int size() { return words.size(); } }` Then, you only need to pass it to the suffle method like `Collections.shuffle(new MeaningList());` and you’re done. – Holger Sep 27 '19 at 12:40
  • @Holger you're right. We can do that. – Mushif Ali Nawaz Sep 27 '19 at 12:48
  • @NikNik If this answer helped you then consider accepting it. – Mushif Ali Nawaz Sep 27 '19 at 12:52
  • 1
    @MushifAliNawaz It helped me for sure and that's why I've upvoted. Let's wait a little bit to see if there are other ideas ;) (However, the people marked the question as duplicate (even if it's not) so now it's not possible to submit new asnwers anymore). – NikNik Sep 27 '19 at 14:04
  • @NikNik yep that's true. Now new answers can't be submitted. – Mushif Ali Nawaz Sep 27 '19 at 17:03
1

Without zip and with only in-place shuffle, your solution look pretty elegant. I would just refactor it a little bit:

ArrayList<Word> wordsCopy = new ArrayList<>(this.words);
Collections.shuffle(wordsCopy);
List<Word> result = IntStream.range(0, words.size())
    .mapToObj(i -> new Word(words.get(i).name, wordsCopy.get(i).meaning))
    .collect(Collectors.toList());
Nikita
  • 4,435
  • 3
  • 24
  • 44
  • This is not an in-place solution. You are creating a new `List`. – Mushif Ali Nawaz Sep 27 '19 at 09:56
  • 2
    Yes, this is exactly what I meant. If there is shuffle which doesn't change the original array, then it would be one line less. And immutable structures are always better in terms of readability. – Nikita Sep 27 '19 at 09:59