2

In languages with support for unary addition, and in situations where a long list of operations is performed against sequential items in an array-like structure I might create a simple counter "int counter= 0;" and do the following:

someOperation(array[counter++]);
nextOperation(array[counter++]);
subsequentOperation(array[counter++]);
..  etc

What is an idiomatic way in scala to achieve similar behavior - i.e. avoid requiring hard-coded input array indices? Here is a specific example: a simple record parser method that converts a tab-separated call detail to a Call object. Not knowing any better way I did an ugly thing of putting in an AtomicInteger. But what is an acceptable way to do this?

Note: we can not simply do a collective operation here because some of the columns require ".toInt" processing and others do not.

  def parseStringToCall(text: String) = {
    val toks = text.split('\t')
    val ix = new AtomicInteger(0)
    new CallUpdate(
      toks(ix.getAndIncrement),  // callDate
      toks(ix.getAndIncrement),  // calledNumber
      toks(ix.getAndIncrement),  // callingNumbe
      toks(ix.getAndIncrement),  // cellTowersVi
      toks(ix.getAndIncrement),  // direction
      toks(ix.getAndIncrement),  // iMSI
      toks(ix.getAndIncrement),  // manufacturer
      toks(ix.getAndIncrement),  // phoneType
      toks(ix.getAndIncrement),  // reasonforDro
      toks(ix.getAndIncrement).toInt,  // weeknum
      toks(ix.getAndIncrement),  // firstCellTow
      toks(ix.getAndIncrement),  // lastCellTowe
      toks(ix.getAndIncrement).toInt,  // calls
      toks(ix.getAndIncrement),  // distinctCell
      toks(ix.getAndIncrement),  // droppedCall
      toks(ix.getAndIncrement).toInt,  // handovers
      toks(ix.getAndIncrement).toInt,  // setupTime
      toks(ix.getAndIncrement).toInt  // talkTime
    )
WestCoastProjects
  • 58,982
  • 91
  • 316
  • 560

3 Answers3

3

Maybe you could not use index to access an array, but use pattern matching.

Given you have your call update defined as a case class (I've omitted some of the fields)

case class CallUpdate(
  callDate: String, 
  calledNumber: String, 
  callingNumber: String, 
  cellTowersVi: String,
  direction: String, 
  iMSI: String, 
  manufacturer: String, 
  phoneType: String,
  reasonforDro: String, 
  weekNum: Int, 
  firstCellTow: String, 
  calls: Int
)

You could write your parseStringToCall like this

def parseStringToCall(text: String) = text.split('\t') match {
  case Array (
    callDate, calledNumber, callingNumber, cellTowersVi, direction, iMSI, manufacturer, 
    phoneType, reasonforDro, weekNumString, firstCellTow, callsString
  ) => CallUpdate(callDate, calledNumber, callingNumber, cellTowersVi, direction, iMSI, manufacturer,     
                  phoneType, reasonforDro, weekNumString.toInt, firstCellTow, callsString.toInt)
}

Additionally using this approach, you could handle lines, which do not match, by adding wildcard case, and return None for example.

PS. Maybe you could think of splitting your CallUpdate into smaller, specialized objects.

lpiepiora
  • 13,659
  • 1
  • 35
  • 47
3

You're approaching the problem imperatively. Idiomatic Scala is more about functional programming. You have to get used to treating functions as values and exploit that power.

Your problem can be solved functionally like this:

toks
  // Get a lazy wrapper around `toks`, so that all the subsequent
  // operations will be done in just a single traversal:
  .view 
  // Pair each item of `toks` up with an according operation:
  .zip(List(someOperation(_), nextOperation(_), subsequentOperation(_)))
  // Traverse thru those pairs, applying the operations:
  .foreach{ case (t, f) => f(t) }

It must be noted that the operations are expected to be of type String => Unit.

Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • +1 This is an insightful/helpful answer. I also liked the one from @ipiepiora. Will think a bit more before deciding which to award. – WestCoastProjects May 27 '14 at 20:01
  • how is this helping? how would you convert an Array[String] in the parameter list that needs to be passed to `callUpdate` ? And how would you pass it? – maasg May 27 '14 at 22:08
  • @maasg The OPs question was quite vague, so I showed how he can do side-effects. If you need to collect the results of those functions and pass it to a vararg function, you can do something like `callUpdate( toks.view.zip(..).map{case (t, f) => f(t)} : _* )` – Nikita Volkov May 27 '14 at 22:20
  • -1 this is plain wrong. Given `val op = String=>Unit = s=>()` (That's the type of 'someOperation(_)' as explained in the answer). What would be the type of: `Array("one","two","three").view.zip(List(op,op,op)).foreach{case (t,f) => f(t) }`. The answer is Unit. That's the result of a foreach(), and the operation will not modify in any way the original array. The only correct part of this answer is the first paragraph. – maasg May 28 '14 at 08:31
  • @maasg Before judging you probably should read more attentively. 1. Read my previous comment again and see that there's no `foreach` in it. 2. There was no question concerning modifying the array. – Nikita Volkov May 28 '14 at 08:55
  • 1
    @NikitaVolkov I think I did and I still don't see how the answer helps the OP or solves the question at hand. The usecase in the question is clear: Based on an array of String, how can s/he pass elements of such array to a fairly large method call that takes specific types as input. Would be nice if you could show how that can be done based on your approach. It's not clear, at least to me. – maasg May 28 '14 at 10:07
  • @maasg Well, read the question again then. The author asks for a way to iterate an array, while calling functions on its elements. Then he presents this large method call as an example of how he tried to achieve this. Then he accepts this answer... – Nikita Volkov May 28 '14 at 10:19
  • @NikitaVolkov I'm not trying to pick a fight, just trying to understand. – maasg May 28 '14 at 11:11
1

For the sake of future generations searching for the question on the title, this is a potential implementation of postIncrement in Scala:

class Counter(start:Int = 0) {
  var current = start
  def ++ : Int = {val x=current; current +=1 ; x}
}

repl> val i = new Counter
repl> i: Counter = Counter@2a01a9f2
repl> i++
repl> res26: Int = 0
repl> i++
repl> res27: Int = 1

Now, to address the specific usecase on the body of the question: i.e. iterating over an array without having to deal with the index, probably the most straightforward way to do that is using an iterator:

val args = Array("arguments","with","ints","23","46")
val iter = args.iterator
val res = CallUpdate(iter.next, iter.next, iter.next, iter.next.toInt, iter.next.toInt)

This solves the index access in a clean way.

Can we do better? Probably. We could address the type conversion in a more elegant way. For that we define a simple marker type and implicit conversions to avoid having to take care of those toInt calls. This can of course be extended to other types like Date, Long, ...

case class Param(value:String)
object Param {
    implicit def paramToString(p:Param):String = p.value
    implicit def paramToInt(p:Param):Int = p.value.toInt
}

and we can do something like:

val args = Array("arguments","with","ints","23","46")
val params = args.map(elem => Param(elem))
val iter = params.iterator
val res = CallUpdate(iter.next, iter.next, iter.next, iter.next, iter.next)

And if you want a mind-blowing solution, I recommend you this reading: applying-an-argument-list-to-curried-function

Community
  • 1
  • 1
maasg
  • 37,100
  • 11
  • 88
  • 115