How do I generically traverse an array in ReScript? - arrays

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)
}

Related

Find an object in Swift Array which has the same object multiple times in it

I'm looking for an alternative to - [NSArray indexOfObject:inRange:] for Swift' Arrays. What I'm trying to do is essentially following:
NSArray *A = #[#1, #3, #5, #4, #3];
NSUInteger cursor = 0;
while (YES) {
NSUInteger res = [A indexOfObject:#3 inRange:NSMakeRange(cursor, A.count - cursor)];
if (res == NSNotFound) {
break;
}
NSLog(#"found 3 at %#", #(res));
cursor = res + 1;
}
But I can't seem to find a way to start searching from a certain index with Swift Array. Of course I can make subarray, but is there better way to do this in Swift?
Array slicing is the natural way to go about this:
let numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]
let range = 4...6
let desiredNumber = 2
let indexOfDesiredNumberInRange = numbers[range].index(of: desiredNumber)
print(indexOfDesiredNumberInRange as Any) // => Optional(4)
An since ArraySlice is just a view into the same backing storage of the soruce Array, there's no wasteful copying being done
I assume you are trying to get all the indexes of the object you are looking for. This is a nicer way to do it
let arr = [1,2,3,4,3,5,6,7,8]
for (num, index) in test.enumerated() {
if num == 3 {
print("found 3 at \(index)")
}
}
Another way of giving you the indexes:
arr.enumerated().filter { (_, num) -> Bool in
num == 3
}.map { $0.offset }

Convert [type_a] to [type_b]

I've actually googled this extensively, within stackoverflow and elsewhere.
Most questions are about [UInt8] to String or [UInt8] to type_a (not array).
To clarify, I'd like to take an array of type_a. Get its pointer and tell swift to treat the next n iterations of type_b (size_of) as array of type_b.
I've tried variations of https://stackoverflow.com/a/26954091/5276890 which didn't work. A comment there led me to https://stackoverflow.com/a/42255468/5276890.
withMemoryRebound seems like the right way but I couldn't find the right invocation.
Here's a sample code of what I'm doing instead to convert [UInt8] to [UInt32.bigEndian], both to clarify and in case it's useful (not likely)
var intData = [UInt32]()
let M = UInt32(256*256*256)
var m = M
var bigE:UInt32 = 0
for i in 0..<data.count {
bigE += UInt32(data[i]) * m
if m == 1 {
intData.append(bigE)
bigE = 0
m = M
} else {
m = m/256
}
}
<disclaimer+rant>
I have to admit I never could figure out the whole closures+withUnsafe* syntax and mostly used patterns online and modified them. I'd spend the time learning this, just as soon as the language authors decide and settle down on one specific syntax :(
</disclaimer+rant>
Use withUnsafeBufferPointer to get a pointer to the element
storage of the source array.
Use withMemoryRebound to "reinterpret" that pointer as pointing
to elements of the target type.
Use Array(UnsafeBufferPointer(...) to create an array of the
target type.
Example:
let source: [UInt16] = [1, 2, 3, 4]
let dest = source.withUnsafeBufferPointer {
$0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 2) {
Array(UnsafeBufferPointer(start: $0, count: 2))
}
}
print(dest) // [131073, 262147]
Or as a generic function:
func convertArray<S, T>(_ source: [S], to: T.Type) -> [T] {
let count = source.count * MemoryLayout<S>.stride/MemoryLayout<T>.stride
return source.withUnsafeBufferPointer {
$0.baseAddress!.withMemoryRebound(to: T.self, capacity: count) {
Array(UnsafeBufferPointer(start: $0, count: count))
}
}
}
Example:
let source: [UInt16] = [1, 2, 3, 4]
let dest = convertArray(source, to: UInt32.self)
print(dest) // [131073, 262147]
If you only need a (temporary) view on the array storage interpreted
in another type then you can avoid the Array creation
and use the UnsafeBufferPointer (which is a Collection and
has array-like methods) without copying the data:
source.withUnsafeBufferPointer {
$0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 2) {
let u32bufptr = UnsafeBufferPointer(start: $0, count: 2)
// ... Operate on u32bufptr ...
for elem in u32bufptr { print(elem) }
}
}

