HashMap wrong values for keys - java

I am kinda new to Java, and I am trying to write a function that maps all element indexes from an ArrayList into a HashMap, so I can easily see the indexes of duplicate elements.
The code below works , but when I try to print the values using the second for, it shows completely different results!
Example:
60 [40, 64]
What the 2nd for shows
60 [64]
more numbers
60 [64]
HashMap<Integer,ArrayList<Integer>> table= new HashMap<Integer,ArrayList<Integer>>();
//checking all items in an ArrayList a
//and putting their index in a hashTable
for(int i=0; i<a.size(); i++){
ArrayList<Integer> indexes = new ArrayList<Integer>();
indexes.add(i);
for(int j=i+1; j<a.size(); j++){
//are the items equal?
if(a.get(j).equals(a.get(i))){
indexes.add(j);
}
}
//put in the HashMap
table.put(a.get(i), indexes);
System.out.println(a.get(i) + " " +table.get((a.get(i))));
}
//shows completely different results!
for(int ix=1;ix<table.size();ix++)
System.out.println(a.get(ix) + " " +table.get(a.get(ix)));

Try this:
public static void main(String[] args) {
List<Integer> input = Arrays.asList(60, 60, 1, 4, 5, 7, 60);
Map<Integer, List<Integer>> result = new HashMap<>();
for (int n = 0; n < input.size(); ++n) {
List<Integer> list = result.get(input.get(n));
if (list != null) {
list.add(n);
} else {
list = new ArrayList<>();
list.add(n);
result.put(input.get(n), list);
}
}
System.out.println(result); // prints {1=[2], 4=[3], 5=[4], 7=[5], 60=[0, 1, 6]}
}

