Scala: Matching case classes - oop

The following code claims that Jack is employed in construction, but Jane is yet another victim of the rough economy:
abstract class Person(name: String) {
case class Student(name: String, major: String) extends Person(name)
override def toString(): String = this match {
case Student(name, major) => name + " studies " + major
case Worker(name, occupation) => name + " does " + occupation
case _ => name + " is unemployed"
}
}
case class Worker(name: String, job: String) extends Person(name)
object Narrator extends Person("Jake") {
def main(args: Array[String]) {
var friend: Person = new Student("Jane", "biology")
println("My friend " + friend) //outputs "Jane is unemployed"
friend = new Worker("Jack", "construction")
println("My friend " + friend) //outputs "Jack does construction"
}
}
Why does the match fail to recognize Jane as a Student?

What I believe is happening here is that the Student case class is being declared inside of Person. Hence the case Student in the toString will only match Students that are part of a particular Person instance.
If you move the case class Student to be parallel to the case class Worker (and then remove the unnecessary extends Person("Jake") from object Narrator ... which is only there so that the new Student wound up being a Person$Student specific to Jake), you will find Jane does indeed study biology.

Emil is entirely correct, but here's an example to make it clear:
scala> case class A(a: String) {
| case class B(b: String)
| def who(obj: Any) = obj match {
| case B(b) => println("I'm A("+a+").B("+b+").")
| case b: A#B => println("I'm B("+b+") from some A")
| case other => println("Who am I?")
| }
| }
defined class A
scala> val a1 = A("a1")
a1: A = A(a1)
scala> val a2 = A("a2")
a2: A = A(a2)
scala> val b1= a1.B("b1")
b1: a1.B = B(b1)
scala> val b2 = a2.B("b2")
b2: a2.B = B(b2)
scala> a1 who b1
I'm A(a1).B(b1).
scala> a1 who b2
I'm B(B(b2)) from some A
To be more precise, this line:
case Student(name, major) => name + " studies " + major
really means
case this.Student(name, major) => name + " studies " + major
Unfortunately, while Jane was instantiated on Jake, this in Jane's case is pointing to Jane herself.

Related

How can I avoid a "Primary constructor call expected" in this case?

I've created a Person class and a Student class here in Kotlin:
In line 27, I'm trying to achieve a case where a user can create a "Student" class by providing 4 parameters: FirstName, LastName, Age, and Degree.
I've also written the equivalent code in Java. I'm trying to achieve the Java equivalent code's Secondary Constructor in line 30:
How can I avoid a "Primary constructor call expected" in the Kotlin code?
For your use case you don't even need secondary constructors. You could have optional arguments in the constructor. Like this for example:
open class Person(var firstName: String, var lastName: String, var age: Int? = null) {
override fun toString() = "$firstName | $lastName | $age"
}
class Student(firstName: String, lastName: String, var degree: String, age: Int? = null) : Person(firstName, lastName, age) {
override fun toString() = "$firstName | $lastName | $age | $degree"
}
To demonstrate:
fun main() {
val a = Person("Aaa", "aaA")
val b = Person("Bbb", "bbB", 20)
val c = Student("Ccc", "ccC", "degreeC")
val d = Student("Ddd", "ddD", "degreeD", 21)
println(a)
println(b)
println(c)
println(d)
}
Output:
Aaa | aaA | null
Bbb | bbB | 20
Ccc | ccC | null | degreeC
Ddd | ddD | 21 | degreeD

Efficiently matching two Flux

I have two Flux with 2 different data types as shown below:
Flux<Dog> dogs = loadDogsFromFile()
Flux<Man> men = loadMenFromFile()
data class Dog(
val name: String,
val ownerName: String,
)
data class Man(
val name: String,
val dogOwnerName: String,
)
As you can see the one field we can use to match these two Flux objects is dogOwnerName. Right now this is how I am comparing them
val disposable = dogs.flatMap { dog ->
men.map { man->
val isEqual = comparator(dog, man)
Triple(dog, man, isEqual)
}
}.filter {x -> x.third === true }
This gets the job done but it is nowhere efficient, it keeps looping even after the desired fields are found and because of that, we have to use a filter operator to only get what we need.
Edit
Based on #marstran comment on the user input, I have large JSON files that contain dogs and men that I'm loading here:
Flux<Dog> dogs = loadDogsFromFile()
Flux<Man> men = loadMenFromFile()
After matching the dogs to their owners/men I'm building an object that I'm saving to the database like this:
val disposable = dogs.flatMap { dog ->
men.map { man->
val isEqual = comparator(dog, man)
Triple(dog, man, isEqual)
}
}.filter {x -> x.third === true }
.map{(dog,man,isEqual) ->
DogOwner(man,dog)
}.doOnNext{dogOwner -> dogOwnerRepository.save(dogOwner)}
Consider using method take(long n, boolean limitRequest) from Flux:
public final Flux<T> take(long n,
boolean limitRequest)
Take only the first N values from this Flux, if available.
using it will allow you to break iterating over man once owner would be found.
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#take-long-boolean-
Take a look at this java example. I assume that dog and his owner have same value.
#Test
void dog_owner_test() {
int COUNT = 10000;
Flux<Integer> dogs = Flux.range(0, COUNT);
Flux<Integer> man = Flux.range(0, COUNT);
dogs.flatMap(
dog ->
man.map(m -> Tuples.of(dog, m))
.filter(tuple -> tuple.getT1().equals(tuple.getT2()))
.map(tuple -> new DogOwner(tuple.getT1(), tuple.getT2()))
.take(1, true)
)
.doOnNext(System.out::println)
// here you can save DogOwner to db
.as(StepVerifier::create)
.expectNextCount(COUNT)
.verifyComplete();
}
private static class DogOwner {
DogOwner(Integer dog, Integer owner) {
this.dog = dog;
this.owner = owner;
}
Integer dog;
Integer owner;
#Override
public String toString() {
return "DogOwner{" +
"dog=" + dog +
", owner=" + owner +
'}';
}
}

