Scalaのお勉強3

book.impress.co.jp
に取り組む。
今日は第4章、第5章。
解答を見まくった。(https://github.com/fpinscala/fpinscala)

object Main4{

  sealed trait Option[+A]{
    def map[B](f:A=>B):Option[B]={
      this match{
        case None=>None
        case Some(a) => Some(f(a))
      }
    }
    def flatMap[B](f:A=>Option[B]):Option[B]=map(f).getOrElse(None)
    def getOrElse[B>:A](default: => B):B={
      this match{
        case None=>default
        case Some(a)=>a
      }
    }
    def orElse[B>:A](ob : =>Option[B]):Option[B]=map(Some(_)).getOrElse(ob)
    def filter(f:A=>Boolean):Option[A]=this match{case Some(a) if f(a)=>this;case _ => None}

    
  }
  case class Some[+A](get:A) extends Option[A]
  case object None extends Option[Nothing]

  def mean(xs:Seq[Double]):Option[Double]= if (xs.isEmpty) None else Some(xs.sum/xs.length)

  def variance(xs:Seq[Double]):Option[Double]=mean(xs).flatMap(( m =>mean(xs.map( x =>math.pow(x-m,2)))))

  def lift[A,B](f: A => B):Option[A] => Option[B] = _.map(f)
  def insuaranceRateQuote(age:Int,numberOfSpeedingTickets:Int):Double={
    numberOfSpeedingTickets/age
  }
  def parseInsuaranceRateQuote(age:String,numberOfSpeedingTicket:String):Option[Double]={
    val optAge:Option[Int]=Try(age.toInt)
    val optTicket:Option[Int]=Try(numberOfSpeedingTicket.toInt)
    map2(optAge,optTicket)(insuaranceRateQuote)
  }

  def Try[A](a: =>A):Option[A]={
    try Some(a)
    catch { case e:Exception => None }
  }
  
  def map2[A,B,C](a:Option[A],b:Option[B])(f:(A,B)=>C):Option[C]={
    a.flatMap(aa=>b.map(bb=>f(aa,bb)))
  }
  def map3[A,B,C,D](a:Option[A],b:Option[B],c:Option[C])(f:(A,B,C)=>D):Option[D]={
    a.flatMap(aa=>b.flatMap(bb=>c.map(cc=>(f(aa,bb,cc)))))
  }

  def sequence[A](a:List[Option[A]]):Option[List[A]]={
    a match{
      case Nil=>Some(Nil)
      case x::xs=>x.flatMap(xx=>sequence(xs).map(xx::_))
    }
  }

  def traverse[A,B](a:List[A])(f:A=>Option[B]):Option[List[B]]={
    a match{
      case Nil=>Some(Nil)
      case x::xs=>f(x).flatMap(xx=>traverse(xs)(f).map(xx::_))
    }
  }

  sealed trait Either[+E,+A]{
    def map[B](f:A=>B):Either[E,B]={
      this match{
        case Right(x)=>Right(f(x))
        case Left(e)=>Left(e)
      }
    }
    def flatMap[EE >: E,B](f:A=>Either[EE,B]):Either[EE,B]={
      this match{
        case Right(x)=>f(x)
        case Left(e)=>Left(e)
      }
      
    }
    def orElse[EE >: E, B >: A](b: => Either[EE,B]):Either[EE,B]={
      this match{
        case _:Right[A]=>this
        case _:Left[E]=>b
      }
    }
    def map2[EE >: E,B,C](b:Either[EE,B])(f:(A,B)=>C):Either[EE,C]={
      flatMap(aa=>b.map(bb=>f(aa,bb)))
    }
  }
    

  def sequence_either[E,A](es:List[Either[E,A]]):Either[E,List[A]]={
    es match{
      case Nil=>Right(Nil)
      case x::xs=>x.flatMap(xx=>sequence_either(xs).map(xx::_))
    }
  }
  def traverse_either[E,A,B](as:List[A])(f:A=>Either[E,B]):Either[E,List[B]]={
    as match{
      case Nil=>Right(Nil)
      case x::xs=>f(x).flatMap(xx=>traverse_either(xs)(f).map(xx::_))
    }
    
  }
  case class Left[+E](value:E) extends Either[E,Nothing]
  case class Right[+A](value:A) extends Either[Nothing,A]

  def mean(xs:IndexedSeq[Double]):Either[String,Double]={
    if(xs.isEmpty)Left("mean of empty list")
    else Right(xs.sum/xs.length)
  }
  def Try_either[A](a: =>A):Either[Exception,A]={
    try Right(a)
    catch { case e:Exception => Left(e)}

}
object Main5{
  case object Empty extends Stream[Nothing]
  case class Cons[+A](h:()=>A,t:()=>Stream[A])extends Stream[A]

  trait Stream[+A]{
    def cons[A](hd: =>A,tl: =>Stream[A]):Stream[A]={
      lazy val head=hd
      lazy val tail=tl
      Cons(()=>head,()=>tail)
    }
    def empty[A]:Stream[A]=Empty
    
    def apply[A](as:A*):Stream[A]=if (as.isEmpty) empty else cons(as.head,apply(as.tail:_*))

    def headOption:Option[A]=this match{
      case Empty=>None
      case Cons(h,t)=>Some(h())
    }
    def toList:List[A]={
      this match{
        case Empty =>Nil
        case Cons(h,t)=>h()::t().toList
      }
    }
    def take(n:Int):Stream[A]={
      if (n<0) empty
      else{
        this match{
          case Empty => empty
          case Cons(h,t)=>cons(h(),t().take(n-1))
        }
      }
    }
    @annotation.tailrec
    private def drop(n:Int):Stream[A]={
      this match{
        case Cons(_,t) if n>0 => t().drop(n-1)
        case _ => this
      }
    }
    def exists(p:A=>Boolean):Boolean=this match{
      case Cons(h,t)=>p(h()) || t().exists(p)
      case _=>false
    }

    def foldRight[B](z: =>B)(f:(A, =>B) =>B):B=this match{
      case Cons(h,t)=>f(h(),t().foldRight(z)(f))
      case _=> z
    }
    def exists2(p:A=>Boolean):Boolean={
      foldRight(false)((a,b)=>p(a)||b)
    }

    def forAll(p:A=>Boolean):Boolean={
      foldRight(true)((a,b)=>p(a)&&b)
    }
    def takeWhile(p:A=>Boolean):Stream[A]={
      foldRight(empty[A])((a,b)=>if(p(a)) cons(a,b) else empty)
    }

    def map[B](f:A=>B):Stream[B]={
      foldRight(empty[B])((a,b)=>cons(f(a),b))
    }
    def filter(p:A=>Boolean):Stream[A]={
      foldRight(empty[A])((a,b)=>if(p(a)) cons(a,b) else b)
    }
    def append[B>:A](s: => Stream[B]):Stream[B]={
      foldRight(s)((a,b)=>cons(a,b))
    }
    def flatMap[B](f: A => Stream[B]):Stream[B]={
      foldRight(empty[B])((a,b)=>f(a).append(b))
    }
    def constant[A](a:A):Stream[A]=cons(a,constant(a))
    
    def from(n:Int):Stream[Int]=cons(n,from(n+1))

    def fibs:Stream[Int]={
      def go(f0:Int,f1:Int):Stream[Int]=cons(f0,go(f1,f0+f1))
      go(0,1)
    }
    def unfold[A,S](z:S)(f:S=>Option[(A,S)]):Stream[A]={
      def go(z1:S):Stream[A]={
        f(z1) match{
          case Some((a,s))=>cons(a,go(s))
          case None=>empty
        }
      }
      go(z)
    }
    def fibs2:Stream[Int]=unfold[Int,(Int,Int)]((0,1))({case (f0,f1)=>Some((f0,(f1,f0+f1)))})
    def from2(n:Int):Stream[Int]=unfold[Int,Int](n)(a=>Some((a+1,a+1)))
    def constant2[A](a:A):Stream[A]=unfold[A,A](a)(_=>Some((a,a)))
    def ones2:Stream[Int]=unfold[Int,Int](1)(_=>Some((1,1)))
    def tails:Stream[Stream[A]]={
      unfold(this){
        case Empty=>None
        case s=>Some((s,s drop 1))
      } append empty
    }
    def map2[B](f:A=>B):Stream[B]={
      unfold(this){
        case Cons(h,t)=>Some((f(h()),t()))
        case Empty => None
      }
    }
    def take2(n:Int):Stream[A]={
      unfold((this,n)){
        case (Cons(h,t),1) =>Some((h(),(empty,0)))
        case (Cons(h,t),n) if n>1 => Some((h(),(t(),n-1)))
        case _ =>None
      }
    }
    def takeWhile2(p:A=>Boolean):Stream[A]={
      unfold(this){
        case Cons(h,t) if p(h()) => Some((h(),t()))
        case _ => None
      }
    }
    def zipWith2[B,C](s2:Stream[B])(f:(A,B)=>C):Stream[C]={
      unfold((this,s2)){
        case (Cons(h1,t1),Cons(h2,t2)) => Some((f(h1(),h2()),(t1(),t2())))
        case _=>None
      }
    }





    
  }
  def main(args:Array[String]){
    println(Stream(1,2).take(2).toList)
    println(Stream(1,2,3,4,5,6,7,6,5,4,3,2,1).takeWhile(4>=).toList)
  }
}



Scalaのお勉強2

昨日と同じく
book.impress.co.jp
に取り組んだ。

今日は第3章。

object Main3{
  private def tail[A](list:List[A]):List[A]={
    list match{
      case Nil=>Nil
      case x::xs=>xs
    }
  }
  private def setHead[A](head:A,list:List[A]):List[A]=head::tail(list)
  @annotation.tailrec
  private def drop[A](l:List[A],n:Int):List[A]=if(n==0)l else drop(tail(l),n-1)
  @annotation.tailrec
  private def dropWhile[A](l:List[A])(f:A => Boolean):List[A]={
    l match{
      case Nil=>Nil
      case x::xs => if(f(x)) dropWhile(xs)(f) else x::xs
    }
  }
  private def init[A](l:List[A]):List[A]={
    l match{
      case Nil=>Nil
      case x::Nil=>Nil
      case x::xs=>x::init(xs)
    }
  }
  private def foldRight[A,B](as:List[A],z:B)(f:(A,B)=>B):B ={
    as match{
      case Nil=>z
      case x::xs => f(x,foldRight(xs,z)(f))
    }
  }
  private def length[A](as:List[A]):Int=foldRight(as,0)((_,b)=>b+1)
  @annotation.tailrec
  private def foldLeft[A,B](as:List[A],z:B)(f:(B,A)=>B):B={
    as match{
      case Nil=>z
      case x::xs=>foldLeft(xs,f(z,x))(f)
    }
  }
  
  private def sum(ints:List[Int])=foldLeft(ints,0)(_+_)
  private def product(ints:List[Int])=foldLeft(ints,1)(_*_)
  private def length2[A](as:List[A]):Int=foldLeft(as,0)((b,_)=>b+1)
  private def reverse[A](as:List[A])=foldLeft(as,List[A]())((bs,a)=>a::bs)
  private def append[A](a1:List[A],a2:List[A]):List[A]=foldRight(a1,a2)(_::_)
  private def plus1(ints:List[Int]):List[Int]=foldRight(ints,List[Int]())((a,bs)=>(a+1)::bs)
  private def ds2ss(doubles:List[Double]):List[String]=foldRight(doubles,List[String]())((a,bs)=>a.toString::bs)
  private def map[A,B](as:List[A])(f:A=>B):List[B]=foldRight(as,List[B]())((a,bs)=>f(a)::bs)
  private def filter[A](as:List[A])(f:A=>Boolean):List[A]=foldRight(as,List[A]())((a,bs)=>if (f(a)) a::bs else bs)
  private def flatMap[A,B](as:List[A])(f:A=>List[B]):List[B]=foldRight(map(as)(f),List[B]())(append(_,_))
  private def filter2[A](as:List[A])(f:A=>Boolean):List[A]=flatMap(as)(a=>if (f(a)) List(a) else List[A]())
  private def pluslists(a1:List[Int],a2:List[Int]):List[Int]={
    (a1,a2) match{
      case (_,Nil)=>Nil
      case (Nil,_)=>Nil
      case ((x::xs),(y::ys))=>(x+y)::pluslists(xs,ys)
    }
  }
  private def zipWith[A,B,C](as:List[A],bs:List[B])(f:(A,B)=>C):List[C]={
    (as,bs) match{
      case (_,Nil)=>Nil
      case (Nil,_)=>Nil
      case ((x::xs),(y::ys))=>(f(x,y))::zipWith(xs,ys)(f)
    }
  }

  
  sealed trait Tree[+A]
  case class Leaf[A](value:A) extends Tree[A]
  case class Branch[A](left:Tree[A],right:Tree[A]) extends Tree[A]

  private def size[A](tree:Tree[A]):Int={
    tree match{
      case Leaf(_)=>1
      case Branch(left,right)=>size(left)+size(right)+1
    }
  }
  private def maximum(tree:Tree[Int]):Int={
    tree match{
      case Leaf(x)=>x
      case Branch(left,right)=>maximum(left) max maximum(right)
    }
  }

  private def depth[A](tree:Tree[A]):Int={
    tree match{
      case Leaf(x)=>1
      case Branch(left,right)=>(depth(left) max depth(right))+1
    }
  }
  private def map[A,B](tree:Tree[A])(f:A=>B):Tree[B]={
    tree match{
      case Leaf(x)=>Leaf(f(x))
      case Branch(left,right)=>Branch(map(left)(f),map(right)(f))
    }
  }
  private def fold[A,B](tree:Tree[A])(l:A=>B)(b:(B,B)=>B):B={
    tree match{
      case Leaf(x)=>l(x)
      case Branch(left,right)=>b(fold(left)(l)(b),fold(right)(l)(b))
    }
  }

    
  
  def main(args:Array[String]){
    println(tail(List(1,2,3,4,5)))
    println(tail(List(1)))
    println(tail(Nil))
    println(setHead(100,List(1,2,3,4,5)))
    println(drop(List(1,2,3,4,5),3))
    println(dropWhile(List[Int](1,2,3,4,5,6))(3 >=))
    println(init(List(1,2,3,4,5,6,7,8)))
    println(length(List(1,2,3,4,5,6,7)))
    println(sum(List(1,2,3)))
    println(product(List(1,2,3)))
    println(length2(List(1,2,3)))
    println(reverse(List(3,2,1)))
    println(append(List(1,2,3),List(4,5,6)))
    println(plus1(List(0,1,2)))
    println(ds2ss(List(0.0,1,1,2.2)))
    println(map(List(0,1,2))(1+_))
    println(filter(List(0,1,2))(0<_))
    println(flatMap(List(1,2,3))(i=>List(i,i)))
    println(zipWith(List(1,2,3),List(4,5,6))(_+_))
    val branch=Branch(Branch(Leaf("a"),Leaf("b")),Branch(Leaf("c"),Leaf("d")))
    println(size(branch))
    println(depth(branch))
    println(map(branch)(_*3))
  }

}

間違ってConsじゃなくてListでやっちゃった。

Scalaのお勉強

書籍できちんとScalaを勉強することにした。

この本。
book.impress.co.jp


今日は二章まで読んだ。
第二章の練習問題の解答が以下(合っているかは知らぬ)

object Main2{
  private def fib(n:Int):Int={
    @annotation.tailrec
    def go(n:Int,a1:Int,a0:Int):Int=
      if (n==0) a1
      else go(n-1,a0,a1+a0)
    go(n,0,1)
  }

  private def isSorted[A](as:Array[A],ordered:(A,A)=>Boolean):Boolean={
    @annotation.tailrec
    def loop(n:Int):Boolean={
      if (n >= as.length-1) true
      else if (ordered(as(n),as(n+1))) loop(n+1)
      else false
    }

    loop(0)
  }
  private def curry[A,B,C](f:(A,B) => C):A => (B=>C)= (a:A)=>((b:B)=>f(a,b))
  private def uncurry[A,B,C](f:A=>B=>C):(A,B)=>C =(a:A,b:B)=>f(a)(b)
  private def compose[A,B,C](f:B=>C,g:A=>B):A=>C = (a:A)=>f(g(a))
  def main(args:Array[String]){
    println(fib(5))
    println(isSorted[Int](Array(1,2,3,4,5),(_ <= _)))
    println(isSorted[Int](Array(8,2,3,4,5),(_ <= _)))
    println(curry[Int,Int,Int]((_+_))(1)(2))
    println(compose[Int,Int,Int]((2*),(3+))(4))
  }
}


再帰が末尾呼び出しならばコンパイル時にループとして扱われ、再帰関数によってスタックをバカ食いされることはなくなることは始めて知った。

Scalaでデジタル回路シュミレータ

Scalaでデジタル回路シュミレータ(仮)を作った。
こんな感じ。
f:id:lilyext:20170311151928p:plainf:id:lilyext:20170311151933p:plainf:id:lilyext:20170311151947p:plain
ソースコードは以下の通り。

import scala.swing._
import scala.swing.event._
import java.awt.event._
import java.awt.image.BufferedImage
import scala.collection.JavaConverters._
import java.awt.Color
import scala.collection.mutable.Map

case class VanishPartEvent(part:Part) extends Event
case class DraggingWireEvent(part:Part,port:Port) extends Event
case class ReleasedHereEvent(part:Part,port:Port) extends Event

abstract class Port{
  val point:Point=null
}
abstract class Part extends Component{
  val PortMap:Map[Port,Wire]=Map.empty[Port,Wire]
  def connect(port:Port,wire:Wire){
    PortMap(port)=wire
  }
}
trait PartObject{
  val Ports:List[Port]=null
  val distance=5
  def portNearToPoint(point:Point):Option[Port]={
    val nearport=Ports.filter(port=>(port.point.x-point.x)*(port.point.x-point.x)+(port.point.y-point.y)*(port.point.y-point.y)<distance*distance)
    if(nearport.isEmpty)return None
    else Some(nearport(0))
  }
}
object OutputTerminal extends PartObject{
  override val Ports=List[Port](Port0)
  object Port0 extends Port{
    override val point=new Point(0,5)
    override val toString="Output"
  }
}
class OutputTerminal extends Part{
  private var signal=false
  preferredSize=new Dimension(10,10)
  listenTo(mouse.clicks)
  reactions+={
    case _=>
  }
  def setSignal(sig:Boolean){
    if(sig!=signal){
      signal=sig
      repaint
    }
  }
  override def paintComponent(g:Graphics2D){
    if(signal)g.setColor(Color.GREEN)
    else g.setColor(Color.RED)
    g.fillRect(0,0,10,10)
  }
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      OutputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      OutputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
}
object InputTerminal extends PartObject{
  override val Ports=List[Port](Port0)
  object Port0 extends Port{
    override val point=new Point(10,5)
    override val toString="Input"
  }
}
class InputTerminal extends Part{
  var signal=false
  var isCompiled=false
  preferredSize=new Dimension(10,10)

  override def paintComponent(g:Graphics2D){
    if(signal)g.setColor(Color.GREEN)
    else g.setColor(Color.RED)
    g.fillRect(0,0,10,10)
  }
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        if(isCompiled){
          signal= !signal
          repaint
          PortMap(InputTerminal.Port0).setSignal(signal)
        }
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      InputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      InputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
 }

object OrGatePart extends PartObject{
  
  private val OrGateImage=new BufferedImage(60,50,BufferedImage.TYPE_INT_ARGB)
  private val OrGateGraphics=OrGateImage.getGraphics
  OrGateGraphics.setColor(Color.GREEN)
  OrGateGraphics.drawArc(-25,0,50,50,-90,180)
  OrGateGraphics.drawLine(25,0,0,0)
  OrGateGraphics.drawLine(0,49,25,49)
  OrGateGraphics.drawArc(0,0,50,50,-90,180)
  override val Ports=List(Port0,Port1,Port2)
  object Port0 extends Port{
    override val point=new Point(21,12)
    override val toString="Or-Port0"
  }
  object Port1 extends Port{
    override val point=new Point(21,38)
    override val toString="Or-Port1"
  }
  object Port2 extends Port{
    override val point=new Point(50,25)
    override val toString="Or-Port2"
  }
}
class OrGatePart extends Part{
 
  preferredSize=new Dimension(50,50) 
  peer.setOpaque(true)
   override def paintComponent(g:Graphics2D){
    g.drawImage(OrGatePart.OrGateImage,0,0,null)
  }
  
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      OrGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      OrGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
}
object AndGatePart extends PartObject{
  private val AndGateImage=new BufferedImage(60,50,BufferedImage.TYPE_INT_ARGB)
  private val AndGateGraphics=AndGateImage.getGraphics
  AndGateGraphics.setColor(Color.GREEN)
  private val x=Array(25,0,0,25)
  private val y=Array(0,0,49,49)
  for (i<- 0 until 3){
    AndGateGraphics.drawLine(x(i),y(i),x(i+1),y(i+1))
  }
  AndGateGraphics.drawArc(0,0,50,50,-90,180)
  override val Ports=List(Port0,Port1,Port2)
  object Port0 extends Port{
    override val point=new Point(0,12)
    override def toString="And-Port0"
  }
  object Port1 extends Port{
    override val point=new Point(0,38)
    override def toString="And-Port1"
  }
  object Port2 extends Port{
    override val point=new Point(50,25)
    override def toString="And-Port2"
  }
}
class AndGatePart extends Part {
  peer.setOpaque(true)
  preferredSize=new Dimension(50,50)
  override def paintComponent(g:Graphics2D){
    g.drawImage(AndGatePart.AndGateImage,0,0,null)
  }
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      AndGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      AndGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
    
  }
}
object InvertPart extends PartObject{
  private val InvertImage=new BufferedImage(60,50,BufferedImage.TYPE_INT_ARGB)
  private val InvertGraphics=InvertImage.getGraphics
  InvertGraphics.setColor(Color.GREEN)
  InvertGraphics.drawPolygon(Array(0,50,0,0),Array(0,25,50,0),4)
  InvertGraphics.drawOval(50,20,10,10)
  override val Ports=List(Port0,Port1)
  object Port0 extends Port{
    override val point=new Point(0,25)
    override def toString="Not-Port0"
  }
  object Port1 extends Port{
    override val point=new Point(50,25)
    override def toString="Not-Port1"
  }
}
class InvertPart extends Part{
  peer.setOpaque(true)
  preferredSize=new Dimension(60,50)
  val myself=this
  override def paintComponent(g:Graphics2D){
    g.drawImage(InvertPart.InvertImage,0,0,null)
  }
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
               
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      InvertPart.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      InvertPart.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
}

class NullPanel extends Panel{
  case class WorkItem(time:Int,action:Action)
  private type Agenda=List[WorkItem]
  private type Action= () => Unit
  private var agenda:Agenda=List()
  private var currenttime=0
  private def insert(ag:Agenda,item:WorkItem):Agenda={
    if(ag.isEmpty || item.time<ag.head.time)item::ag
    else ag.head::insert(ag.tail,item)
  }
  def initSimulation={
    agenda=List()
    currenttime=0
  }
  def afterDelay(delay:Int)(block : =>Unit){
    val item=WorkItem(currenttime+delay,()=>block)
    agenda=insert(agenda,item)
  }
  def next{
    agenda match{
      case WorkItem(time,action)::rest=>{
        agenda=rest
        currenttime=time
        action()
      }
      case List()=>
    }
  }
  def run(){
    afterDelay(0)({})
    while(!agenda.isEmpty)next
  }
  def inverter(input:Wire,output:Wire){
    def invertAction(){
      val sig=input.getSignal
      afterDelay(10){output setSignal !sig}
    }
    input addAction invertAction
  }
  def andGate(input0:Wire,input1:Wire,output:Wire){
    def andAction(){
      val sig0=input0.getSignal
      val sig1=input1.getSignal
      afterDelay(10){output setSignal(sig0 & sig1)}
    }
    input0 addAction andAction
    input1 addAction andAction
  }
  def orGate(input0:Wire,input1:Wire,output:Wire){
    def orAction(){
      val sig0=input0.getSignal
      val sig1=input1.getSignal
      afterDelay(10){output.setSignal(sig0 | sig1)}
    }
    input0 addAction orAction
    input1 addAction orAction
  }
  def input(terminal:InputTerminal){
    terminal.isCompiled=true
    def inputAction(){
      val sig=terminal.signal
      afterDelay(0){terminal.PortMap(InputTerminal.Port0).setSignal(sig)}
    }
    terminal.PortMap(InputTerminal.Port0) addAction inputAction
  }
  def output(terminal:OutputTerminal){
    def outputAction(){
      val sig=terminal.PortMap(OutputTerminal.Port0).getSignal
      afterDelay(0){terminal.setSignal(sig)}
    }
    terminal.PortMap(OutputTerminal.Port0) addAction outputAction
  }
  def compile={
    wirelist.foreach(wire=>{
      wire.terminal(0)._1.connect(wire.terminal(0)._2, wire)
      wire.terminal(1)._1.connect(wire.terminal(1)._2, wire)
    })
    try{
      partlist.foreach(_ match{
        case part:AndGatePart=>{
          andGate(part.PortMap(AndGatePart.Port0),part.PortMap(AndGatePart.Port1),part.PortMap(AndGatePart.Port2))
        }
        case part:OrGatePart=>{
          orGate(part.PortMap(OrGatePart.Port0),part.PortMap(OrGatePart.Port1),part.PortMap(OrGatePart.Port2))
        }
        case part:InvertPart=>{
          inverter(part.PortMap(InvertPart.Port0),part.PortMap(InvertPart.Port1))
        }
        case terminal:InputTerminal=>{
          input(terminal)
        }
        case terminal:OutputTerminal=>{
          output(terminal)
        }
      })
      run
    }catch{
      case e:java.util.NoSuchElementException=>{
        println(e)
        println("failed")
        initSimulation
        wirelist.foreach(_.removeAllAction)
        
      }
    }
  }
  peer.setLayout(null)
  preferredSize=new Dimension(500,500)
  val imgBuffer=new BufferedImage(500,500,BufferedImage.TYPE_INT_RGB)
  implicit def DoubleToInt(d: Double): Int = d.toInt
  val movingBuffer=new BufferedImage(500,500,BufferedImage.TYPE_INT_RGB)
  val mg=movingBuffer.getGraphics
  val bg=imgBuffer.getGraphics
  var partlist:List[Part]=Nil
  var wirelist:List[Wire]=Nil
  var isWiring=false
  var tmpWiringWire:Wire=_
  listenTo(this)
  listenTo(mouse.moves)
  listenTo(mouse.clicks)
  def output={
    wirelist.foreach(println)
  }
    reactions+={
      case e:MouseClicked=>{
        val event=e.asInstanceOf[MouseClicked]
        if(event.peer.getButton==MouseEvent.BUTTON3){
          val popupMenu:PopupMenu=new PopupMenu{
            contents+=new MenuItem("invert"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  val part=new InvertPart
                  addPart(part,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("and"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new AndGatePart,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("or"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new OrGatePart,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("input"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new InputTerminal,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("output"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new OutputTerminal,event.point.x,event.point.y)
                }
              }
            }
          }
          popupMenu.show(this,event.point.x,event.point.y)
        }else if(event.peer.getButton==MouseEvent.BUTTON1){
          if(isWiring)finishWiring
        }
        
      }
      case MouseMoved(_,p,_)=>{
        if(isWiring){
          mg.drawImage(imgBuffer,0,0,null)
          val locateOfPart0=tmpWiringWire.terminal(0)._1.peer.getLocation()
          val offsetOfTerminal0=tmpWiringWire.terminal(0)._2.point
          mg.drawLine(locateOfPart0.getX+offsetOfTerminal0.getX,locateOfPart0.getY+offsetOfTerminal0.getY,p.getX,p.getY)
          repaint
        }
      }
      case DraggingWireEvent(part,port)=>{
        if(!isWiring){
        isWiring=true
        tmpWiringWire=new Wire
        tmpWiringWire.terminal(0)=(part,port)
        }
      }
      case ReleasedHereEvent(part,port)=>{
        if(isWiring){
          if(tmpWiringWire.terminal(0)._1!=part){
            finishWiring
            tmpWiringWire.terminal(1)=(part,port)
            wirelist=tmpWiringWire::wirelist
            addWireToPanel(tmpWiringWire)
            repaint
          }
        }
      }
      case VanishPartEvent(p)=>{
        peer.remove(p.peer)
        partlist=partlist.filter(p!=)
        wirelist=wirelist.filter(wire=>wire.terminal(0)._1!=p && wire.terminal(1)._1!=p)
        bg.setColor(Color.BLACK)
        bg.fillRect(0,0,imgBuffer.getWidth,imgBuffer.getHeight)
        wirelist.foreach(addWireToPanel(_))
        repaint
      }
      case _:MouseExited=>{
        //if(isWiring)isWiring=false
      }
      
    }
  def finishWiring={
    isWiring=false
    repaint
  }
  def addWireToPanel(wire:Wire)={
    val locateOfPart0=wire.terminal(0)._1.peer.getLocation()
    val offsetOfTerminal0=wire.terminal(0)._2.point
    val locateOfPart1=wire.terminal(1)._1.peer.getLocation()
    val offsetOfTerminal1=wire.terminal(1)._2.point
    bg.setColor(Color.GREEN)
    bg.drawLine(locateOfPart0.getX+offsetOfTerminal0.getX,locateOfPart0.getY+offsetOfTerminal0.getY,locateOfPart1.getX+offsetOfTerminal1.getX,locateOfPart1.getY+offsetOfTerminal1.getY)
  }
  override def paintComponent(g:Graphics2D){
    if(isWiring)g.drawImage(movingBuffer,0,0,null)
    else g.drawImage(imgBuffer,0,0,null)
  }
  def addPart(comp:Part,x:Int,y:Int):Unit={
    val p =comp.peer
    partlist=comp::partlist
    p.setLocation(x,y)
    p.setSize(p.getPreferredSize)
    peer.add(p)
    repaint()
    listenTo(comp)
    
  }
  def remove(comp:Component):Unit={
    peer.remove(comp.peer)
  }

}
class Wire {
  val terminal=new Array[(Part,Port)](2)
  private var sigVal=false
  private var actions:List[Action]=List()
  private type Action = () =>Unit
  def getSignal=sigVal
  def setSignal(s:Boolean)={
    if(s!=sigVal){
      sigVal=s
      actions.foreach(action=>action())
    }
  }
  def addAction(a:Action)={
    actions=a::actions
    a()
  }
  def removeAllAction={
    sigVal=false
    actions=List()
  }
  override def toString:String=terminal(0)._2.toString+"--"+terminal(1)._2.toString  
}


class TestFrame extends MainFrame{
  title="Null Test"
  resizable=false
  val board=new NullPanel
  contents=new BoxPanel(Orientation.Vertical){
    contents+=board
    contents+=new BoxPanel(Orientation.Horizontal){
      contents+=Button("Compile"){
         board.output
         board.compile
      }
    }
  }
  
}
object Main {
  def main(args:Array[String]){
    val ui=new TestFrame
    ui.visible=true
  }
}

かなり雑なので推敲したい。

以下のような機能も追加したい

  • 配線を分岐出来る機能
  • 配線を曲げることが出来るように
  • 複数のパーツをひとまとめに出来るように

これを参考にした。
http://www.scala-lang.org/docu/files/ScalaByExample.pdf

というかUI部分以外の論理回路の実装はほぼパクリ……

迷路作成&探索

迷路作成&探索をScalaで行った。

import scala.swing._
import scala.math._
import java.awt.Color
import scala.collection.mutable.Queue
import scala.util.control.Breaks.{break,breakable}

abstract class MazeMaker(val step:Int){
  val field=Array.ofDim[Int](2*step+1,2*step+1)
  def next
  def isFinished:Boolean
  def status:Array[Array[Int]]=field
}
class PoleDown(step:Int) extends MazeMaker(step){
  private var x=2
  private var y=2
  for (i <- 0 until step*2+1){
    field(i)(0) = -1
    field(i)(2*step) = -1
    field(0)(i) = -1
    field(2*step)(i) = -1
  }
  for (i <- 2 until 2*step+1 by 2){
    for (j <- 2 until 2*step+1 by 2){
      field(i)(j) = -1
    }
  }
  def next={
    _next
    y+=2
    if (y==2*step){
      y=2
      x+=2
    }
    def _next:Unit={
      if (x==2){
        floor(random*4).toInt match{
          case 0=> field(x-1)(y)= -1
          case 1=> field(x+1)(y)= -1
          case 2=>{
            if (field(x)(y-1)== -1)_next
            else field(x)(y-1) = -1
          }
          case 3=>field(x)(y+1) = -1
        }
      }else{
        floor(random*3).toInt match{
          case 0=>field(x+1)(y) = -1
          case 1=>{
            if (field(x)(y-1)== -1)_next
            else field(x)(y-1) = -1
          }
          case 2=> field(x)(y+1)= -1
        }
      }
    }
  }
  def isFinished:Boolean={
    if (x==2*step && y==2)return true
    return false
  }
}
abstract class MazeSolver(val step:Int,val start:(Int,Int),val goal:(Int,Int)){
  val field=Array.ofDim[Int](2*step+1,2*step+1)
  def init(initfield:Array[Array[Int]])
  def next
  def isFinished:Boolean
  def status:Array[Array[Int]]=field
}

class BFS(step:Int,start:(Int,Int),goal:(Int,Int)) extends MazeSolver(step,start,goal){
  val queue=new Queue[(Int,Int)]
  def init(initfield:Array[Array[Int]]){
    for (i <- 0 until 2*step+1){
      for (j <- 0 until 2*step+1){
        field(i)(j)=initfield(i)(j)
        if(field(i)(j)==0){
          field(i)(j)=Int.MaxValue
        }
      }
    }
    queue.enqueue(start)
    field(start._1)(start._2)=1
    
  }
  
  def next={
    val (x,y)=queue.dequeue()
    for ((dx,dy) <- List((0,1),(1,0),(-1,0),(0,-1))){
      try{
        if(field(x+dx)(y+dy) > field(x)(y)+1){
          field(x+dx)(y+dy)=field(x)(y)+1
          queue.enqueue((x+dx,y+dy))
        }
        
      }catch{
        case _=>
      }
    }
    
  }
  def isFinished:Boolean={
   
   return queue.isEmpty
  }
  override def status:Array[Array[Int]]={
    val rfield=Array.ofDim[Int](2*step+1,2*step+1)
    for (i <- 0 until 2*step+1){
      for (j <- 0 until 2*step+1){
        field(i)(j) match{
          case Int.MaxValue=>rfield(i)(j)=0
          case -1=>rfield(i)(j) = -1
          case _=>rfield(i)(j)=1
        }
      }
    }
    if(isFinished){
     var (nx,ny)=goal
     rfield(nx)(ny)=2
     while((nx,ny)!=start){
       breakable{
       for ((dx,dy)<- List((0,1),(1,0),(-1,0),(0,-1))){
         try{
           if(field(nx)(ny)-1==field(nx-dx)(ny-dy)){
             nx=nx-dx
             ny=ny-dy
             rfield(nx)(ny)=2
             break
           }
         }catch{
           case _=>
         }
       }
       }
     }
    }
    return rfield
  }
}
class Maze(val step:Int,val maker:MazeMaker,val solver:MazeSolver){
  var field=Array.ofDim[Int](step,step)
  def apply(x:Int,y:Int)=field(x)(y)
  
  def startMake=field=maker.status
  def makeStep:Boolean={
    if(maker.isFinished)return false
    else{
      maker.next
      field=maker.status
      return true
    }
  }
  def startSolve:Boolean={
    if(maker.isFinished){
      solver.init(field)
      return true
    }else return false
  }
  def solveStep:Boolean={
    if(solver.isFinished)return false
    else{
      solver.next
      field=solver.status
      return true
    }
  }
}
class MazeCanvas(val maze:Maze) extends Component {
  preferredSize=new Dimension(420,420)
  
  private def squareGeometry:(Int,Int,Int,Int)={
    val d = size
    val squareSide=d.height min d.width
    val x0=(d.width-squareSide)/2
    val y0=(d.height-squareSide)/2
    (squareSide,x0,y0,squareSide/(maze.step*2+1))
  }
  override def paintComponent(g:Graphics2D){

    g.setRenderingHint(
        java.awt.RenderingHints.KEY_ANTIALIASING,
        java.awt.RenderingHints.VALUE_ANTIALIAS_ON
    )
    val (squareSide,x0,y0,wid)=squareGeometry
    for (x <- 0 until maze.step*2+1){
      for (y <- 0 until maze.step*2+1){
        if (maze(x,y)== -1){
          g.setColor(Color.BLACK)
        }else if (maze(x,y)==0){
          g.setColor(Color.WHITE)
        }else if(maze(x,y)==1){
          g.setColor(Color.RED)
        }else if (maze(x,y)==2){
          g.setColor(Color.GREEN)
        }else if (maze(x,y)==3){
          
        }
        g.fillRect(x0+x*wid,y0+y*wid,wid,wid)
      }
    }
  }
}

class MazeFrame(val maze:Maze) extends MainFrame{
   private def restrictHeight(s:Component){
    s.maximumSize = new Dimension(Short.MaxValue,s.preferredSize.height)
  }
   title="MAZE"
   resizable=false
   val canvas=new MazeCanvas(maze)
   contents=new BoxPanel(Orientation.Vertical){
     contents+=canvas
   }
}

object Main{
  def main(args:Array[String]){
    val maze=new Maze(10,new PoleDown(10),new BFS(10,(1,1),(19,19)))
    maze.startMake
    while(maze.makeStep){}
    maze.startSolve
    while(maze.solveStep){}
    
    val ui=new MazeFrame(maze)
    ui.visible=true
  }
}

探索済経路が赤、最短経路が緑で表示される。
f:id:lilyext:20170307154224p:plain
以下のサイトを参考にした。
自動生成迷路

ScalaでRSS読み取り(続き)

前回の続き
lilyext.hatenablog.com

このサイトを参考にRSSの仕様をおおまかに把握した。
amarron.hatenablog.com

で、雑に実装したクラス群が以下の通り

import scala.xml._
import java.net.URL

class RSS(val url:String,val sitealias:String,val title:String ,val link:String,val description:String,val articles:Seq[Article]){
  override def toString:String=sitealias
}
class Article(val title:String,val link:String,val description:String,val pubdate:String,val categories:Seq[String]=Seq.empty){
  override def toString:String=title
}
trait RSSParser{
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS
}
object RSS1 extends RSSParser{
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS={
    val title= (xml \ "channel" \ "title").text
    val link = (xml \ "channel" \ "link").text
    val description = (xml \ "channel" \ "description").text
    val articles = (xml \ "item").map(item=>new Article((item \ "title").text,
                                                        (item \ "link").text,
                                                        (item \ "description").text,
                                                        (item \ "dc:date").text))
    new RSS(url,sitealias,title,link,description,articles)
  }
}
object RSS2 extends RSSParser{
  
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS={
    
    val title = (xml \ "title").text
    val link = (xml \ "link").text
    val description = (xml \ "description").text
    val articles=(xml \ "item").map(item=>new Article((item \ "title").text,
                                                    (item \ "link").text,
                                                    (item \ "description").text,
                                                    (item \ "pubDate").text,
                                                    ((item \ "category").map(_.text))))
                                               
    new RSS(url,sitealias,title,link,description,articles)
  }
}
object Atom extends RSSParser{
  
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS={
    val title= (xml \ "title").text
    val link=(xml \ "link").text
    val description=(xml \ "subtitle").text
    val articles=(xml \ "entry").map(item=>{
      val entrytitle= (item \ "title").text
      val entrylink=(item \ "link" ).filter(link=>{val t=(link \ "@type").text;t=="text/html" || t==""}).map(link=>(link \ "@href").text).head
      val entryupdated=(item \ "updated").text
      val entrysummary=(item \ "summary").text
      new Article(entrytitle,entrylink,entrysummary,entryupdated)
      
    })
    new RSS(url,sitealias,title,link,description,articles)
  }
  
}


object RSS {
  def apply(url:String,sitealias:String):RSS={
    val xml=XML.load(new URL(url))
    if (xml.head.namespace=="http://www.w3.org/2005/Atom"){
      Atom(xml,url,sitealias)
    }else if (xml.head.label=="rss" && (xml \ "@version").text=="2.0"){
      RSS2(xml \ "channel",url,sitealias)
    }else if(xml.head.namespace=="http://purl.org/rss/1.0/"){
      RSS1(xml,url,sitealias)
    }else{
      RSS1(xml,url,sitealias)
    }
  }
}

RSSのフォーマットにはおおよそRSS1.0,RSS2.0,ATOMの3種類あるのでまず分類した後にパースする必要がある。
まぁ本格的なものを作るつもりも(技術も)ないのでATOM、RSS1.0はXML名前空間、RSS2.0はルート要素のバージョン属性で分類することにした。
名前空間はNodeSeqの\演算子で取得しようと悪戦苦闘したが結局xml.head.namespaceであっさり取得できることがわかった。
あとはそれぞれの規則に(雑に)従いパースした。

GUI面については以下のように実装した。

class RSSFrame(var rssarticles:List[RSS]) extends MainFrame {
  title="RSS"
  val tabPane:TabbedPane= new TabbedPane{
    preferredSize=new Dimension(500,500)
    listenTo(mouse.clicks)
    reactions+={
      case e :MouseClicked=>{
        val event=e.asInstanceOf[MouseClicked]
        if((event.peer.getButton)==MouseEvent.BUTTON3){
          val popupMenu = new PopupMenu{
            contents+=new MenuItem("削除"){
            listenTo(mouse.clicks)
            reactions+={
              case MousePressed (_,_,m,_,e)=>{
                val i:Int=tabPane.selection.index
                  if (i>=0){
                  tabPane.pages.remove(i)
                  }
               }
            }
           }
          }
          popupMenu.show(this,event.point.x,event.point.y)
        }
      }
    }
  }
  contents=tabPane
  menuBar = new MenuBar{
    contents+=new Menu("File"){
      contents+=new MenuItem(Action("add tab"){
        new Frame{
          title="Add new tab"
          contents=new ScrollPane( new BoxPanel(Orientation.Vertical){
            contents+=new ListView(rssarticles){
              listenTo(mouse.clicks)
              reactions+={
                case e : MouseClicked=>{
                  val event=e.asInstanceOf[MouseClicked]
                  if(event.clicks==2){
                    val index=peer.locationToIndex(event.point)
                    tabPane.pages+=new TabbedPane.Page(
                        rssarticles(index).sitealias,
                        new ScrollPane(new ListView(rssarticles(index).articles){
                          listenTo(mouse.clicks)
                          reactions+={
                            case e :MouseClicked=>{
                              val event=e.asInstanceOf[MouseClicked]
                              if (event.clicks==2){
                                val targeturi=rssarticles(index).articles(peer.locationToIndex(event.point)).link
                                val desktop=Desktop.getDesktop()
                                desktop.browse(new URI(targeturi))
                              }
                            }
                          }
                    }))
                  }
                }
              }
              
            }
            
          })
          visible=true
        }
        val title="テスト"
      })
      contents+=new MenuItem(Action("add feed source"){
       Dialog.showInput(message="URL",initial="http://hoge.hoge") match{
         case Some (x)=>{
           if (isExist(x)){
             Dialog.showInput(message="Name",initial="name") match{
               case Some(y)=>{
                 rssarticles=rssarticles:+RSS(x,y)
                 val file=new File("./rsstestdoc")
                 val fw=new FileWriter(file,true)
                 fw.write(y+","+x)
               }
               case None =>
             }
           }else{
             Dialog.showMessage(message="Not found")
           }
         }
         case None=>
       }
       def isExist(url:String):Boolean={
         try{
           XML.load(new URL(url))
         }catch{
           case _=>return false
         }
         return true
       }
      })
      contents+=new MenuItem(Action("Quit"){
        sys.exit(0)
      })
    }
  }

サイトごとにタブを作りそれぞれに記事のリストを入れる感じに実装。記事のタイトルをダブルクリックするとブラウザが起動する。

  • ダブルクリックの検知
  • 右クリックの検知

このあたりの方法がひどいことになっている。もっとシンプルな方法がないものか……
あとRSSを購読するサイトを登録するためにDialog.showInputを使ったのだがコピーアンドペーストが出来ないのでめんどくさい。コピーアンドペーストが出来るDialogって自分で実装しないといけないんだよな……(してない)

出来上がったやつのスクショが以下の通り
f:id:lilyext:20170305134914p:plain
Gigazinehttp://gigazine.net/とgizmodohttp://www.gizmodo.jp/RSSを表示している。

ScalaでRSS読み取り

import scala.swing._
import scala.swing.event._
import java.awt.event._
import scala.collection.mutable.ArrayBuffer
import scala.io.Source
import java.awt.Desktop
import java.net.URI
import scala.xml._
import java.net.URL
import java.io.FileWriter
import java.io.File

class RSSFrame(var rssarticles:List[RSS]) extends MainFrame {
  title="RSS"
  val tabPane:TabbedPane= new TabbedPane{
    preferredSize=new Dimension(500,500)
    listenTo(mouse.clicks)
    reactions+={
      case e :MouseClicked=>{
        val event=e.asInstanceOf[MouseClicked]
        if((event.peer.getButton)==MouseEvent.BUTTON3){
          val popupMenu = new PopupMenu{
            contents+=new MenuItem("削除"){
            listenTo(mouse.clicks)
            reactions+={
              case MousePressed (_,_,m,_,e)=>{
                val i:Int=tabPane.selection.index
                  if (i>=0){
                  tabPane.pages.remove(i)
                  }
               }
            }
           }
          }
          popupMenu.show(this,event.point.x,event.point.y)
        }
      }
    }
  }
  contents=tabPane
  menuBar = new MenuBar{
    contents+=new Menu("File"){
      contents+=new MenuItem(Action("add tab"){
        new Frame{
          title="Add new tab"
          contents=new ScrollPane( new BoxPanel(Orientation.Vertical){
            contents+=new ListView(rssarticles){
              listenTo(mouse.clicks)
              reactions+={
                case e : MouseClicked=>{
                  val event=e.asInstanceOf[MouseClicked]
                  if(event.clicks==2){
                    val index=peer.locationToIndex(event.point)
                    tabPane.pages+=new TabbedPane.Page(
                        rssarticles(index).sitealias,
                        new ScrollPane(new ListView(rssarticles(index).articles){
                          listenTo(mouse.clicks)
                          reactions+={
                            case e :MouseClicked=>{
                              val event=e.asInstanceOf[MouseClicked]
                              if (event.clicks==2){
                                val targeturi=rssarticles(index).articles(peer.locationToIndex(event.point)).link
                                val desktop=Desktop.getDesktop()
                                desktop.browse(new URI(targeturi))
                              }
                            }
                          }
                    }))
                  }
                }
              }
              
            }
            
          })
          visible=true
        }
        val title="テスト"
      })
      contents+=new MenuItem(Action("add feed source"){
       Dialog.showInput(message="URL",initial="entry") match{
         case Some (x)=>{
           if (isExist(x)){
             Dialog.showInput(message="TITLE",initial="tete") match{
               case Some(y)=>{
                 rssarticles=rssarticles:+RSS(x,y)
                 val file=new File("./rsstestdoc")
                 val fw=new FileWriter(file,true)
                 fw.write(y+","+x)
               }
               case None =>
             }
           }else{
             Dialog.showMessage(message="Not found")
           }
         }
         case None=>
       }
       def isExist(url:String):Boolean={
         try{
           XML.load(new URL(url))
         }catch{
           case _=>return false
         }
         return true
       }
      })
      contents+=new MenuItem(Action("Quit"){
        sys.exit(0)
      })
    }
  }
  
}
class RSS(val url:String,val sitealias:String,val title:String ,val link:String,val description:String,val articles:Seq[Article]){
  override def toString:String=sitealias
}
class Article(val title:String,val link:String,val description:String,val pubdate:String,val categories:Seq[String]=Seq.empty){
  override def toString:String=title
}
trait RSSParser{
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS
}
object RSS1 extends RSSParser{
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS={
    val title= (xml \ "channel" \ "title").text
    val link = (xml \ "channel" \ "link").text
    val description = (xml \ "channel" \ "description").text
    val articles = (xml \ "item").map(item=>new Article((item \ "title").text,
                                                        (item \ "link").text,
                                                        (item \ "description").text,
                                                        (item \ "dc:date").text))
    new RSS(url,sitealias,title,link,description,articles)
  }
}
object RSS2 extends RSSParser{
  
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS={
    
    val title = (xml \ "title").text
    val link = (xml \ "link").text
    val description = (xml \ "description").text
    val articles=(xml \ "item").map(item=>new Article((item \ "title").text,
                                                    (item \ "link").text,
                                                    (item \ "description").text,
                                                    (item \ "pubDate").text,
                                                    ((item \ "category").map(_.text))))
                                               
    new RSS(url,sitealias,title,link,description,articles)
  }
}
object Atom extends RSSParser{
  
  def apply(xml:NodeSeq,url:String,sitealias:String):RSS={
    val title= (xml \ "title").text
    val link=(xml \ "link").text
    val description=(xml \ "subtitle").text
    val articles=(xml \ "entry").map(item=>{
      val entrytitle= (item \ "title").text
      val entrylink=(item \ "link" ).filter(link=>{val t=(link \ "@type").text;t=="text/html" || t==""}).map(link=>(link \ "@href").text).head
      val entryupdated=(item \ "updated").text
      val entrysummary=(item \ "summary").text
      new Article(entrytitle,entrylink,entrysummary,entryupdated)
      
    })
    new RSS(url,sitealias,title,link,description,articles)
  }
  
}


object RSS {
  def apply(url:String,sitealias:String):RSS={
    val xml=XML.load(new URL(url))
    if (xml.head.namespace=="http://www.w3.org/2005/Atom"){
      Atom(xml,url,sitealias)
    }else if (xml.head.label=="rss" && (xml \ "@version").text=="2.0"){
      RSS2(xml \ "channel",url,sitealias)
    }else if(xml.head.namespace=="http://purl.org/rss/1.0/"){
      RSS1(xml,url,sitealias)
    }else{
      RSS1(xml,url,sitealias)
    }
  }
}
object Main {
  def main(args:Array[String]){
    val s=Source.fromFile("./rsstestdoc")
    val rssfeedlist=for (line<-s.getLines.toList)yield line split ','
    val rssarticles=rssfeedlist.map(l=>RSS(l(1),l(0)))
    val ui=new RSSFrame(rssarticles)
    ui.visible=true
  }
}

かなり適当。RSSを取得してタブにリストとしてのっける。記事のタイトルをダブルクリックでブラウザ起動。
書きなおそう……