Repository: jtablesaw/tablesaw
Pull request #635 (2019-08-25 14:17:11)

added a few map functions and tests for same.
Thanks for contributing.

- [ ] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

What was changed

## Testing

Did you add a unit test?



Patch:
diff --git a/core/src/main/java/tech/tablesaw/api/Row.java b/core/src/main/java/tech/tablesaw/api/Row.java
index edee4c32..9a61334a 100644
--- a/core/src/main/java/tech/tablesaw/api/Row.java
+++ b/core/src/main/java/tech/tablesaw/api/Row.java
@@ -52,17 +52,17 @@ public class Row implements Iterator<Row> {
           String actualType = tableSlice.getTable().columns().get(i).type().name();
           String proposedType = columnTypeName();
           throw new IllegalArgumentException(
-                  String.format(
-                          "Column %s is of type %s and cannot be cast to %s. Use the method for %s.",
-                          columnName, actualType, proposedType, actualType));
+              String.format(
+                  "Column %s is of type %s and cannot be cast to %s. Use the method for %s.",
+                  columnName, actualType, proposedType, actualType));
         }
       }
     }
 
     private void throwColumnNotPresentError(String columnName) {
       throw new IllegalStateException(
-              String.format(
-                      "Column %s is not present in table %s", columnName, tableSlice.getTable().name()));
+          String.format(
+              "Column %s is not present in table %s", columnName, tableSlice.getTable().name()));
     }
 
     private String columnTypeName() {
@@ -84,7 +84,7 @@ public class Row implements Iterator<Row> {
   private final ColumnMap<Column<String>> stringColumnMap = new ColumnMap<>(ColumnType.STRING);
   private final ColumnMap<BooleanColumn> booleanColumnMap = new ColumnMap<>(ColumnType.BOOLEAN);
   private final ColumnMap<DateTimeColumn> dateTimeColumnMap =
-          new ColumnMap<>(ColumnType.LOCAL_DATE_TIME);
+      new ColumnMap<>(ColumnType.LOCAL_DATE_TIME);
   private final ColumnMap<InstantColumn> instantColumnMap = new ColumnMap<>(ColumnType.INSTANT);
   private final ColumnMap<TimeColumn> timeColumnMap = new ColumnMap<>(ColumnType.LOCAL_TIME);
   private final ColumnMap<Column<?>> columnMap = new ColumnMap<>();
@@ -446,4 +446,4 @@ public class Row implements Iterator<Row> {
   public void setTime(String columnName, LocalTime value) {
     timeColumnMap.get(columnName).set(rowNumber, value);
   }
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java b/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java
index f3af7d81..e8d70a77 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/NumberMapFunctions.java
@@ -259,6 +259,19 @@ public interface NumberMapFunctions {
     return result;
   }
 
+  default DoubleColumn remainder(double val2) {
+    DoubleColumn result = DoubleColumn.create(name() + " % " + val2, size());
+    for (int r = 0; r < size(); r++) {
+      double val1 = getDouble(r);
+      if (DoubleColumnType.isMissingValue(val1) || DoubleColumnType.isMissingValue(val2)) {
+        result.setMissing(r);
+      } else {
+        result.set(r, getDouble(r) % val2);
+      }
+    }
+    return result;
+  }
+
   /** Returns the natural log of the values in this column as a NumberColumn. */
   default DoubleColumn logN() {
     DoubleColumn newColumn = DoubleColumn.create(name() + "[logN]", size());
diff --git a/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java b/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java
index 8271b306..943b1ea4 100644
--- a/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java
+++ b/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java
@@ -62,6 +62,51 @@ public interface StringMapFunctions extends Column<String> {
     return newColumn;
   }
 
+  /**
+   * Capitalizes each String changing the first character of each to title case as per {@link
+   * Character#toTitleCase(int)}, as if in a sentence. No other characters are changed.
+   *
+   * <pre>
+   * capitalize(null)  = null
+   * capitalize("")    = ""
+   * capitalize("cat") = "Cat"
+   * capitalize("cAt") = "CAt"
+   * capitalize("'cat'") = "'cat'"
+   * </pre>
+   */
+  default StringColumn capitalize() {
+
+    StringColumn newColumn = StringColumn.create(name() + "[titleCase]");
+
+    for (int r = 0; r < size(); r++) {
+      String value = getString(r);
+      newColumn.append(StringUtils.capitalize(value));
+    }
+    return newColumn;
+  }
+
+  /**
+   * Repeats each the column's values elementwise, concatinating the results into a new StringColumn
+   *
+   * @param times The number of repeat desired
+   *     <pre>
+   *  repeat("", 2)   = ""
+   *  repeat("cat", 3) = "catcatcat"
+   * </pre>
+   *
+   * @return the new StringColumn
+   */
+  default StringColumn repeat(int times) {
+
+    StringColumn newColumn = StringColumn.create(String.format("%s [rep %d]", name(), times));
+
+    for (int r = 0; r < size(); r++) {
+      String value = getString(r);
+      newColumn.append(StringUtils.repeat(value, times));
+    }
+    return newColumn;
+  }
+
   default StringColumn trim() {
 
     StringColumn newColumn = StringColumn.create(name() + "[trim]");
@@ -224,7 +269,7 @@ public interface StringMapFunctions extends Column<String> {
     return newColumn;
   }
 
-  default StringColumn commonPrefix(Column<?> column2) {
+  default StringColumn commonPrefix(Column<String> column2) {
 
     StringColumn newColumn = StringColumn.create(name() + column2.name() + "[prefix]");
 
@@ -236,7 +281,7 @@ public interface StringMapFunctions extends Column<String> {
     return newColumn;
   }
 
-  default StringColumn commonSuffix(Column<?> column2) {
+  default StringColumn commonSuffix(Column<String> column2) {
 
     StringColumn newColumn = StringColumn.create(name() + column2.name() + "[suffix]");
 
@@ -249,7 +294,7 @@ public interface StringMapFunctions extends Column<String> {
   }
 
   /** Returns a column containing the levenshtein distance between the two given string columns */
-  default DoubleColumn distance(Column<?> column2) {
+  default DoubleColumn distance(Column<String> column2) {
 
     DoubleColumn newColumn = DoubleColumn.create(name() + column2.name() + "[distance]");
 
@@ -267,14 +312,14 @@ public interface StringMapFunctions extends Column<String> {
    * @param columns the column to append
    * @return the new column
    */
-  default StringColumn join(String separator, Column<?>... columns) {
+  default StringColumn join(String separator, Column... columns) {
     StringColumn newColumn = StringColumn.create(name() + "[column appended]", this.size());
     for (int r = 0; r < size(); r++) {
-      String result = getString(r);
+      StringBuilder result = new StringBuilder(getString(r));
       for (Column<?> stringColumn : columns) {
-        result = result + separator + stringColumn.get(r);
+        result.append(separator).append(stringColumn.get(r));
       }
-      newColumn.set(r, result);
+      newColumn.set(r, result.toString());
     }
     return newColumn;
   }
@@ -282,13 +327,32 @@ public interface StringMapFunctions extends Column<String> {
   /**
    * Return a copy of this column with the given string appended to each element
    *
-   * @param append the string to append
+   * @param stringsToAppend the stringified objects to append
+   * @return the new column
+   */
+  default StringColumn concatenate(Object... stringsToAppend) {
+    StringColumn newColumn = StringColumn.create(name() + "[append]", this.size());
+    for (int r = 0; r < size(); r++) {
+      for (Object o : stringsToAppend) {
+        newColumn.set(r, getString(r) + o);
+      }
+    }
+    return newColumn;
+  }
+
+  /**
+   * Return a copy of this column with the corresponding value of each column argument appended to
+   * each element. getString is used to ensure the value returned by the args are strings
+   *
+   * @param stringColumns the string columns to append
    * @return the new column
    */
-  default StringColumn concatenate(String append) {
+  default StringColumn concatenate(Column... stringColumns) {
     StringColumn newColumn = StringColumn.create(name() + "[append]", this.size());
     for (int r = 0; r < size(); r++) {
-      newColumn.set(r, getString(r) + append);
+      StringBuilder s = new StringBuilder(getString(r));
+      for (Column stringColumn : stringColumns) s.append(stringColumn.getString(r));
+      newColumn.set(r, s.toString());
     }
     return newColumn;
   }


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/api/NumericColumnTest.java b/core/src/test/java/tech/tablesaw/api/NumericColumnTest.java
index 78a42633..d3af92a4 100644
--- a/core/src/test/java/tech/tablesaw/api/NumericColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/NumericColumnTest.java
@@ -1,15 +1,16 @@
 package tech.tablesaw.api;
 
+import static java.lang.Double.NaN;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.columns.numbers.DoubleColumnType;
 
-public class NumericColumnTest {
+class NumericColumnTest {
 
   @Test
-  public void testPctChange() {
+  void testPctChange() {
     double[] data = new double[] {100, 100, 100, 100, 101, 102, 99, 98};
     double missing = DoubleColumnType.missingValueIndicator();
     double[] pctChange = new double[] {missing, missing, missing, missing, .01, .02, -.01, -.02};
@@ -17,4 +18,12 @@ public class NumericColumnTest {
     assertArrayEquals(pctChange, result.asDoubleArray(), 0.000001);
     assertEquals("data 4-period Percent Change", result.name());
   }
+
+  @Test
+  void testRemainder() {
+    double[] data = new double[] {100, 101, NaN, 105};
+    DoubleColumn result = DoubleColumn.create("data", data).remainder(20);
+    double[] expected = {0, 1, NaN, 5};
+    assertArrayEquals(expected, result.asDoubleArray());
+  }
 }
diff --git a/core/src/test/java/tech/tablesaw/api/StringColumnTest.java b/core/src/test/java/tech/tablesaw/api/StringColumnTest.java
index c8129ffe..b2a2ee66 100644
--- a/core/src/test/java/tech/tablesaw/api/StringColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/StringColumnTest.java
@@ -393,6 +393,33 @@ class StringColumnTest {
     assertEquals(result.get(2), "k");
   }
 
+  @Test
+  void testRepeat() {
+    String[] words = {"running", "icecube", "back"};
+    StringColumn wordColumn = StringColumn.create("words", words);
+    StringColumn result = wordColumn.repeat(3);
+    assertEquals(result.get(0), "runningrunningrunning");
+  }
+
+  @Test
+  void testCapitalize() {
+    String[] words = {"running", "ice cube", "stuff4us"};
+    StringColumn wordColumn = StringColumn.create("words", words);
+    StringColumn result = wordColumn.capitalize();
+    assertEquals(result.get(0), "Running");
+    assertEquals(result.get(1), "Ice cube");
+    assertEquals(result.get(2), "Stuff4us");
+  }
+
+  @Test
+  void testConcatinate() {
+    String[] words = {"running", "icecube", "back"};
+    StringColumn wordColumn1 = StringColumn.create("words1", words);
+    StringColumn wordColumn2 = StringColumn.create("words2", words);
+    StringColumn result = wordColumn1.concatenate(wordColumn2);
+    assertEquals(result.get(0), "runningrunning");
+  }
+
   @Test
   void testSubstring2() {
     String[] words = {"running", "icecube", "back"};