Skip to content

Playing

Update:
No sooner had I posted this than the excellent Tim Yates () contacted me to show me how a true master does things, viz:

Another Groovy option might be to mark the `Statement` class with `@groovy.transform.Canonical` and then do:

text.split( /<(\d+)>/ )
    .collect { it.split( ',' ) }
    .findAll { it.size() == 9 }
    .collect { new Statement( *it ) }
    .each { println it.dump() }

Beautiful! I love this solution! Tim’s work is always informative.

I admit that each time I wrote the ‘if’ statements I said to myself “there simply has to be a better way!” Trouble is, I couldn’t see through to that better way. I doff my cap to Tim for teaching me.

Similarly munging the other examples here is Left As An Exercise For The Reader (I’m too lazy, in other words).

And now, back to the original post…

This post on the Grails user list got me thinking. So I thought and then I played, and played…and played.

Below are Groovy, Ruby, Haskell and Scala versions of the same program.

Groovy Version

class Statement {
  String item1
  String item2
  String item3
  String item4
  String item5
  String item6
  String item7
  String item8
  String item9
}

def text ='<1>TCODE<2>14044,20110331,0,GBP,0,14044,20110331,52,TT<3>14044,20110331,0,GBP,0,14044,20110331,401,MM<4>14044,20110331,0,GBP,0,14044,20110331,403,MM<5>14044,20110331,0,GBP,0,14044,20110331,701,SW<6>14044,20110331,0,GBP,0,14044,20110331,701,SW<7>14044,20110331,0,GBP,0,14044,20110331,701,SW<8>14044,20110331,0,GBP,0,14044,20110331,701,SW'

def matcher = text =~ /<(\d+)>([^<]*)/
matcher.each { it, csv = it[2] ->
  def split = csv.split(',', -1)

  if (split.size() == 9) {
    def itemMap = split.inject([:]) { map, o -> map << [("item${map.size() + 1}".toString()): o] }

    def s = new Statement(itemMap)

    // do 'something' with the new Statement
    println s.dump()
    }
}

And the result:

<Statement@1a260305 item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=52 item9=TT>
<Statement@62e7f06c item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=401 item9=MM>
<Statement@6959752e item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=403 item9=MM>
<Statement@701c550a item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=701 item9=SW>
<Statement@54133d06 item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=701 item9=SW>
<Statement@3b0b8009 item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=701 item9=SW>
<Statement@7002ed27 item1=14044 item2=20110331 item3=0 item4=GBP item5=0 item6=14044 item7=20110331 item8=701 item9=SW>

Ruby Version

class Statement < Struct.new(:item1, :item2, :item3, :item4, :item5, :item6, :item7, :item8, :item9)
end

text = '<1>TCODE<2>14044,20110331,0,GBP,0,14044,20110331,52,TT<3>14044,20110331,0,GBP,0,14044,20110331,401,MM<4>14044,20110331,0,GBP,0,14044,20110331,403,MM<5>14044,20110331,0,GBP,0,14044,20110331,701,SW<6>14044,20110331,0,GBP,0,14044,20110331,701,SW<7>14044,20110331,0,GBP,0,14044,20110331,701,SW<8>14044,20110331,0,GBP,0,14044,20110331,701,SW'

matches = text.enum_for(:scan, /<(\d+)>([^<]*)/).map do
  Regexp.last_match
end
matches.each do |it, csv = (it[2])|
  split = csv.split(',')

  if split.size == 9
    itemHash = split.inject({}) do |h, o|
      k = "item#{h.size + 1}"
      h[k] = o
      h
    end

    s = Statement.new(itemHash)

    # do 'something' with the new Statement
    puts s.to_s
  end
end

Scala Version

import scala.util.matching.Regex.MatchIterator

case class Statement(item1: String, item2: String, item3: String,
                     item4: String, item5: String, item6: String,
                     item7: String, item8: String, item9: String)

object ScalaSplit {
  private val TEXT = "<1>TCODE<2>14044,20110331,0,GBP,0,14044,20110331,52,TT<3>14044,20110331,0,GBP,0,14044,20110331,401,MM<4>14044,20110331,0,GBP,0,14044,20110331,403,MM<5>14044,20110331,0,GBP,0,14044,20110331,701,SW<6>14044,20110331,0,GBP,0,14044,20110331,701,SW<7>14044,20110331,0,GBP,0,14044,20110331,701,SW<8>14044,20110331,0,GBP,0,14044,20110331,701,SW"
  private val PAT = """<[^<>]+>([^<]*)""".r

  private def stripTag(s: String): String = s.dropWhile(_ != '>').drop(1)

  def main(args: Array[String]) {

    val matches: MatchIterator = PAT findAllIn TEXT
    matches foreach { m =>
        val csv: Array[String] = stripTag(m).split(',')
        if (csv.length == 9) {
          val s: Statement = Statement(csv(0), csv(1), csv(2), csv(3), csv(4), csv(5), csv(6), csv(7), csv(8))

          // do 'something' with the new Statement
          println(s)
        }
    }
  }
}

I couldn’t find any way to spread parameters to arguments, so this is a bit long-winded.

Haskell Version

import Text.Regex
import Text.Regex.Posix
import Data.Char
import Data.List
import Data.Maybe
import Control.Monad

data Something = Something { item1 :: String,
                             item2 :: String,
                             item3 :: String,
                             item4 :: String,
                             item5 :: String,
                             item6 :: String,
                             item7 :: String,
                             item8 :: String,
                             item9 :: String
                             } deriving Show

stripTag s = fromJust $ stripPrefix ">" $ dropWhile (/= '>') s

eachMatch m = do
    let csv = stripTag m
    let matches = splitRegex (mkRegex ",") csv
    when ((length matches) == 9) $ do
        let st = Something { item1 = matches!!0, item2 = matches!!1, item3 = matches!!2,
                             item4 = matches!!3, item5 = matches!!4, item6 = matches!!5,
                             item7 = matches!!6, item8 = matches!!7, item9 = matches!!8
                             }
        putStrLn $ show st

main = do
    let text = "<1>TCODE<2>14044,20110331,0,GBP,0,14044,20110331,52,TT<3>14044,20110331,0,GBP,0,14044,20110331,401,MM<4>14044,20110331,0,GBP,0,14044,20110331,403,MM<5>14044,20110331,0,GBP,0,14044,20110331,701,SW<6>14044,20110331,0,GBP,0,14044,20110331,701,SW<7>14044,20110331,0,GBP,0,14044,20110331,701,SW<8>14044,20110331,0,GBP,0,14044,20110331,701,SW"
    let pat = "<[^<>]+>([^<]*)"
    let matches = getAllTextMatches (text =~ pat :: AllTextMatches [] String)
    mapM_ eachMatch matches

It’s interesting that all the versions look pretty much the same. There exist two possible reasons, as far as I can see. Either (1) since the problem is the same, the solution will pretty much be the same, or (2) if all you have is a hammer, everything looks like a nail.

Still, a fun activity that kept me occupied!

Am I cool yet?

Tags: , , , ,

C, Java Enterprise Edition, JEE, J2EE, JBoss, Application Server, Glassfish, JavaServer Pages, JSP, Tag Libraries, Servlets, Enterprise Java Beans, EJB, Java Messaging Service JMS, BEA Weblogic, JBoss, Application Servers, Spring Framework, Groovy, Grails, Griffon, GPars, GAnt, Spock, Gradle, Seam, Open Source, Service Oriented Architectures, SOA, Java 2 Standard Edition, J2SE, Eclipse, Intellij, Oracle Service Bus, OSB