Converting a Swift array of Ints into an array of its running subtotals [duplicate]

I'd like a function runningSum on an array of numbers a (or any ordered collection of addable things) that returns an array of the same length where each element i is the sum of all elements in A up to an including i.
Examples:
runningSum([1,1,1,1,1,1]) -> [1,2,3,4,5,6]
runningSum([2,2,2,2,2,2]) -> [2,4,6,8,10,12]
runningSum([1,0,1,0,1,0]) -> [1,1,2,2,3,3]
runningSum([0,1,0,1,0,1]) -> [0,1,1,2,2,3]
I can do this with a for loop, or whatever. Is there a more functional option? It's a little like a reduce, except that it builds a result array that has all the intermediate values.
Even more general would be to have a function that takes any sequence and provides a sequence that's the running total of the input sequence.
The general combinator you're looking for is often called scan, and can be defined (like all higher-order functions on lists) in terms of reduce:
extension Array {
func scan<T>(initial: T, _ f: (T, Element) -> T) -> [T] {
return self.reduce([initial], combine: { (listSoFar: [T], next: Element) -> [T] in
// because we seeded it with a non-empty
// list, it's easy to prove inductively
// that this unwrapping can't fail
let lastElement = listSoFar.last!
return listSoFar + [f(lastElement, next)]
})
}
}
(But I would suggest that that's not a very good implementation.)
This is a very useful general function, and it's a shame that it's not included in the standard library.
You can then generate your cumulative sum by specializing the starting value and operation:
let cumSum = els.scan(0, +)
And you can omit the zero-length case rather simply:
let cumSumTail = els.scan(0, +).dropFirst()
Swift 4
The general sequence case
Citing the OP:
Even more general would be to have a function that takes any sequence
and provides a sequence that's the running total of the input
sequence.
Consider some arbitrary sequence (conforming to Sequence), say
var seq = 1... // 1, 2, 3, ... (CountablePartialRangeFrom)
To create another sequence which is the (lazy) running sum over seq, you can make use of the global sequence(state:next:) function:
var runningSumSequence =
sequence(state: (sum: 0, it: seq.makeIterator())) { state -> Int? in
if let val = state.it.next() {
defer { state.sum += val }
return val + state.sum
}
else { return nil }
}
// Consume and print accumulated values less than 100
while let accumulatedSum = runningSumSequence.next(),
accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91
// Consume and print next
print(runningSumSequence.next() ?? -1) // 120
// ...
If we'd like (for the joy of it), we could condense the closure to sequence(state:next:) above somewhat:
var runningSumSequence =
sequence(state: (sum: 0, it: seq.makeIterator())) {
(state: inout (sum: Int, it: AnyIterator<Int>)) -> Int? in
state.it.next().map { (state.sum + $0, state.sum += $0).0 }
}
However, type inference tends to break (still some open bugs, perhaps?) for these single-line returns of sequence(state:next:), forcing us to explicitly specify the type of state, hence the gritty ... in in the closure.
Alternatively: custom sequence accumulator
protocol Accumulatable {
static func +(lhs: Self, rhs: Self) -> Self
}
extension Int : Accumulatable {}
struct AccumulateSequence<T: Sequence>: Sequence, IteratorProtocol
where T.Element: Accumulatable {
var iterator: T.Iterator
var accumulatedValue: T.Element?
init(_ sequence: T) {
self.iterator = sequence.makeIterator()
}
mutating func next() -> T.Element? {
if let val = iterator.next() {
if accumulatedValue == nil {
accumulatedValue = val
}
else { defer { accumulatedValue = accumulatedValue! + val } }
return accumulatedValue
}
return nil
}
}
var accumulator = AccumulateSequence(1...)
// Consume and print accumulated values less than 100
while let accumulatedSum = accumulator.next(),
accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91
The specific array case: using reduce(into:_:)
As of Swift 4, we can use reduce(into:_:) to accumulate the running sum into an array.
let runningSum = arr
.reduce(into: []) { $0.append(($0.last ?? 0) + $1) }
// [2, 4, 6, 8, 10, 12]
By using reduce(into:_:), the [Int] accumulator will not be copied in subsequent reduce iterations; citing the Language reference:
This method is preferred over reduce(_:_:) for efficiency when the
result is a copy-on-write type, for example an Array or a
Dictionary.
See also the implementation of reduce(into:_:), noting that the accumulator is provided as an inout parameter to the supplied closure.
However, each iteration will still result in an append(_:) call on the accumulator array; amortized O(1) averaged over many invocations, but still an arguably unnecessary overhead here as we know the final size of the accumulator.
Because arrays increase their allocated capacity using an exponential
strategy, appending a single element to an array is an O(1) operation
when averaged over many calls to the append(_:) method. When an array
has additional capacity and is not sharing its storage with another
instance, appending an element is O(1). When an array needs to
reallocate storage before appending or its storage is shared with
another copy, appending is O(n), where n is the length of the array.
Thus, knowing the final size of the accumulator, we could explicitly reserve such a capacity for it using reserveCapacity(_:) (as is done e.g. for the native implementation of map(_:))
let runningSum = arr
.reduce(into: [Int]()) { (sums, element) in
if let sum = sums.last {
sums.append(sum + element)
}
else {
sums.reserveCapacity(arr.count)
sums.append(element)
}
} // [2, 4, 6, 8, 10, 12]
For the joy of it, condensed:
let runningSum = arr
.reduce(into: []) {
$0.append(($0.last ?? ($0.reserveCapacity(arr.count), 0).1) + $1)
} // [2, 4, 6, 8, 10, 12]
Swift 3: Using enumerated() for subsequent calls to reduce
Another Swift 3 alternative (with an overhead ...) is using enumerated().map in combination with reduce within each element mapping:
func runningSum(_ arr: [Int]) -> [Int] {
return arr.enumerated().map { arr.prefix($0).reduce($1, +) }
} /* thanks #Hamish for improvement! */
let arr = [2, 2, 2, 2, 2, 2]
print(runningSum(arr)) // [2, 4, 6, 8, 10, 12]
The upside is you wont have to use an array as the collector in a single reduce (instead repeatedly calling reduce).
Just for fun: The running sum as a one-liner:
let arr = [1, 2, 3, 4]
let rs = arr.map({ () -> (Int) -> Int in var s = 0; return { (s += $0, s).1 } }())
print(rs) // [1, 3, 6, 10]
It does the same as the (updated) code in JAL's answer, in particular,
no intermediate arrays are generated.
The sum variable is captured in an immediately-evaluated closure returning the transformation.
If you just want it to work for Int, you can use this:
func runningSum(array: [Int]) -> [Int] {
return array.reduce([], combine: { (sums, element) in
return sums + [element + (sums.last ?? 0)]
})
}
If you want it to be generic over the element type, you have to do a lot of extra work declaring the various number types to conform to a custom protocol that provides a zero element, and (if you want it generic over both floating point and integer types) an addition operation, because Swift doesn't do that already. (A future version of Swift may fix this problem.)
Assuming an array of Ints, sounds like you can use map to manipulate the input:
let arr = [0,1,0,1,0,1]
var sum = 0
let val = arr.map { (sum += $0, sum).1 }
print(val) // "[0, 1, 1, 2, 2, 3]\n"
I'll keep working on a solution that doesn't use an external variable.
I thought I'd be cool to extend Sequence with a generic scan function as is suggested in the great first answer.
Given this extension, you can get the running sum of an array like this: [1,2,3].scan(0, +)
But you can also get other interesting things…
Running product: array.scan(1, *)
Running max: array.scan(Int.min, max)
Running min: array.scan(Int.max, min)
Because the implementation is a function on Sequence and returns a Sequence, you can chain it together with other sequence functions. It is efficient, having linear running time.
Here's the extension…
extension Sequence {
func scan<Result>(_ initialResult: Result, _ nextPartialResult: #escaping (Result, Self.Element) -> Result) -> ScanSequence<Self, Result> {
return ScanSequence(initialResult: initialResult, underlying: self, combine: nextPartialResult)
}
}
struct ScanSequence<Underlying: Sequence, Result>: Sequence {
let initialResult: Result
let underlying: Underlying
let combine: (Result, Underlying.Element) -> Result
typealias Iterator = ScanIterator<Underlying.Iterator, Result>
func makeIterator() -> Iterator {
return ScanIterator(previousResult: initialResult, underlying: underlying.makeIterator(), combine: combine)
}
var underestimatedCount: Int {
return underlying.underestimatedCount
}
}
struct ScanIterator<Underlying: IteratorProtocol, Result>: IteratorProtocol {
var previousResult: Result
var underlying: Underlying
let combine: (Result, Underlying.Element) -> Result
mutating func next() -> Result? {
guard let nextUnderlying = underlying.next() else {
return nil
}
previousResult = combine(previousResult, nextUnderlying)
return previousResult
}
}
One solution using reduce:
func runningSum(array: [Int]) -> [Int] {
return array.reduce([], combine: { (result: [Int], item: Int) -> [Int] in
if result.isEmpty {
return [item] //first item, just take the value
}
// otherwise take the previous value and append the new item
return result + [result.last! + item]
})
}
I'm very late to this party. The other answers have good explanations. But none of them have provided the initial result, in a generic way. This implementation is useful to me.
public extension Sequence {
/// A sequence of the partial results that `reduce` would employ.
func scan<Result>(
_ initialResult: Result,
_ nextPartialResult: #escaping (Result, Element) -> Result
) -> AnySequence<Result> {
var iterator = makeIterator()
return .init(
sequence(first: initialResult) { partialResult in
iterator.next().map {
nextPartialResult(partialResult, $0)
}
}
)
}
}
extension Sequence where Element: AdditiveArithmetic & ExpressibleByIntegerLiteral {
var runningSum: AnySequence<Element> { scan(0, +).dropFirst() }
}

`break` and `continue` in `forEach` in Kotlin

Kotlin has very nice iterating functions, like forEach or repeat, but I am not able to make the break and continue operators work with them (both local and non-local):
repeat(5) {
break
}
(1..5).forEach {
continue#forEach
}
The goal is to mimic usual loops with the functional syntax as close as it might be. It was definitely possible in some older versions of Kotlin, but I struggle to reproduce the syntax.
The problem might be a bug with labels (M12), but I think that the first example should work anyway.
It seems to me that I've read somewhere about a special trick/annotation, but I could not find any reference on the subject. Might look like the following:
public inline fun repeat(times: Int, #loop body: (Int) -> Unit) {
for (index in 0..times - 1) {
body(index)
}
}
This will print 1 to 5. The return#forEach acts like the keyword continue in Java, which means in this case, it still executes every loop but skips to the next iteration if the value is greater than 5.
fun main(args: Array<String>) {
val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
nums.forEach {
if (it > 5) return#forEach
println(it)
}
}
This will print 1 to 10 but skips 5.
fun main(args: Array<String>) {
val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
nums.forEach {
if (it == 5) return#forEach
println(it)
}
}
This will print 1 to 4, and break when reaching 5.
fun main(args: Array<String>) {
val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
run breaking# {
nums.forEach {
if (it == 5) return#breaking
println(it)
}
}
}
Link to code snippet from ashuges.
Edit:
According to Kotlin's documentation, it is possible to simulate continue using annotations.
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit# {
if (it == 3) return#lit // local return to the caller of the lambda, i.e. the forEach loop
print(it)
}
print(" done with explicit label")
}
If you want to simulate a break, just add a run block
fun foo() {
run lit# {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return#lit // local return to the caller of the lambda, i.e. the forEach loop
print(it)
}
print(" done with explicit label")
}
}
Original Answer:
Since you supply a (Int) -> Unit, you can't break from it, since the compiler do not know that it is used in a loop.
You have few options:
Use a regular for loop:
for (index in 0 until times) {
// your code here
}
If the loop is the last code in the method
you can use return to get out of the method (or return value if it is not unit method).
Use a method
Create a custom repeat method method that returns Boolean for continuing.
public inline fun repeatUntil(times: Int, body: (Int) -> Boolean) {
for (index in 0 until times) {
if (!body(index)) break
}
}
A break can be achieved using:
//Will produce "12 done with nested loop"
//Using "run" and a tag will prevent the loop from running again.
//Using return#forEach if I>=3 may look simpler, but it will keep running the loop and checking if i>=3 for values >=3 which is a waste of time.
fun foo() {
run loop#{
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return#loop // non-local return from the lambda passed to run
print(it)
}
}
print(" done with nested loop")
}
And a continue can be achieved with:
//Will produce: "1245 done with implicit label"
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return#forEach // local return to the caller of the lambda, i.e. the forEach loop
print(it)
}
print(" done with implicit label")
}
As anyone here recommends... read the docs :P
https://kotlinlang.org/docs/reference/returns.html#return-at-labels
EDIT:
While the main question asks about forEach, it's important to consider the the good old "for". Using Kotlin doesn't mean we need to use forEach all the time. Using the good old "for" is perfectly ok, and sometimes even more expressive and concise than forEach:
fun foo() {
for(x in listOf(1, 2, 3, 4, 5){
if (x == 3) break //or continue
print(x)
}
print("done with the good old for")
}
As the Kotlin documentation says, using return is the way to go. Good thing about Kotlin is that if you have nested functions, you can use labels to explicitly write where your return is from:
Function Scope Return
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
/** Non-local return directly to the caller of foo(). */
if (it == 3) return
print(it)
}
println("this point is unreachable")
}
Local Return
It doesn't stop going through forEach loop (it's like a continue in for loop).
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit#{
/** Local return to the caller of the lambda, i.e. the forEach loop. */
if (it == 3) return#lit
print(it)
}
print(" done with explicit label")
}
Check out the documentation, it's really good :)
You can use return from lambda expression which mimics a continue or break depending on your usage.
This is covered in the related question: How do I do a "break" or "continue" when in a functional loop within Kotlin?
continue type behaviour in forEach
list.forEach { item -> // here forEach give you data item and you can use it
if () {
// your code
return#forEach // Same as continue
}
// your code
}
for break type behaviour you have to use for in until or for in as per the list is Nullable or Non-Nullable
For Nullable list:
for (index in 0 until list.size) {
val item = list[index] // you can use data item now
if () {
// your code
break
}
// your code
}
For Non-Nullable list:
for (item in list) { // data item will available right away
if () {
// your code
break
}
// your code
}
I have the perfect solution for this (:
list.apply{ forEach{ item ->
if (willContinue(item)) return#forEach
if (willBreak(item)) return#apply
}}
Break statement for nested loops forEach():
listOf("a", "b", "c").forEach find#{ i ->
listOf("b", "d").forEach { j ->
if (i == j) return#find
println("i = $i, j = $j")
}
}
Result:
i = a, j = b
i = a, j = d
i = c, j = b
i = c, j = d
Continue statement with anonymous function:
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return
print("$value ")
})
Result:
1 2 4 5
Maybe change forEach to this:
for (it in myList) {
if (condition) {
doSomething()
} else {
break // or continue
}
}
It works for HashMap's:
for (it in myMap) {
val k = it.key
val v = it.value
if (condition) {
doSomething()
} else {
break // or continue
}
}
If the condition depends on the outcome of a previous element in the list, you can use sequence and takeWhile to execute depth-first lazily.
sequenceOf(1, 2, 3, 4, 5).map { i ->
println("i = ${i}")
i < 3
}.takeWhile { success ->
println("success = ${success}")
success
}.toList()
will print
i = 1
success = true
i = 2
success = true
i = 3
success = false
You need the terminal toList() in the end to execute the sequence.
More details: https://kotlinlang.org/docs/sequences.html#sequence
fun part2(ops: List<Int>): Int = ops.asSequence()
.scan(0) { acc, v -> acc + v }
.indexOf(-1)
If you can afford to turn a collection into a sequence, normally the cost is trivial, then you should be able to take advantage of the deferred feature.
You might already notice asSequence in the above. It's here for saving us going over the entire list. Right after we have a match via indexOf, it'll stop. Bingo! Saving us write a while here.
as in Part 2 of medium article.

How to iterate or map over tuples?

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);
}

Resources