But I don't get it...What did I do wrong? As far as I see, my code is really inefficient compared to yours, but shouldn't it do the same thing?
Well no. In addition to being inefficient, your version has a significant bug.
Lets take your example input {60, 60, 1, 4, 5, 6, 7, 60}.
First iteration the loop, you build a list containing {0, 1, 7} and put it into the map so that we have map containing{ 60 -> {0, 1, 7} }`
Second iteration of the loop, we build a list containing {1, 7} and put it into the map. But this of course replaces the original (correct) list for 60 ... and we end up with { 60 -> {1, 7} }
And so on. In short, your version will end up producing a map that maps from the values to a list containing just the last index of that value.

Related

Limit the number of occurrences for each Array's element to 2

I was attempting to solve this question in a Test.
It asked to make sure that an array could hold maximum of 2 repeated elements, if any element is occurring more than twice, that should be removed.
Given - [2, 2, 2, 3, 4, 4, 5]
Expected - [2, 2, 3, 4, 4, 5]
So, I tried with this approach as follows -
// 1. HashMap to count frequency of each element Key - Element, Value - Frequency
Map<Integer, Integer> data = new HashMap<Integer, Integer>();
for(int index = 0; index < n; index++) {
if(data.containsKey(arr[index])) {
data.put(arr[index], data.get(arr[index]) + 1);
}
else {
data.put(arr[index], 1);
}
}
// 2. Find most frequent element
int max_count = 0, need_to_remove = 0;
for(Entry<Integer, Integer> value : data.entrySet()) {
if(max_count < value.getValue()) {
need_to_remove = value.getKey();
max_count = value.getValue();
}
}
System.out.println("Max Count: " + max_count + " , Remove one occurrence of: " + need_to_remove);
//Output - Max Count: 3 , Remove one occurrence of : 2
Since, Value - 2 with Frequency - 3, need to remove a '2' from the map
// 3. Remove 'need_to_remove' value from map
map.remove(need_to_remove);
But doing so, it removes all occurrences of Element - 2 from the map.
{3=1, 4=2, 5=1}
I'm not really sure, about what needs to be done from here.
In case if the order of the elements should be preserved, then it would be correct to use List.remove() because this method removes the very first occurrence of the given element. Also, removal of elements from a List has the worst case time complexity O(n), which leads to overall quadratic timecomplexity O(n^2). We can do better.
Instead, you can build and a new List while iterating over the given array. And simultaneously, you need to track the occurrences of the previously encountered element via a HashMap. If the number of occurrences of a current element hasn't exceeded the limit, it should be added to the resulting list, otherwise ignored.
Note: that this solution would run in O(n), since we're iterating the list twice: to generate the list. And then to turn it into an array, all the action needs to be performed during iterations run in O(1).
That's how it might be implemented:
public static int[] removeOccurrencesAboveLimit(int[] arr, int limit) {
Map<Integer, Integer> occurrences = new HashMap<>();
List<Integer> result = new ArrayList<>();
for (int next : arr) {
int freq = occurrences.merge(next, 1, Integer::sum); // returns a new Value (i.e. updated number of occurrences of the current array element)
if (freq <= limit) result.add(next);
}
return toArray(result);
}
public static int[] toArray(List<Integer> list) {
return list.stream().mapToInt(i -> i).toArray();
}
main()
public static void main(String[] args) {
int[] arr = {3, 1, 2, 1, 3, 3, 4, 4, 5, 1, 3, 5};
System.out.println(Arrays.toString(removeOccurrencesAboveLimit(arr, 2)));
}
Output:
[3, 1, 2, 1, 3, 4, 4, 5, 5]
Create a new, empty list (lets call it dest) and a Map of value and frequency (lets call this one freq).
Populate the dest list by iterating over the data list.
As you iterate over the list, update freq and increment the value of the key that corresponds to the value in data.
If freq.get(value) is greater than 2, then don't copy it to dest.
Once you have completely iterated over the data list, dest should contain the values with no more than 2 instances of a given value.
Additionally, freq should contain the total count of the number of times that the key occurred in data.
This makes it so that you don't need to mutate the data list in place - you're just copying it and only copying at most 2 of a given value.
If the input is a list, the frequency map should be used just to track the number of occurrences using method Map::merge. The repeated elements can be removed from the list if an iterator is used to traverse the list, and Iterator has method remove():
List<Integer> list = new ArrayList<>(Arrays.asList(2, 2, 2, 3, 4, 4, 5));
Map<Integer, Integer> freq = new HashMap<>();
for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
if (freq.merge(it.next(), 1, Integer::sum) > 2) {
it.remove();
}
}
System.out.println(list); // -> [2, 2, 3, 4, 4, 5]
If the input is provided as an array, then a new resized array should be created and appropriate elements need to be copied.
int[] arr = {2, 2, 2, 3, 3, 4, 4, 2, 3, 5, 6, 4, 2};
Map<Integer, Integer> freq2 = new HashMap<>();
int i = 0;
for (int n : arr) {
if (freq.merge(n, 1, Integer::sum) <= 2) {
arr[i++] = n;
}
}
arr = Arrays.copyOf(arr, i);
System.out.println(Arrays.toString(arr)); // -> [2, 2, 3, 3, 4, 4, 5, 6]
The java.util.HashMap.remove() is used to remove the mapping of any particular key from the map. It basically removes the values for any particular key in the Map.
https://www.geeksforgeeks.org/hashmap-remove-method-in-java/
assuming that map is your data hashMap. and you want to reduce the count of value in your key, here is 2. so you should set the value again by getting the value and reducing it. like this
map.put(key, map.get(key) - 1);
however you should remove one key in your arr. instead of your map.
arr.remove(need_to_remove);
By the way you can solve your problem better.
List<Integer> list = new ArrayList<>(Arrays.asList(2, 2, 2, 3, 4, 4, 5));
Map<Integer,Long> resultMap= list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
resultMap.entrySet()
.stream()
.forEach(entry -> {
if(entry.getValue() > 2){
list.remove(entry.getKey());
}
});
System.out.println(list.toString());

How to count duplicates and change List values in ONE for-loop?

I'm trying to count all the duplicate numbers in the list in my code. In addition, the list should be compared with a set; if there is a match, the respective value should be replaced by another.
I managed to program correctly, but can you get it into a single for loop? If I get it in the same loop, the map always gets the wrong value because the values ​​are always overwritten.
Here is my code:
public static Map<Integer, Integer> replace(List<Integer> inputList, Set<Integer> numberSet, Integer newNumber) {
Map<Integer, Integer> map = new HashMap<>();
for (int i =0; i < inputList.size(); i++){
if(numberSet.contains(inputList.get(i))){
map.put(inputList.get(i), Collections.frequency(inputList, inputList.get(i)));
}
}
for (int i =0; i < inputList.size(); i++){
if(numberSet.contains(inputList.get(i))){
inputList.set(i, newNumber);
}
}
return map;
}
Using Collections.frequency while altering the list in the same loop is throwing off your numbers. Here is an approach that handles counting frequency with each iteration while at the same time updating the list values equal to newNumber.
If the code you displayed is giving you the output you desire, then here I have written the following single loop solution that does the same.
public static Map<Integer, Integer> replace(List<Integer> inputList, Set<Integer> numberSet, Integer newNumber) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < inputList.size(); i++){
Integer value = inputList.get(i);
// value already in map, count 1 more
if(numberSet.contains(value) && map.containsKey(value)) {
map.put(value, map.get(value) + 1);
// value not in map yet, count 1
} else {
map.put(value, 1);
}
// always set all list items to newNumber
inputList.set(i, newNumber);
}
return map;
}
Actually, Collections.frequency is not needed here at all because it will recalculate the frequency for each element of inputList from the start.
The frequency of elements is conveniently calculated using Map::merge method, so that each matching value is counted and replaced at the same iteration:
public static Map<Integer, Integer> replace(List<Integer> inputList, Set<Integer> numberSet, Integer newNumber) {
Map<Integer, Integer> map = new HashMap<>();
for (int i =0; i < inputList.size(); i++) {
Integer v = inputList.get(i);
if(numberSet.contains(v)) {
map.merge(v, 1, Integer::sum);
inputList.set(i, newNumber);
}
}
return map;
}
Test:
List<Integer> ints = Arrays.asList(1, 2, 3, 2, 3, 5, 3, 1, 4, 2);
System.out.println(ints);
System.out.println(replace(ints, Set.of(3, 4, 5), 9));
System.out.println(ints);
Output:
frequencies of 3, 4, 5 are calculated correctly
all occurrences of 3, 4, 5 set to 9
[1, 2, 3, 2, 3, 5, 3, 1, 4, 2]
{3=3, 4=1, 5=1}
[1, 2, 9, 2, 9, 9, 9, 1, 9, 2]

How to copy a Set<Set<Integer>> into a List<Integer[]> in Java?

I want to copy a Set<Set<Integer>> of array to a List of Integer[]. I want to do that because I want to change the first element of every array when it is 0 , and put it at the end.
I have manage to do that but in a List<Set> , and when looping throw , I can't make changes because the array are still a Set.
No other questions make this clear, instead they explained when we have only Set<SomeWrapperClass>
Maybe some other solutions? Remark: I cannot change the Set<Set<Integer>>
My Set:
Set<Set<Integer>> indexs = new HashSet<>();
I can convert to a List only in this way:
List<Set<Integer>> arrangedIndexOfCourses = new ArrayList<>();
static Set<Set<Integer>> coursesIndex = Sets.powerSet(Sets.newHashSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
static List<Set<Integer>> arrangedIndexOfCourses = new ArrayList<>();
The following method return all the occurence of unique numbers made from 0123456789 ( Guava library )
private void indexSet(Set<Set<Integer>> coursesIndex) {
Set<Set<Integer>> indexs = new HashSet<>();
for (Set<Integer> token : coursesIndex) {
indexs.add(token);
arrangedIndexOfCourses.add(token);
count++;
}
}
And the code where I tried to change the first 0 of the arrays and put at last:
for (Set<Integer> i : arrangedIndexOfCourses) {
if (i.contains(0)) {
Collections.rotate(arrangedIndexOfCourses, -1);
}
}
It appears, the main trick in this task is how to implement rotation of the first element if it is 0.
As mentioned in the comments, not all types of sets maintain order and therefore have a "first" element. But upon converting a set to stream, even for a HashSet there's a first element in the stream unless it's empty.
Therefore, the set of integer sets may be converted to a list of integer arrays as follows:
Take the first element of the stream using limit or findFirst
Compare to 0, if needed put it to the end
Else keep the stream of the set as is
Set<Set<Integer>> data = Set.of(
Collections.emptySet(),
new LinkedHashSet<>(Arrays.asList(0, 1, 2, 3)),
new LinkedHashSet<>(Arrays.asList(4, 5, 6)),
new LinkedHashSet<>(Arrays.asList(10, 0, 100, 1000)),
new TreeSet<>(Arrays.asList(25, 0, 1, 16, 4, 9)),
new HashSet<>(Arrays.asList(0, 5, 50))
);
List<Integer[]> rotatedZeros = data
.stream()
.map(s ->
(s.stream().findFirst().orElse(-1) == 0 // or limit(1).findAny()
? Stream.concat(s.stream().skip(1), Stream.of(0))
: s.stream())
.toArray(Integer[]::new)
)
.collect(Collectors.toList());
rotatedZeros.stream().map(Arrays::toString).forEach(System.out::println);
Output:
[]
[4, 5, 6]
[1, 2, 3, 0]
[10, 0, 100, 1000]
[1, 4, 9, 16, 25, 0]
[50, 5, 0]
the 2-step solution with a lambda w/o external library
(1) convert the Set<Set<Integer>> to a List<Integer[]>
List<Integer[]> arrangedIndexOfCourses = coursesIndex.stream()
.map(s -> s.toArray(Integer[]::new)).collect(toList());
(2) iterate over the arrays and change the zero-arrays in the traditional way
for (Integer[] indices : arrangedIndexOfCourses) {
if (indices.length > 0 && indices[0] == 0) {
for (int i = 0; i < indices.length - 1; i++)
indices[i] = indices[i + 1];
indices[indices.length - 1] = 0;
}
}
a lambda based on Alex's mind blowing solution
moving the zero-rotating-stuff upstream makes the lambda less trickier and You can work with collections instead of arrays
List<Integer[]> arrangedIndexOfCourses = coursesIndex.stream()
.map(s -> {
if (s.stream().findFirst().orElse(-1) == 0) {
List<Integer> l = new ArrayList<>();
l.addAll(s);
l.remove(0);
l.add(0);
return l.toArray(Integer[]::new);
} else
return s.toArray(Integer[]::new);
}).collect(toList());

Evenly distribute lists into sublists in Java

I want to evenly distribute a list into a given number of sublists.
For example, I have a list with elements 1 to 10 and I want 3 lists. These should look like:
SL1 -> {1, 2, 3, 4}
SL2 -> {5, 6, 7}
SL3 -> {8, 9, 10}
Important: What each list contains is not relevant, i.e. SL1 could have {1, 5, 7, 10}. The most important thing is that there are 2 lists with size 3 and 1 list with size 4.
I have tried several things, including the Iterables.partition but that won't help.
The only thing I've come up with that works is:
public Iterable<List<Integer>> distributeEvenlyQueryListIntoLists(final LinkedList<Integer> bigList, final Integer numberOfSublists) {
List<List<Integer>> result = new ArrayList<>();
// Creates as many lists as needed
for (int i = 0; i < numberOfSublists; i++) {
result.add(new ArrayList<>());
}
while (bigList.iterator().hasNext()) {
for (int i = 0; i < numberOfSublists; i++) {
if (!bigList.iterator().hasNext()) {
break;
}
result.get(i).add(bigList.poll());
}
}
return result;
}
The passed bigList does not have to be a LinkedList, it can be any Iterable.
I especially hate the first loop where I create the sublists.
Thanks!
Just distribute them in a round-robin pattern:
public <T> List<List<T>> partition(Iterable<T> iterable, int partitions){
List<List<T>> result = new ArrayList<>(partitions);
for(int i = 0; i < partitions; i++)
result.add(new ArrayList<>());
Iterator<T> iterator = iterable.iterator()
for(int i = 0; iterator.hasNext(); i++)
result.get(i % partitions).add(iterator.next());
return result;
}
A sample run with this code:
List<String> l = Stream.iterate(0, i->i + 1).limit(25).map(i->Integer.toString(i)).collect(Collectors.toList());
System.out.println(partition(l, 4).toString());
Produces
[[0, 4, 8, 12, 16, 20, 24], [1, 5, 9, 13, 17, 21], [2, 6, 10, 14, 18, 22], [3, 7, 11, 15, 19, 23]]
The basic idea is to add a single element to each list in the result set roundwise. This way it's guaranteed that the difference in the number of elements between two lists never exceeds 1.
As an alternative you could use guavas implementation of Iterables.partition, which takes a slightly different approach.
If you hate creating sublists, that implies you're looking for a fast solution. If you have the original List, and you plan on not altering the original List, consider List.subList().
int subSize = bigList.length() / numSubs;
int numBigSubs = 0; // # of subs that need to be one bigger
if (bigList.length() % numSubs > 0) {
subSize++;
numBigSubs = bigList.length() % numSubs;
}
int from = 0;
int to = subSize;
List<List<Integer>> subList = new ArrayList<List<Integer>>(numSubs);
for (int i = 0; i < numSubs; i++) {
List<Integer> newSub = bigList.subList(from, to);
subList.add (newSub);
from = to;
to += subSize;
if (i >= numBigSubs && numBigSubs > 0) to--;
}
Note: I wrote this without testing - if it fails, I apologize, and hope someone will edit it to work.
Again, the big upside to this is that it should be wicked fast - all the sublists are simply views into the larger one. The downside is that if you change the list, all bets are off.
You can use org.apache.commons.collections4.ListUtils to create equal size sublists.
List<String> bigList = ...
int batchSize = 1000;
List<List<String>> smallerLists = ListUtils.partition(bigList, batchSize);

Java program to find 'Lucky' numbers from 0 to n using Lists

I have to write a program that finds all the lucky numbers from 0 to any number n.
Here's what a lucky number is:
Consider the sequence of natural numbers.
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ………………………………….
Removing every second number produces the sequence
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 ………………………….
Removing every third number produces the sequence
1, 3, 7, 9, 13, 15, 19, 21, 25 ………………………….
This process continues indefinitely by removing the fourth, fifth…and so on, till after a fixed number of steps, certain natural numbers remain indefinitely. These are known as Lucky Numbers.
I decided to try using ArrayList for this. But I can't seem to figure this small bit out. I've been trying for days now.
Here's the code:
import java.util.*;
class luckyy
{
public static void main(String args[])
{
Scanner scan = new Scanner(System.in);
System.out.println("Enter n: ");
int i, n, index;
n = scan.nextInt();
ArrayList <Integer> ele = new ArrayList <Integer> (n);
//storing in a list
for(i = 1;i<=n;i++)
{
ele.add(i);
}
System.out.println(ele);
int count = 2;
index = 1;
System.out.println("Size: "+ele.size());
while(count<ele.size())
{
for(i = 0;i<ele.size();i++)
{
int chk = ele.get(i);
if(chk%count == 0)
{
ele.remove(i);
}
}
count++;
}
System.out.print(ele);
}
}
This gives the output:
[1, 5, 7]
When the desired output is:
[1, 3, 7]
So, sorry if this code is so bad that it's offensive haha...
But I would really appreciate any help. I am just starting out as a programmer, and have a lot to learn, and would love any advice. Thanks to anyone who tries to help me!
First of all it seems to me that your assumption of the expected output is not correct. According to the task you described, the out should be something like this:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25] // every 2nd number removed
[1, 3, 7, 9, 13, 15, 19, 21, 25] // every 3rd number removed
[1, 3, 7, 13, 15, 19, 25] // every 4th number removed
[1, 3, 7, 13, 19, 25] // every 5th number removed
[1, 3, 7, 13, 19] // 6th number removed = final output
Beside that, I see two mistakes.
You want to remove "every n-th number", so you don't want to test the values, but their position/index in the list.
Whenever you remove an element from an ArrayList, the index of the following elements and the size of the list is reduced by 1. Means if you start with removing the "2", the next number to remove will be the "5", not the "4" as desired (assuming you are testing the index, not the value). One solution for that would be to test the indexes starting at the end of the list. In that case it wouldn't matter that higher indexes change after removing elements, because you already passed them.
Edit
Answer edited in regard to request of #Kedar Mhaswade to provide some code in order to test how removing elements from the end of the list perfoms.
This is my first approach:
List<Integer> removeBackward(List<Integer> numbers) {
int count, sPos;
count = 2;
while(count<=numbers.size())
{
for(sPos = numbers.size(); sPos >= numbers.size()-count; sPos--)
{
if(0 == sPos%count) {
break;
}
}
for(int pos = sPos; pos > 0; pos=pos-count)
{
numbers.remove(pos-1);
}
count++;
}
return numbers;
}
I did some tests (see below) with the result that it performs quite well for small sets of numbers (< 12000). On larger sets the second approach of #Kedar Mhaswade (maintaining an extra list for the elements to retain) outperforms this approach.
Therefore I tried a second approach:
The idea was, that there is no need to maintain a second list for retained elements when at first step half of the elements will be removed and the number of elements to retain decreases step by step.
So I simply moved the elements to retain to the end of the same list and maintain additional pointers in order to know the range of the retained elements. At the end of the process the final result only needs to be extracted from the end of the list.
List<Integer> retainedAtEnd(List<Integer> numbers) {
int removeX, baseQty, retainedQty, basePos, retainedPos;
removeX = 1;
baseQty = numbers.size();
while(removeX <= baseQty)
{
removeX++;
basePos = numbers.size();
retainedPos = basePos;
retainedQty = 0;
for(int checkPos = baseQty; checkPos >= 1; checkPos--)
{
if(0 != checkPos%removeX)
{
basePos = numbers.size()-baseQty+checkPos;
numbers.set(retainedPos-1, numbers.get(basePos-1));
retainedPos--;
retainedQty++;
}
}
baseQty = retainedQty;
}
return numbers.subList(numbers.size()-baseQty, numbers.size());
// return new ArrayList(numbers.subList(numbers.size()-baseQty, numbers.size()));
}
According to my test unforunately this approach doesn't perform to good on small sets (<12000). It can not compete with the first or #Kedar Mhaswade's second approach, but on larger sets, it outperforms both.
Here is how I tested:
public void test() {
int n = 1000;
long start;
System.out.println("Testing with " + n + " numbers ...");
System.out.println("Test removing elements backwards:");
List<Integer> numbers1 = Stream.iterate(1, k -> k + 1).limit(n).collect(Collectors.toList());
start = System.nanoTime();
List<Integer> out1 = this.removeBackward(numbers1);
System.out.println("Time taken:" + (System.nanoTime() - start));
// System.out.println(out1);
System.out.println("Test maintaining extra list for retained elements:");
List<Integer> numbers2 = Stream.iterate(1, k -> k + 1).limit(n).collect(Collectors.toList());
start = System.nanoTime();
List<Integer> out2 = this.extraRetainedList(numbers2);
System.out.println("Time taken:" + (System.nanoTime() - start));
// System.out.println(out2);
System.out.println("Test collecting retained elements at end of list:");
List<Integer> numbers3 = Stream.iterate(1, k -> k + 1).limit(n).collect(Collectors.toList());
start = System.nanoTime();
List<Integer> out3 = this.retainedAtEnd(numbers3);
System.out.println("Time taken:" + (System.nanoTime() - start));
// System.out.println(out3);
System.out.println("Test maintaining extra list for elements to remove:");
List<Integer> numbers4 = Stream.iterate(1, k -> k + 1).limit(n).collect(Collectors.toList());
start = System.nanoTime();
List<Integer> out4 = this.extraDeletedList(numbers4);
System.out.println("Time taken:" + (System.nanoTime() - start));
// System.out.println(out4);
}
This is a tricky problem! I went about it in a slightly different fashion and used another list to store the deleted elements. This is required because of the data structure that I chose. Since I wanted to use integers only and I was using an ArrayList, every time I remove an element, the list gets immediately adjusted. What we really need to do is mark the element for deletion. There are more than one way to do this, but I chose to maintain another list of deleted elements (since all the elements are unique, it is fine to use this idea).
Here is my first attempt then:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** <p>
* Find the lucky numbers amongst natural numbers from 1 to n.
* Here's how you find Lucky numbers.
* </p>
* Created by kmhaswade on 2/27/16.
*/
public class Lucky {
public static void main(String[] args) {
printLucky1(Integer.valueOf(args[0]));
}
private static void printLucky1(int n) {
List<Integer> numbers = Stream.iterate(1, k -> k + 1).limit(n).collect(Collectors.toList());
System.out.println(numbers);
int delIndex = 1; // index of the element to be removed, we start with 2nd element
while (delIndex < numbers.size()) {
List<Integer> deleted = new ArrayList<>();
for (int i = delIndex; i < numbers.size(); i += (delIndex + 1)) {
deleted.add(numbers.get(i));
}
numbers.removeAll(deleted); // expensive operation!
System.out.println(numbers);
delIndex += 1;
}
System.out.println("number of lucky numbers:" + numbers.size());
System.out.println(numbers);
}
}
This works! But for really long lists, this is very slow, because of the expensive operation: numbers.removeAll(deleted) -- we are removing bunch of elements from an ArrayList that has to move all affected elements on every deletion!
For instance, with the first 100_000 natural numbers, it takes about 10 seconds on my computer. Clearly, I looked for an alternative. What if we devise another list and collect the elements that we want to retain, and then in the next iteration, this list of retained elements becomes our list to operate on? It looked like that will work better because there is no deletion of elements involved. You will still need to have another ArrayList to collect the elements. In analysis terms, this is an O(n) additional space (or c.n where c ~ 0.5).
Here's my second attempt then:
private static void printLucky2(int n) {
List<Integer> numbers = Stream.iterate(1, k -> k + 1).limit(n).collect(Collectors.toList());
System.out.println(numbers);
int delIndex = 1; // index of the element to be removed, we start with 2nd element
while (delIndex < numbers.size()) {
List<Integer> retained = new ArrayList<>();
for (int i = 0; i < numbers.size(); i += 1)
if ((i+1) % (delIndex + 1) != 0)
retained.add(numbers.get(i));
numbers = retained;
System.out.println(numbers);
delIndex += 1;
}
System.out.println("number of lucky numbers:" + numbers.size());
System.out.println(numbers);
}
There may be more improvements possible because for really large inputs the time taken by this algorithm may still be unacceptable (will work on that improvement). But I already see two orders of magnitude improvement!
Here's the complete code. I made sure that both the methods return lists that are same (list1.equals(list2) returns true) and here is the output on my computer (with first 100_000 numbers):
[1, 3, 7, ...]
number of lucky numbers: 357
time taken: 6297
number of lucky numbers: 357
[1, 3, ...]
time taken: 57
for anyone still interested, I found another way to do this.
It isn't revolutionary or anything, and it uses a lot of the stuff the people on this thread told me, but it sort of summarizes all the advice I guess.
I felt it only fair to post my answer.
So, it involves iterating through every element, storing all the elements you need to remove in each round of counting, and then using the removeAll function to remove them before the count increases.
Here's the complete code for anyone interested. Any comments would also be welcome.
import java.util.*;
class luckyy
{
public static void main(String args[])
{
Scanner scan = new Scanner(System.in);
System.out.println("Enter n: ");
int i, n, index;
n = scan.nextInt();
ArrayList <Integer> ele = new ArrayList <Integer> (n);
ArrayList <Integer> toRemove = new ArrayList <Integer> (n);
//storing in a list
for(i = 1;i<=n;i++)
{
ele.add(i);
}
System.out.println(ele);
int count = 2;
System.out.println("Size: "+ele.size());
while(count<=ele.size())
{
for(i = 0;i<ele.size();i++)
{
if((i+1)%count == 0)
{
toRemove.add(ele.get(i));
}
}
ele.removeAll(toRemove);
toRemove.clear();
count++;
}
System.out.println(ele);
}
}
There it is! It works, and boy am I glad! If anyone has any further advice, or anything else for me to learn or checkout, you are totally welcome to comment.
Thanks again everyone!

Categories