3
\$\begingroup\$

Challenge

Given a list of numbers and a positive integer k, reverse the elements of the list, k items at a time.

Specifications

  1. The first argument is a path to a file.
  2. The file contains multiple lines.
  3. Each line is a test case represented by a comma separated list of numbers and the number k, separated by a semicolon.
  4. Print the comma separated list of numbers after reversing.
  5. If the number of elements is not a multiple of k, then the remaining elements should be unaltered.

Sample Input

1,2,3,4,5;2
1,2,3,4,5;3

Sample Output

2,1,4,3,5
3,2,1,4,5

Source

My Solution

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Solution {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("No file path provided.");
            System.exit(1);
        }

        if (args.length >= 2) {
            System.out.println("Excessive arguments only the first will be considered.");
        }
        
        try (Scanner file = new Scanner(new File(args[0]))) {
            while (file.hasNextLine()) {
                System.out.println(reversed(file.nextLine()));
            }
        } catch (FileNotFoundException fnfe) {
            System.err.println("Could not find file.");
        }
    }

    private static String reversed(String line) {
        String[] helper = line.split(";");
        int k = Integer.parseInt(helper[1]);
        if (k == 0) {
            return helper[0];
        }
        return reversedByK(helper[0].split(","), k);
    }

    private static String reversedByK(String[] nums, int k) {
        StringBuilder result = new StringBuilder();
        for (int i = k - 1; i < nums.length; i += k) {
            for (int j = i; i - j <= k - 1; j--) {
                result.append(',').append(nums[j]);
            }
        }

        for (int i = nums.length - nums.length % k; i < nums.length; i++) {
            result.append(',').append(nums[i]);
        }
        return result.substring(1);
    }
}
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

The core of your solution doesn't suit my taste:

private static String reversedByK(String[] nums, int k) {
    StringBuilder result = new StringBuilder();
    for (int i = k - 1; i < nums.length; i += k) {
        for (int j = i; i - j <= k - 1; j--) {
            result.append(',').append(nums[j]);
        }
    }

    for (int i = nums.length - nums.length % k; i < nums.length; i++) {
        result.append(',').append(nums[i]);
    }
    return result.substring(1);
}

My main gripe is that it works at two different levels: it accepts an array as input, but returns a string. It's a mild violation of the Single Responsibility Principle. One consequence is that it uses the assumption that a comma should be used as a delimiter. It would be more elegant if whichever function performed .split(",") were also responsible for doing String.join(",", …).

Also consider taking advantage of the Scanner more to help you with parsing the input.

private static final Pattern DELIM = Pattern.compile(";|\n|\r");

public static void main(String[] args) {
    …
    try (Scanner file = new Scanner(new File(args[0])).useDelimiter(DELIM)) {
        while (file.hasNextLine()) {
            System.out.println(reversedByK(file.next(), file.nextInt()));
            file.nextLine();
        }
    } catch (FileNotFoundException fnfe) {
        System.err.println("Could not find file.");
    }
}

private static String reversedByK(String list, int k) {
    String[] array = list.split(",");
    reversedByK(array, k);
    return String.join(",", array);
}

private static void reversedByK(String[] array, int k) {
    for (int i = 0; i + k <= array.length; i += k) {
        for (int a = i, b = i + (k - 1); a < b; a++, b--) {
            String swap = array[a];
            array[a] = array[b];
            array[b] = swap;
        }
    }
}

One bonus benefit of this swap-in-place solution is that there is no need for a second loop in reversedByK() to handle the remaining elements.

\$\endgroup\$
4
  • \$\begingroup\$ Not certain why but nextInt doesn't seem to work properly this way. Also, want to take a moment to say thank you for all your awesome answers throughout the site. They're insightful, educational and inspiring. \$\endgroup\$
    – Legato
    Sep 5, 2016 at 16:01
  • \$\begingroup\$ I think it should work if you set the delimiters appropriately. In what way does it fail? \$\endgroup\$ Sep 5, 2016 at 16:10
  • \$\begingroup\$ InputMismatchException when calling it. What's interesting is calling next works and returns the integer there, but then trying to call Integer.parseInt causes a NumberFormatException. It's quite confusing. \$\endgroup\$
    – Legato
    Sep 5, 2016 at 16:28
  • \$\begingroup\$ It must be because your input is using DOS-style CR+LF line terminators. Fixed in Rev 4. \$\endgroup\$ Sep 5, 2016 at 18:45
2
\$\begingroup\$
        for (int i = k - 1; i < nums.length; i += k) {
            for (int j = i; i - j <= k - 1; j--) {
                result.append(',').append(nums[j]);
            }
        }

        for (int i = nums.length - nums.length % k; i < nums.length; i++) {
            result.append(',').append(nums[i]);
        }

You could write this more simply as

        int i = k - 1;
        for ( ; i < nums.length; i += k) {
            for (int j = i; j > i - k; j--) {
                result.append(',').append(nums[j]);
            }
        }

        for (i = i - k + 1; i < nums.length; i++) {
            result.append(',').append(nums[i]);
        }

This works because the second loop is just finishing what the first loop started. So instead of recalculating where we left off, just persist that past the end of the first loop and start the second loop before the last increment. And adjust by 1 since i started at k - 1.

The following are equivalent

i - j <= k - 1
i - j < k
i < j + k
i - k < j
j > i - k

The first change works because if an integer is less than or equal to one less than something, it is strictly less than the something. Why do an extra subtraction if it's not required?

The last change is just reversing the order of the comparison. I'd prefer to have j alone, unnegated, and first, since it's the loop variable. Also, i - k is invariant to the loop, so we can hope the compiler will only calculate it once rather than every iteration.

The other changes are just subtracting the same value from both sides to get j alone and unnegated.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.