Why class member property reflection in Kotlin?

In 'Kotlin in Action', it says "if a memberProperty refers to the age property of the Person class, memberProperty.get(person) is a way to dynamically get the value of person.age" with code(10.2.1 The Kotlin Reflection API):
class Peron(val name: String, val age: Int)
>> val person = Person("Alice", 29)
>> val memberProperty = Person::age
>> println(memberProperty.get(person))
29
I can't understand why this example refers to "dynamically" getting the value of property. It just works when I run this code:
println(person.age)
Is there any other case or example of member property reflection?
For example, say you want to write a function which prints all the properties of an object along with their values, this is how you can do that:
inline fun <reified T: Any> T.printProperties() {
T::class.memberProperties.forEach { property ->
println("${property.name} = ${property.get(this)}") // You can't use `this.property` here
}
}
Usage:
data class Person(val firstName: String, val lastName: String)
data class Student(val graduate: Boolean, val rollNumber: Int)
fun main() {
val person = Person("Johnny", "Depp")
val student = Student(false, 12345)
person.printProperties()
student.printProperties()
}
Output:
firstName = Johnny
lastName = Depp
graduate = false
rollNumber = 12345

In Kotlin get array member with their name like in JavaScripe

In Javascript an Array can contain an Object and we can call Object properties by their name.
like
const person = {firstName:"John", lastName:"Doe", age:46};
let fN = person.firstName;
How can achieve this in Kotlin.
You can either try using a mapOf or a custom Person object.
// mapOf
val person = mapOf("firstName" to "John", "lastName" to "Doe", "age" to 46)
val name = person.get("firstName")
print(name) // John
// Custom class
class Person(val firstname: String, val lastname: String, val age: Int)
val person2 = Person(firstname = "John", lastname = "Doe", age = 46)
print(person2.firstname) // John
i think the best way is using data class
data class Person(val firstName:String ,val lastName:String, val age:Int)
val person1 = Person(firstName = "John", lastName = "Doe", age = 46)
val john = person1.firstName //print "John"
//you can use it in a list
val people = listOf(person1,person2,..)
#Edit
data class is kotlin special class with some standard functionality and some utility functions included, such as
equals()/hashCode()
toString() of the form "Person(name=John,lastName="Doe", age=46)"
in normal class when you call println(person1), it will print the memory location of that class, but data class will print your "data"
componentN()
For destructuring like in the javascript
val (firstName,lastNmae,age) = person1
copy()
here is the documentation https://kotlinlang.org/docs/data-classes.html

Kotlin replace multiple words in string

How to replace many parts of a string with something else in Kotlin using .replace()
For example, we can do it only by replacing one word
fun main(args: Array<String>) {
var w_text = "welcome his name is John"
println("${w_text.replace("his","here")}")
}
and the result will be " welcome here name is John " .
finally we need the result be " welcome here name is alles "
by replacing his to here and john to alles using .replace()
You can do it using multiple consecutive calls to replace():
w_text.replace("his", "here").replace("john", "alles")
You could write an extension that overloads String::replace:
fun String.replace(vararg replacements: Pair<String, String>): String {
var result = this
replacements.forEach { (l, r) -> result = result.replace(l, r) }
return result
}
fun main(args: Array<String>) {
val sentence = "welcome his name is John"
sentence.replace("his" to "here", "John" to "alles")
}
If you have many of those replacement rules, then create a mapping of them and call the replace method in a loop:
val map = mapOf("his" to "here", "john" to "alles", ...)
val sentence = "welcome his name is John"
var result = sentence
map.forEach { t, u -> result = result.replace(t, u) }
println(result)
For the ones interested in replacing a map of values in a text:
private fun replaceText(text: String, keys: Map<String, String>): String =
val replaced = map.entries.fold(text) { acc, (key, value) -> acc.replace(key, value) }
Here is a one liner:
fun String.replace(vararg pairs: Pair<String, String>): String =
pairs.fold(this) { acc, (old, new) -> acc.replace(old, new, ignoreCase = true) }
Test:
#Test fun rep() {
val input = "welcome his name is John"
val output = input.replace("his" to "her", "john" to "alles")
println(output)
output shouldBeEqualTo "welcome her name is alles"
}
Similar to other responses but using Kotlin extension and overloading String::replace to accept a map of oldValue to newValue.
fun String.replace(mapping: Map<String, String>): String {
var str = this
mapping.forEach { str = str.replace(it.key, it.value) }
return str
}
Usage:
val mapping = mapOf("his" to "here", "John" to "alles")
"his dad is John".replace(mapping) // here dad is alles
The issue with just using replace without any regex is:
Let's say I want to replace the occurrence of "here" with "there" inside the string "Where is my bag? Your bag is here." As you can imagine the result will be "Wthere is my bag? Your bag is there." which will not be correct. The solution is to use a regex like given below.
var str = "Where is my bag? Your bag is here."
val replacements = setOf("\\bhere\\b" to "there",
"\\bjohn\\b" to "alles")
replacements.forEach {
str = str.replace(Regex(it.first), it.second)
}