The new, Java 8 way:
List<Integer> range = IntStream.range(1, 501).boxed().collect(Collectors.toList());
Answer from Norswap on Stack OverflowThe new, Java 8 way:
List<Integer> range = IntStream.range(1, 501).boxed().collect(Collectors.toList());
Using Guava, you can resort to a Range: https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html
Of course, there will still be loops in your code, but they just might be hidden from the code for simplicity sake.
For instance:
Range<Integer> yourValues = Range.closed(1, 500);
Check https://github.com/google/guava/wiki/RangesExplained for some more examples.
Keep in mind that if you do need to eventually iterate over the Range, you cannot do so directly, only through using DiscreteDomains.integers().
To answer your question, yes, early returns are very good. Often, you will check all error conditions at the beginning of the method, then do your work knowing the input data is OK. You can read more about this here.
if(list.size() == 0){ return 0; } if(list.size() == 1){ return 1; }
You can combine these into a single statement. Look at what you are returning - you are returning the value of list.size() in both of these conditions.
if(list.size() == 0 || list.size() == 1){
return list.size();
}
if(elementValue < min){ min = elementValue; }
For the most part, your indentation is very good. Just try to be a bit more consistent.
Other than these little things, this looks like very good code.
Use this:
if (list.size() <= 1) {
return list.size();
}
list.get(1) refers to the second element in the list, not the first. Either rename the variable or use list.get(0). With that change, you don't need to use a list size of 1 as a special case.
Only being able to do the job on ArrayList is quite useless. Your method can take an argument of type List instead.
No, it is no need to use else if in this case.
Rewriting with Iterable
You currently check the first item in the list twice. If your code would accept an Iterable<Integer>, you would support even more data types (You still support ArrayList also).
The only thing you need is the Iterator from the iterable. When you have that, you can first see if it contains at least one item. If it doesn't, return 0. Otherwise, you do similar to what you are already doing, but with different code.
public static int range(Iterable<Integer> numbers) {
Iterator<Integer> iterator = numbers.iterator();
if (!iterator.hasNext()) {
return 0;
}
int firstElement = iterator.next();
int max = firstElement;
int min = firstElement;
while (iterator.hasNext()) {
int elementValue = iterator.next(i);
if (max < elementValue) {
max = elementValue;
}
if (elementValue < min) {
min = elementValue;
}
}
return (max - min) + 1;
}
Happy coding!
One way you can remove duplicated code is to create a method:
public static int[] GetMaxMin (ArrayList <Integer> list)
{
int max = list.get(0);
int min = list.get(0);
for (int i : list){
if (i > max) {
max = i;
} else if (i < min) {
min = i;
}
}
int[] maxMin = {max, min};
return maxMin;
}
This can then be called like this:
max = GetMaxMin(list)[0];
min = GetMaxMin(list)[1];
However, the above said, I would not recommend doing this because it has two purposes - to get the max value and the min value in the list. What I would recommend is to implement two methods - one to get the max value and one to get the min value:
public static int GetMax (ArrayList <Integer> list)
{
int max = list.get(0);
for (int i : list){
if (i > max) {
max = i;
}
}
return max;
}
GetMin would be similar. Then, all you would need to do is this:
int max = GetMax(list1);
int min = GetMin(list1);
int max2 = GetMax(list2);
int min2 = GetMin(list2);
As you are calculating a range of values, though, you should just write a method to do that:
public static int CalculateRange (ArrayList <Integer> list)
{
int max = list.get(0);
int min = list.get(0);
for (int i : list){
if (i > max) {
max = i;
} else if (i < min) {
min = i;
}
}
return max - min;
}
As brought up in the comments, this will crash if there are 0 elements in the list. To prevent this, you should probably check this before you try to get the range, like this:
if (list.size() != 0) {
System.out.printf("List 1 Range: %d\tList 2 Range: %d\n", CalculateRange(list1), CalculateRange(list2));
} else {
System.out.println("There are no elements in this array");
}
Hosch250 and h.j.k. have the right idea here, to reduce the code to a function that returns the range (max - min) for each list. The function, according to Hosch250, will have the signature:
public static int CalculateRange (ArrayList <Integer> list)
I would recommend changing that to a more Java-standard capitalization of having a lower-case C in CalculateRange. i.e. calculateRange. Additionally, I would change the input type from ArrayList, to just Collection. There is no need to restrict the function to just one concrete type.
Finally, because you have the option, I would strongly recommend that you investigate the new Java 8 features which, in this case, would help a lot.
The idea would be to convert your Collection<Integer> to an int stream, and to then compute the IntSummaryStatistics you need.... something like:
public static int calculateRange(Collection<Integer> data) {
IntSummaryStatistics stats = data.stream()
.filter(d -> d != null)
.mapToInt(d-> d.intValue())
.summaryStatistics();
return stats.count() == 0 ? 0 : stats.max() - stats.min();
}
Then, in your main method, I would also recommend the use of 'formatted' print, using the Format syntax
System.out.printf("List 1 Range: %d\tList 2 Range: %d\n",
calculateRange(list1),
calculateRange(list2));
You can use IntStream.range :
IntStream.range(0,listOfAs.getList().size()).forEach(i->{...});
This won't iterate over your list.
The forEach method of IntStream accepts an IntConsumer, which is a functional interface that has the method void accept(int value). In my example I supplied a lambda expression that matches that interface. You do get the int index, whether you use it or not.
you can use the famous for loop:
for(int i = 0; i < listOfAs.getList().size(); i++){
}
This will not iterate through the elements of your list.
Can you use a stream rather than an array in the rest of your application? If so I'd simply cut off the toArray bit at the end. Otherwise this looks exactly like what I'd expect in Java 8.
IntStream has a rangeClosed method that allows you to omit the +1.
IntStream.rangeClosed(start, end) instead of IntStream.range(start, end+1)
You could collect the numbers into a sorted set and then iterate over the numbers.
Quick and dirty example:
SortedSet<Integer> numbers = new TreeSet<Integer>();
numbers.add( 1 );
numbers.add( 2 );
numbers.add( 3 );
numbers.add( 6 );
numbers.add( 7 );
numbers.add( 10 );
Integer start = null;
Integer end = null;
for( Integer num : numbers ) {
//initialize
if( start == null || end == null ) {
start = num;
end = num;
}
//next number in range
else if( end.equals( num - 1 ) ) {
end = num;
}
//there's a gap
else {
//range length 1
if( start.equals( end )) {
System.out.print(start + ",");
}
//range length 2
else if ( start.equals( end - 1 )) {
System.out.print(start + "," + end + ",");
}
//range lenth 2+
else {
System.out.print(start + "-" + end + ",");
}
start = num;
end = num;
}
}
if( start.equals( end )) {
System.out.print(start);
}
else if ( start.equals( end - 1 )) {
System.out.print(start + "," + end );
}
else {
System.out.print(start + "-" + end);
}
Yields: 1-3,6,7,10
Apache Commons has the IntRange type that you can use. Unfortunately I didn't find a good corresponding set of utilities to create them. Here's the basic approach you could use:
//create a list of 1-integer ranges
List<IntRange> ranges = new LinkedList<IntRange>();
for ( int pageNum : pageNums ) {
ranges.add(new IntRange(pageNum));
}
//sort the ranges
Collections.sort(ranges, new Comparator<IntRange>() {
public int compare(IntRange a, IntRange b) {
return Integer.valueOf(a.getMinimumInteger()).compareTo(b.getMinimumInteger());
}
});
List<IntRange> output = new ArrayList<IntRange>();
if ( ranges.isEmpty() ) {
return output;
}
//collapse consecutive ranges
IntRange range = ranges.remove(0);
while ( !ranges.isEmpty() ) {
IntRange nextRange = ranges.remove(0);
if ( range.getMaximumInteger() == nextRange.getMinimumInteger() - 1 ) {
range = new IntRange(range.getMinimumInteger(), nextRange.getMaximumInteger());
} else {
output.add(range);
range = nextRange;
}
}
output.add(range);
Alternatively you could skip the first step and create the ranges directly from the sorted list of page numbers.
You can use a stream to generate the range and collect it to a list
IntStream.range(0, 10)
.collect(Collectors.toList());
Note that the first number is inclusive and the second is exclusive. You can use the rangeClosed method to include the second argument. http://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#range-int-int-
There are other types of streams for other primitives (e.g. see DoubleStream.iterate for example).
If you need a possibility to generate numbers of any type, and you are allowed to use third-party libraries, I would recommend to use Guava Ranges.
Uppermost, Ranges are used to manipulate with ranges, as "mathematical abstractions". But you are allowed to generate sequences from ranges:
Set<Integer> set = ContiguousSet.create(Range.closed(1, 5), DiscreteDomain.integers());
// set contains [2, 3, 4]
By default, Guava supplies you with three kinds of discrete domains: integers, longs and big integers. But you are allowed to create your own:
You can make your own DiscreteDomain objects, but there are several important aspects of the DiscreteDomain contract that you must remember.
Please, consult official docs about DiscreteDomain implementation peculiarities.
What you are describing is a sparse matrix upon which you wish to do a range select.
I'm going to start out with no, neither a HashMap nor a LinkedHashMap will do what you want optimally. The reason for this to find the elements in a range HashMap is effectively walking an unordered list - O(n). The LinkedHashMap may be slightly more optimal than awful if the elements are put into the LinkedHashMap in sorted order and no new ones are added afterwards. Its still not an optimal approach because instead of O(n) you're getting O(n/m) (because you stop walking the list once you get past the range) which is still effectively O(n). You can't do a binary search on the LinkedHashMap list.
So, lets look at some other structures. What you are really after is the SortedMap interface. It allows you to quickly pull out the sub map from one key to another with SortedMap subMap(K fromKey, K toKey) which is what you want to do. Pick one of those classes and you will have what you are after.
You could then get the values for each of the submaps and do a boolean retainAll(Collection c) of one set to the other.
That's the easy answer. It is effectively the lists of lists approach for the sparse matrix. You could, however encode the matrix instead in some other format that gives you a more direct sub range option. This isn't something that is part of the Java standard library and you may find yourself going down this path for writing your own implementation in that case.
If you see the possibility in the future that this may be the case, you should consider writing your own interface for the sparse matrix and then implement a class behind it that does the lookups that you want. This way if you later decide that you need to change the implementation for some reason, its a matter of changing the implementation (a new class that wraps something else) rather than changing all the code to handle a different structure.
While your solution based on a treemap of treemaps will work, and is much better than the hashmap solution you started with, you may want to look into the possibility of using a QuadTree, which is a structure similar to a TreeMap but with a pair of coordinates as the key rather than just one. This makes selecting rectangular ranges from them particularly efficient. There are many implementations available for Java that can be found with a quick google.
Apache Commons Lang has a Range class for doing arbitrary ranges.
Range<Integer> test = Range.between(1, 3);
System.out.println(test.contains(2));
System.out.println(test.contains(4));
Guava Range has similar API.
If you are just wanting to check if a number fits into a long value or an int value, you could try using it through BigDecimal. There are methods for longValueExact and intValueExact that throw exceptions if the value is too big for those precisions.
You could create a class to represent this
public class Range
{
private int low;
private int high;
public Range(int low, int high){
this.low = low;
this.high = high;
}
public boolean contains(int number){
return (number >= low && number <= high);
}
}
Sample usage:
Range range = new Range(0, 2147483647);
if (range.contains(foo)) {
//do something
}