Repository: alexhsamuel/ntab

Issue:
add a concatenate function

Discussion:

Patch:
diff --git a/ntab/fn.py b/ntab/fn.py
index 23a3ecd..04282ed 100644
--- a/ntab/fn.py
+++ b/ntab/fn.py
@@ -1,8 +1,32 @@
+import numpy as np
+
 from   .lib import container
 from   .tab import Table
 
 #-------------------------------------------------------------------------------
 
+def concat(*tabs):
+    """
+    Concatenates `tabs`.
+
+    All must have the same names and dtypes, up to order.
+    """
+    dtypes = { n: a.dtype for n, a in tabs[0].arrs.items() }
+
+    # First check dtypes.
+    for tab in tabs[1 :]:
+        tab_dtypes = { n: a.dtype for n, a in tab.arrs.items() }
+        if tab_dtypes != dtypes:
+            raise TypeError(f"dtypes do not match: {tab_dtypes}")
+
+    # Collect and concatenate arrays.
+    return Table(
+        (n, np.concatenate([ t.arrs[n] for t in tabs ]))
+        for n in dtypes
+    )
+        
+
+
 def select_arrs(tab, names):
     """
     Returns a table with arrays selected by container `names`.
diff --git a/ntab/tab.py b/ntab/tab.py
index 1b504b7..c89d23e 100644
--- a/ntab/tab.py
+++ b/ntab/tab.py
@@ -55,7 +55,7 @@ def _ensure_array(obj, length):
 
 #-------------------------------------------------------------------------------
 
-class ArraysObjectProxy:
+class ArraysObjectProxy(object):
     """
     Proxy for arrays by name as attributes.
     """
@@ -145,7 +145,7 @@ class ArraysProxy(collections.MutableMapping):
 
 
 
-class Row:
+class Row(object):
 
     def __init__(self, arrs, idx):
         self.__arrs = arrs
@@ -245,7 +245,7 @@ class RowsProxy(collections.Sequence):
 
 #-------------------------------------------------------------------------------
 
-class Table:
+class Table(object):
     """
     Data table consisting of named parallel `ndarray`s.
 


Test patch:
diff --git a/ntab/test/test_fn.py b/ntab/test/test_fn.py
index 7752478..537cf80 100644
--- a/ntab/test/test_fn.py
+++ b/ntab/test/test_fn.py
@@ -1,8 +1,39 @@
+import pytest
+
 from   ntab import Table, fn
 from   ntab.lib.container import ALL, all_but
 
 #-------------------------------------------------------------------------------
 
+def test_concat():
+    tab0 = Table(x=[3,4,5], y=[4,5,6], z=[5,6,7], w=[6,7,8])
+    tab1 = Table(y=[2,1], z=[3,2], x=[0,1], w=[1,0])
+
+    res = fn.concat(tab0, tab1, tab0)
+    assert res.names == ["x", "y", "z", "w"]
+    assert res.num_rows == 8
+    assert list(res.a.x) == [3, 4, 5, 0, 1, 3, 4, 5]
+    assert list(res.a.y) == [4, 5, 6, 2, 1, 4, 5, 6]
+    assert list(res.a.z) == [5, 6, 7, 3, 2, 5, 6, 7]
+    assert list(res.a.w) == [6, 7, 8, 1, 0, 6, 7, 8]
+
+
+def test_concat_err():
+    tab0 = Table(x=[3,4,5], y=[4,5,6], z=[5,6,7], w=[6,7,8])
+    
+    tab1 = Table(y=[2,1], z=[3,2], x=[0,1], w=["foo", "bar"])
+    with pytest.raises(TypeError):
+        fn.concat(tab0, tab1)
+
+    tab2 = Table(y=[2,1], x=[0,1], w=[1,0])
+    with pytest.raises(TypeError):
+        fn.concat(tab0, tab2)
+
+    tab3 = Table(x=[3,4,5], q=[7,8,9], y=[4,5,6], z=[5,6,7], w=[6,7,8])
+    with pytest.raises(TypeError):
+        fn.concat(tab0, tab3)
+    
+
 def test_select_arrs():
     tab = Table(x=[3,4,5], y=[4,5,6], z=[5,6,7], w=[6,7,8])
     assert tab.names == ["x", "y", "z", "w"]