I've been looking for a method similar to String.split in Scala Array, but I've not been able to find it.
What I want to do is to split an array by a separator.
For example, separating the following array:
val array = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
using the '\n' separator, should result in:
List(Array(a, b), Array(c, d, e), Array(g))
I know that I can convert the Array to String, and apply split there:
array.mkString.split('\n').map(_.toArray)
but I would prefer to skip the conversion.
The solution I have so far involves using span recursively and is a bit too boilerplate:
def splitArray[T](array: Array[T], separator: T): List[Array[T]] = {
def spanRec(array: Array[T], aggResult: List[Array[T]]): List[Array[T]] = {
val (firstElement, restOfArray) = array.span(_ != separator)
if (firstElement.isEmpty) aggResult
else spanRec(restOfArray.dropWhile(_ == separator), firstElement :: aggResult)
}
spanRec(array, List()).reverse
}
I'm sure there must be something in Scala I'm missing. Any idea?
thanks,
Ruben
This is not the most concise implementation, but it should be fairly performed and preserves the array type without resorting to reflection. The loop can of course be replaced by a recursion.
Since your question doesn't explicitly state what should be done with the separator I assume, that they should not cause any entry in the output list (see the test cases below).
def splitArray[T](xs: Array[T], sep: T): List[Array[T]] = {
var (res, i) = (List[Array[T]](), 0)
while (i < xs.length) {
var j = xs.indexOf(sep, i)
if (j == -1) j = xs.length
if (j != i) res ::= xs.slice(i, j)
i = j + 1
}
res.reverse
}
Some tests:
val res1 =
// Notice the two consecutive '\n'
splitArray(Array('a', 'b', '\n', 'c', 'd', 'e', '\n', '\n', 'g', '\n'), '\n')
println(res1)
// List([C#12189646, [C#c31d6f2, [C#1c16b01f)
res1.foreach(ar => {ar foreach print; print(" ")})
// ab cde g
// No separator
val res2 = splitArray(Array('a', 'b'), '\n')
println(res2)
// List([C#3a2128d0)
res2.foreach(ar => {ar foreach print; print(" ")})
// ab
// Only separators
val res3 = splitArray(Array('\n', '\n'), '\n')
println(res3)
// List()
A borrowed the arguments from sschaef's solution:
def split[T](array : Array[T])(where : T=>Boolean) : List[Array[T]] = {
if (array.isEmpty) Nil
else {
val (head, tail) = array span {!where(_)}
head :: split(tail drop 1)(where)
}
} //> split: [T](array: Array[T])(where: T => Boolean)List[Array[T]]
val array = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
split(array){_ =='\n'} //> res2: List[Array[Char]] = List(Array(a, b), Array(c, d, e), Array(g))
def splitByNewLines(array : Array[Char]) = split(array){_ =='\n'}
splitByNewLines(array) //> res3: List[Array[Char]] = List(Array(a, b), Array(c, d, e), Array(g))
You can use the span method to split the array into two parts and then call your split method recursively on the second part.
import scala.reflect.ClassTag
def split[A](l:Array[A], a:A)(implicit act:ClassTag[Array[A]]):Array[Array[A]] = {
val (p,s) = l.span(a !=)
p +: (if (s.isEmpty) Array[Array[A]]() else split(s.tail,a))
}
This is not very efficient though, since it has quadratic performance. If you want something fast, a simple tail recursive solution will probably be the best approach.
With lists instead of arrays you would get linear performance and wouldn't need reflection.
This is a short formulation that should do the job:
def split(array:Array[Char], sep:Char) : Array[Array[Char]] = {
/* iterate the list from right to left and recursively calculate a
pair (chars,list), where chars contains the elements encountered
since the last occurrence of sep.
*/
val (chars, list) = array.foldRight[(List[Char],List[Array[Char]])]((Nil,Nil))((x,y) => if (x == sep) (Nil, (y._1.toArray)::y._2) else (x::y._1, y._2) );
/* if the last element was sep, do nothing;
otherwise prepend the last collected chars
*/
if (chars.isEmpty)
list.toArray
else
(chars.toArray::list).toArray
}
/* example:
scala> split(array,'\n')
res26: Array[Array[Char]] = Array(Array(a, b), Array(c, d, e), Array(g), Array())
*/
If we use List instead of Array, we can generalize the code a bit:
def split[T](array:List[T], char:T) : List[List[T]] = {
val (chars, list) = array.foldRight[(List[T],List[List[T]])]((Nil,Nil))((x,y) => if (x == char) (Nil, (y._1)::y._2) else (x::y._1, y._2) )
if (chars.isEmpty) list else (chars::list)
}
/* example:
scala> split(array.toList, '\n')
res32: List[List[Char]] = List(List(a, b), List(c, d, e), List(g), List())
scala> split(((1 to 5) ++ (1 to 5)).toList, 3)
res35: List[List[Int]] = List(List(1, 2), List(4, 5, 1, 2), List(4, 5))
*/
If this solution is considered as elegant or unreadable is left to the reader and her preference for functional programming :)
Simple way of doing it using foldLeft
val f = array.foldLeft((Array[Char](),List[Array[Char]]()))(
(acc, char: Char) => {
char match {
case '\n' => (Array(),acc._1 :: acc._2)
case _ => (acc._1 :+ char,acc._2)
}
}
)._2.reverse
I came up with a solution that aims at the following:
is generic: you should be able to split an Array just like a Vector, and a collection of Chars just like a collection of arbitrary objects
preserves the types of the inputs: an Array[A] gets split in an Array[Array[A]], a Vector[A] gets split in a Vector[Vector[A]]
allows to use a lazy approach if needed (via an Iterator)
exposes a compact interface for most cases (just call a split method on your collection)
Before getting to the explanation, note that you can play with the code that follows here on Scastie.
The first step is implementing an Iterator that chunks your collection:
import scala.language.higherKinds
import scala.collection.generic.CanBuildFrom
final class Split[A, CC[_]](delimiter: A => Boolean, as: CC[A])(
implicit view: CC[A] => Seq[A], cbf: CanBuildFrom[Nothing, A, CC[A]])
extends Iterator[CC[A]] {
private[this] var it: Iterator[A] = view(as).iterator
private def skipDelimiters() = {
it = it.dropWhile(delimiter)
}
skipDelimiters()
override def hasNext: Boolean = it.hasNext
override def next(): CC[A] = {
val builder = cbf()
builder ++= it.takeWhile(!delimiter(_))
skipDelimiters()
builder.result()
}
}
I'm using a predicate instead of a value to be more elastic in how the collection gets split, especially when splitting a collection of non-scalar values (like Chars).
I'm using an implicit view on the collection type to be able to apply this to all kinds of collection that can be seen as a Seq (like Vectors and Arrays) and a CanBuildFrom to be able to build the exact type of collection I'm receiving as an input.
The implementation of the Iterator simply makes sure to drop delimiters and chunk the rest.
We can now use an implicit class to offer a friendly interface and add the split method to all the collections, both allowing a predicate or a value to be defined as delimiters:
final implicit class Splittable[A, CC[_]](val as: CC[A])(implicit ev1: CC[A] => Seq[A], ev2: CanBuildFrom[Nothing, A, CC[A]], ev3: CanBuildFrom[Nothing, CC[A], CC[CC[A]]]) {
def split(delimiter: A => Boolean): CC[CC[A]] = new Split(as)(delimiter).to[CC]
def split(delimiter: A): CC[CC[A]] = new Split(as)(_ == delimiter).to[CC]
}
Now you can use your method freely on collection of Chars
val a = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
val b = List('\n', '\n', '\n')
val c = Vector('\n', 'c', 'd', 'e', '\n', 'g', '\n')
val d = Array('a', 'b', 'c', 'd', 'e', 'g', '\n')
val e = Array('a', 'b', 'c', 'd', 'e', 'g', '\n')
a.split('\n')
b.split('\n')
c.split('\n')
d.split('\n')
e.split('\n')
and arbitrary objects alike:
final case class N(n: Int, isDelimiter: Boolean)
Vector(N(1, false), N(2, false), N(3, true), N(4, false), N(5, false)).split(_.isDelimiter)
Note that by using the iterator directly you use a lazy approach, as you can see if you add a debug print to the next method and try to execute the following:
new Split(Vector('\n', 'c', 'd', 'e', '\n', 'g', '\n'))(_ == '\n'}).take(1).foreach(println)
If you want, you can add a couple of methods to Splittable that return an Iterator, so that you can expose the lazy approach as well directly through it.
I don't know of any build-in method, but I came up with a simpler one than yours:
def splitOn[A](xs: List[A])(p: A => Boolean): List[List[A]] = xs match {
case Nil => Nil
case x :: xs =>
val (ys, zs) = xs span (!p(_))
(x :: ys) :: splitOn(zs.tail)(p)
}
// for Array
def splitOn[A : reflect.ClassTag](xs: Array[A])(p: A => Boolean): List[Array[A]] =
if (xs.isEmpty) List()
else {
val (ys, zs) = xs.tail span (!p(_))
(xs.head +: ys) :: splitOn(zs.tail)(p)
}
scala> val xs = List('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
xs: List[Char] =
List(a, b,
, c, d, e,
, g,
)
scala> splitOn(xs)(_ == '\n')
res7: List[List[Char]] = List(List(a, b), List(c, d, e), List(g))
How about this? No reflection, and not recursive either but tries to use as much of the scala library as possible.
def split[T](a: Array[T], sep: T)(implicit m:ClassManifest[T]): Array[Array[T]] = {
val is = a.indices filter (a(_) == sep)
(0 +: (is map (1+))) zip (is :+ (a.size+1)) map {
case(from,till) => a.slice(from, till)
}
}
Probably slow, but just for fun. :-)
The indices filter gives you the indices (is) of where your separator was found.
In your example, that's 2,6,8. I think this is O(n).
The next line transforms that into (0,2), (3,6), (7,8), (9, 10). So k separators yield k+1 ranges.
These are handed to slice, which does the rest of the work. The transformation is also O(n) where n is the number of separators found.
(This means an input of Array[Char]() will yield Array(Array()) and not the more intuitive Array() but that's not too interesting).
The array appending/prepending (:+, +:) is wasteful using arrays, but nothing that can't be solved by using an appropriate collection that lets you have O(1) appends/prepends.
You can also accomplish this using fold:
def splitArray[T](array: Array[T], separator: T) =
array.foldRight(List(List.empty[T])) { (c, list) =>
if (c == separator) Nil :: list
else (c :: list.head) :: list.tail
}.filter(!_.isEmpty).map(_.reverse).toArray
which was already mentioned by lambda.xy.x, but for some reason it was a bit less readable then necessary ;)
Pimped version of generic sequence / array split -
implicit def toDivide[A, B <% TraversableLike[A, B]](a : B) = new {
private def divide(x : B, condition: (A) => Boolean) : Iterable[B] = {
if (x.size > 0)
x.span(condition) match {
case (e, f) => if (e.size > 0) Iterable(e) ++ divide(f.drop(1),condition) else Iterable(f)
}
else
Iterable()
}
def divide(condition: (A) => Boolean): Iterable[B] = divide(a, condition)
}
Almost a one-liner:
val it = array.iterator
List.range(0, array.count(_ == '\n')).map(_ => it.takeWhile(_ != '\n').toArray)
For a given array, this makes use of an Iterator version of the Array in order to call .takeWhile as many times as there are occurrences of the separator.
Another version of the same a bit shorter although less readable, using List.tabulate which is a map over a range:
val it = array.iterator
List.tabulate(array.count(_ == '\n'))(_ => it.takeWhile(_ != '\n').toArray)
This can be made a generic equivalent of array.mkString.split("\n", -1).map(_.toArray) by pimping Array with:
implicit class ArrayExtensions[T: ClassTag](array: Array[T]) {
def split(sep: T): List[Array[T]] = {
val it = array.iterator
List.range(0, array.count(_ == sep)).map(_ => it.takeWhile(_ != sep).toArray)
}
}
and used this way:
Array('\n', '\n', 'a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n').split('\n')
// List(Array(), Array(), Array(a, b), Array(c, d, e), Array(g), Array())
To get rid of empty sub-arrays resulting from 2 successive occurrences of the separator, one can pipe the result with .filter(_.nonEmpty).
Related
I would like to use nimbioseq and iterate two files with the same number of sequences (using the readSeq()iterator), as:
for seq1, seq2 in readSeq(file1), readSeq(file2):
echo seq1.id, "\t", seq2.id
For this scenario I suppose I need some sort of "zipping" operator, which I couldn't understand how to use [ found this: https://hookrace.net/nim-iterutils/iterutils.html#zip.i,,Iterable[S] ].
or alternatively understand how to get a single "iteration" outside a for loop (if possible):
for seq1 in readSeq(file1):
let seq2 = readSeq(file2);
echo seq1.id, "\t", seq2.id
Thanks for you help!
toClosure from iterutils is limited, but you can:
import iterutils
template initClosure(id,iter:untyped) =
let id = iterator():auto{.closure.} =
for x in iter:
yield x
initClosure(f1,readSeq(file1))
#creates a new closure iterator, 'f1'
initClosure(f2,readSeq(file2))
#creates a new closure iterator, 'f2'
for seq1,seq2 in zip(f1,f2):
echo seq1.id,"\t",seq2.id
Edit: thanks to #pietropeter for pointing out the bug, here's their example rewritten using this template:
import iterutils
template initClosure(id:untyped,iter:untyped) =
let id = iterator():auto {.closure.} =
for x in iter:
yield x
iterator letters: auto =
for c in 'a' .. 'z':
yield c
# Now requires a parameter
iterator numbers(s: int): int =
var n = s
while true:
yield n
inc n
initClosure(cletter,letters())
initClosure(numbers8,numbers(8))
for (c, n) in zip(cletter, numbers8):
echo c, n
I'm going to use this iterators code from Manual, and insert your problem in it. I'm sure it has room for improvement:
type
Task = iterator (r: var int)
iterator f1(r: var int){.closure.} =
for n in [1, 3, 5]:
r = n
yield
iterator f2(r: var int){.closure.} =
for n in [2, 4, 6]:
r = n
yield
proc runTasks(t: varargs[Task]) =
var ticker = 0
var r: int
while true:
var x = t[ticker mod t.len]
x(r)
echo r
if finished(x): break
inc ticker
runTasks(f1, f2)
You'll see in the output 1,2,3,4,5,6,6 (finished is prone to error, as stated in the manual, and returns the last item twice). You have to update the code, replacing r: var int with whatever type returns readSeq(file) (r: var Record, I think), and replace the iterators for n in [1, 2, 3] with for s in readSeq(file).
If the type of behaviour you want is that of zip, the one from iterutils seems to work fine. The only caveat is that it requires closure iterators (see manual for the difference between inline and closure iterators). Example (https://play.nim-lang.org/#ix=2yXV):
import iterutils
iterator letters: char {.closure.} =
for c in 'a' .. 'z':
yield c
iterator numbers: int {.closure.}=
var n = 1
while true:
yield n
inc n
for (c, n) in zip(letters, numbers):
echo c, n
I see that readseq in nimbioseq is not closure but probably something like this could work (edit: its should not, see below):
iterator closureReadSeqs(filename: string): Record {.closure.} =
for rec in readSeqs(filename):
yield rec
Edit
For the case of iterator with a parameter in the comments, the fix is to have a proc that returns an iterator (which will be a closure iterator by default in this case). Updated example (https://play.nim-lang.org/#ix=2z0e):
import iterutils
iterator letters: char {.closure.} =
for c in 'a' .. 'z':
yield c
# Now requires a parameter
proc numbers(s: int): iterator(): int =
return iterator(): int =
var n = s
while true:
yield n
inc n
let numbers8 = numbers(8)
for (c, n) in zip(letters, numbers8):
echo c, n
Now my best guess on how to make this work for nimbioseq is:
proc closureReadSeqs(filename: string): iterator(): Record =
return iterator(): Record =
for rec in readSeqs(filename):
yield rec
Let's say I want to iterate over an array in a way that isn't well-supported by the Js/Belt standard library functions. For example, perhaps I need to examine pairs of elements at a time. With a list, this is straightforward to do in a recursive style:
let rec findDouble = (list) => switch list {
| list{a, b, ..._} when a == b => a
| list{_, b, ...rest} => findDouble(list{b, ...rest})
| _ => 0
}
list{7, 9, 10, 10, 11, 13} |> findDouble |> Js.log // 10
However, ReScript seems to gently discourage lists in favor of arrays (see: the clumsier list syntax and the absence of list equivalents of certain standard library functions like Belt.Map.fromArray), so I'm not sure if converting an array to a list just to use this style is idiomatic - especially if the function produces a list that must then turn back into an array.
Of course I can use mutability to implement the function in a traditional imperative way:
let findDouble = (arr) => {
let idx = ref(1)
let answer = ref(0)
while (idx.contents < Js.Array.length(arr)) && (answer.contents == 0) {
if arr[idx.contents] == arr[idx.contents - 1] {
answer := arr[idx.contents]
}
idx := idx.contents + 1
}
answer.contents
}
[7, 9, 10, 10, 11, 13] |> findDouble |> Js.log // 10
But this is ugly and runs counter to the functional bones of ReScript.
What is a clean, idiomatic way to implement this function?
You can still use recursion, just with incrementing an index instead of using the tail of the list:
let findDouble = arr => {
let rec loop = idx =>
if idx >= Array.length(arr) {
0
} else if arr[idx] == arr[idx - 1] {
arr[idx]
} else {
loop(idx + 1)
}
loop(1)
}
Here the requirement is to compare both the array elements a(i) and b(i). And update the
If a[i]>b[i] , then a is awarded 1 point If b[i]>a[i] , then b
is awarded 1 point
object MyWizard extends App {
def compareTriplets(a:Array[Int],b:Array[Int]):Array[Int]= {
var aCount=0
var bCount=0
for(i<-0 to a.length-1){
for(j<-0 to b.length-1){
if(i==j) {
if(a(i)>b(i)){
aCount=aCount+1
}else if(a(i)<b(i)){
bCount=bCount+1
}
}
}
}
val c:Array[Int]=Array(aCount,bCount)
c
}
println(compareTriplets(Array(1,32,3),Array(453,2,1)))
}
Here is a more efficient, idiomatic, generic and simpler implementation of your algorithm.
// This works for any type as long as we know how to compare instances of it.
def compareTriplets[T : Ordering](as: List[T], bs: List[T]): (Int, Int) = {
import Ordering.Implicits._ // Provides the comparison operators.
(as lazyZip bs).foldLeft((0, 0)) {
case ((aCount, bCount), (a, b)) =>
if (a > b) (aCount + 1, bCount)
else if (a < b) (aCount, bCount + 1)
else (aCount, bCount)
}
}
use a toString() method because it is being interpreted as its data address, not a string format of the data.
Here is a simple geeksForGeeks example: https://www.geeksforgeeks.org/scala-map-tostring-method-with-example/
I'm wondering about methods of mapping multiple arrays into one list of object.
I mean e.g. I have
val a = arrayOf("A1","A2","A3")
val b = arrayOf("B1","B2","B3")
and
data class SomeClass(val v1:String, val v2:String)
I want to parse it in elegant way to have list like that:
val list = listOf(SomeClass("A1","B1"),SomeClass("A2","B2"),SomeClass("A3","B3"))
I assume they are of the same length. The only way I thought of is:
val list = mutableListOf<SomeClass>()
for (i in a.indices)
array.add(SomeClass(a[i],b[i])
Is there a better, more elegant solution (maybe using Collecions.zip or Array.map)?
Try Array.zip and then map:
val list = a.zip(b)
.map { SomeClass(it.first, it.second) }
or if you like it more:
val list = a.zip(b)
.map { (a, b) -> SomeClass(a, b) }
Note that if both arrays differ in size, the additional values are ignored. Note also that this will create intermediate Pairs (which is the default transformation function of zip). Even though I like the explicit map more, #hotkeys solution regarding the overloaded method is more appropriate (you spare that hidden Pair-transformation):
val list = a.zip(b) { a, b -> SomeClass(a, b) }
And where the overloaded method probably shines, is when using references instead:
a.zip(b, ::SomeClass)
Which will work as long as you have a constructor matching the zipped arguments and doesn't work out of the box for the Pair (yet?).
Improving on #Roland's answer, you can use the zip overload that accepts a two-argument function for mapping the pairs immediately:
val result = a.zip(b) { x, y -> SomeClass(x, y) }
You can write some custom fun like this:
inline fun <T, R, E, V> Iterable<T>.zipThree(other1: Iterable<R>, other2: Iterable<E>, transform: (T, R, E) -> V): List<V> {
val first = iterator()
val second = other1.iterator()
val third = other2.iterator()
val list = ArrayList<V>()
while (first.hasNext() && second.hasNext()) {
list.add(transform(first.next(), second.next(), third.next()))
}
return list
}
And use this transform for getting List
val strings = listOf("1", "2")
val ints = listOf(1, 2)
val boolean = listOf(true, false)
val listYoutObjects = strings.zipThree(ints, boolean) { one, two, three -> YouObject(one, two, three) }
My initial problem was to convert a tuple of different types to a string. In Python, this would be something like:
>> a = ( 1.3, 1, 'c' )
>> b = map( lambda x: str(x), a )
['1.3', '1', 'c']
>> " ".join(b)
'1.3 1 c"
Yet, Rust doesn't support map on tuples -- only on vector-like structures. Obviously, this is due to being able to pack different types into a tuple and the lack of function overloading. Also, I couldn't find a way to get the tuple length at runtime. So, I guess, a macro would be needed to do the conversion.
As a start, I tried to match the head of an tuple, something like:
// doesn't work
match some_tuple {
(a, ..) => println!("{}", a),
_ => ()
}
So, my question:
Is it possible, using library functions, to convert a tuple to a string, specifying an arbitrary separator?
How to write a macro to be able to map functions to arbitrary sized tuples?
Here's an overly-clever macro solution:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {};
( ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )* ) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where
$typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts: &[&::std::fmt::Display] = &[&self.$idx, $( &self.$nidx ),*];
parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().join(sep)
}
}
tuple_impls!($( ($nidx => $ntyp), )*);
};
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
fn main() {
let a = (1.3, 1, 'c');
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}
The basic idea is that we can take a tuple and unpack it into a &[&fmt::Display]. Once we have that, it's straight-forward to map each item into a string and then combine them all with a separator. Here's what that would look like on its own:
fn main() {
let tup = (1.3, 1, 'c');
let slice: &[&::std::fmt::Display] = &[&tup.0, &tup.1, &tup.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
let joined = parts.join(", ");
println!("{}", joined);
}
The next step would be to create a trait and implement it for the specific case:
trait TupleJoin {
fn tuple_join(&self, sep: &str) -> String;
}
impl<A, B, C> TupleJoin for (A, B, C)
where
A: ::std::fmt::Display,
B: ::std::fmt::Display,
C: ::std::fmt::Display,
{
fn tuple_join(&self, sep: &str) -> String {
let slice: &[&::std::fmt::Display] = &[&self.0, &self.1, &self.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
parts.join(sep)
}
}
fn main() {
let tup = (1.3, 1, 'c');
println!("{}", tup.tuple_join(", "));
}
This only implements our trait for a specific size of tuple, which may be fine for certain cases, but certainly isn't cool yet. The standard library uses some macros to reduce the drudgery of the copy-and-paste that you would need to do to get more sizes. I decided to be even lazier and reduce the copy-and-paste of that solution!
Instead of clearly and explicitly listing out each size of tuple and the corresponding index/generic name, I made my macro recursive. That way, I only have to list it out once, and all the smaller sizes are just part of the recursive call. Unfortunately, I couldn't figure out how to make it go in a forwards direction, so I just flipped everything around and went backwards. This means there's a small inefficiency in that we have to use a reverse iterator, but that should overall be a small price to pay.
The other answer helped me a lot because it clearly illustrated the power of Rust's simple macro system once you make use of recursion and pattern matching.
I've managed to make a few crude improvements (might be able to make the patterns a bit simpler, but it's rather tricky) on top of it so that the tuple accessor->type list is reversed by the macro at compile time before expansion into the trait implementation so that we no longer need to have a .rev() call at runtime, thus making it more efficient:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {}; // no more
(($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
/*
* Invoke recursive reversal of list that ends in the macro expansion implementation
* of the reversed list
*/
tuple_impls!([($idx, $typ);] $( ($nidx => $ntyp), )*);
tuple_impls!($( ($nidx => $ntyp), )*); // invoke macro on tail
};
/*
* ([accumulatedList], listToReverse); recursively calls tuple_impls until the list to reverse
+ is empty (see next pattern)
*/
([$(($accIdx: tt, $accTyp: ident);)+] ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
tuple_impls!([($idx, $typ); $(($accIdx, $accTyp); )*] $( ($nidx => $ntyp), ) *);
};
// Finally expand into the implementation
([($idx:tt, $typ:ident); $( ($nidx:tt, $ntyp:ident); )*]) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where $typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts = vec![self.$idx.to_string(), $( self.$nidx.to_string() ),*];
parts.join(sep)
}
}
}
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
#[test]
fn test_join_tuple() {
let a = ( 1.3, 1, 'c' );
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}