0

Example lets say I have a data class:

data class MyList(
    val username :String,
    val correctQuestions :Int
)

Then an ArrayList like so:

var myList = ArrayList<MyList>()

myList is now populated with many values example:

myList.add(MyList("@Frank68", 54))

A user name might exist multiple times!

Now what is the most efficient way to create a new ArrayList of type of MyList which would show all the correct questions of each username? Of course each username should exist only one time in newly created ArrayList.

P.S.) I have updated my question to look more realistic even though this is a question constructed with minimum input to mind, in order to be easier to reproduce.

  • Show us your expected output when you add multiple MyList instances to the list, with the same name but different correctQuestions. Is the type of MyList.correctQuestions meant of be a List instead of int? – k314159 Mar 12 '21 at 11:52
  • This is an example with minimum input. Each name can exist multiple times, and can have different correct questions. – FrankCorre Mar 12 '21 at 11:56
  • A suggestion: use descriptive names. It's very confusing to call your dataclass "MyList" when it doesn't represent a list at all, but rather a single item. And `correctQuestions` suggests that it is a collection of something rather than a count of something. – Tenfour04 Mar 12 '21 at 14:35
  • @Tenfour04 yes you are right, I should have named it differently... – FrankCorre Mar 12 '21 at 16:00

5 Answers5

1

I'm giving you the straightforward answer, and will ignore the "most efficient" bit for now. When a bottleneck was encountered in this code, feel free to ask a followup question.

Convert to a lookup structure, compute the total sum, then convert to list again:

Map<MyList, Integer> lookup = new HashMap<>();
for (var item : list) {
  lookup.computeIfAbsent(item.name, n -> new MyList(name, 0)).correctQuestions += item.correctQuestions;
}
List<MyList> sums = lookup.values();

You can also use streams and the groupingBy collector:

List<MyList> sums = list.stream()
  .collect(Collectors.groupingBy(
    l -> l.name,
    Collectors.summingInt(l -> l.correctQuestions)))
  .entrySet()
  .stream()
  .map(e -> new MyList(e -> e.getKey(), e -> e.getValue()))
  .collect(Collectors.toList());

Input:

List.of(new MyList("a", 1), new MyList("b", 2), new MyList("a", 3));

Output:

a -> 4
b -> 2
knittl
  • 246,190
  • 53
  • 318
  • 364
  • 1
    The first example could also be `lookup.merge(item.name, item, (v0, v1) -> v0.correctQuestions + v1.correctQuestions);` – Felix Mar 12 '21 at 11:53
  • This is looking pretty neat! Do you recommend any good book more advanced by the way? :) – FrankCorre Mar 12 '21 at 11:54
  • @codeflush.dev only if the map were `Map`. Otherwise, the lambda would need to return a new `MyList` instance. – knittl Mar 12 '21 at 12:14
1

Here's a two-line Kotlin solution. Group by the names to get a Map of the names to lists of the associated data. Then map these entries and sum their integer properties.

myList.groupBy(MyList::username)
    .map { (name, values) -> MyList(name, values.sumBy(MyList::correctQuestions)) }

If you really need it in a MutableList (you specified ArrayList), you can do this:

myList.groupBy(MyList::username)
    .asSequence()
    .map { (name, values) -> MyList(name, values.sumBy(MyList::correctQuestions)) }
    .toMutableList()
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • This is looking the best approach to Kotlin style. Do you have any good book to recommend? Or you can come up with solutions by experience? – FrankCorre Mar 12 '21 at 15:54
  • I learned Kotlin by reading the official site's documentation. I was already fluent in Java. I've spent some time exploring the available functions for working with collections by looking at the autocomplete options in the IDE. I haven't ready any books, but I've read Medium articles by Roman Elizarov, who is now the design lead of Kotlin. That helped flesh out the considerations that went into the intended style for writing Kotlin, which helps with learning to write it cleanly. Be careful about third-party Medium articles because there are a lot written by newbies. – Tenfour04 Mar 12 '21 at 16:18
0

You can use Stream with the groupingBy collector for this:

Map<String, List<MyList>> map = myList.stream()
  .collect(Collectors.groupingBy(MyList::getName));

Now you have a Map, that has name as key and all MyList objects with that specific name as value.

Benjamin M
  • 23,599
  • 32
  • 121
  • 201
0

Kotlin one-liner:

myList.groupingBy { it.name }
    .fold(0, { acc, it ->  acc + it.correctQuestions})
    .map { e -> MyList(e.key, e.value) }
Flame239
  • 1,274
  • 1
  • 8
  • 20
0

Try this.

List<MyList> mylist = List.of(
    new MyList("A", 2),
    new MyList("B", 3),
    new MyList("B", 1));
List<MyList> result = mylist.stream()
    .collect(Collectors.groupingBy(e -> e.name, Collectors.summingInt(e -> e.correctQuestions)))
    .entrySet().stream()
    .map(e -> new MyList(e.getKey(), e.getValue()))
    .collect(Collectors.toList());
System.out.println(result);

output:

[MyList(A, 2), MyList(B, 4)]