I have 2 immutable case classes A(source, key, value) and B(source, key, value)
I want to add A over B in such a way when 'source' and 'key' doesn't exist, to be added from A to the B and when 'source' and 'key' exist to replace the value from B with the one from A. The same way 'merge_array' function from php works on a multidimensional array.
I tried with 'A.union(B).groupBy(.key)' and then 'groupBy(.source)' and get the 1st value. But then I realized that I can never be sure that first value will always be the value of A.
I'm quite new to scala and I really ran out of ideas how I could do this from a functional immutable point of view.
Anyone has any idea how I could do this?
Thank you
Edit:
case class TranslationValue(source: String, key: String, value: String)
def main(args:Array[String]):Unit = {
println(merge(data1.toSet, data2.toSet))
}
def merge(a: Set[TranslationValue], b: Set[TranslationValue]) = {
a.union(b).groupBy(_.key).flatMap{ case (s, v) =>
v.groupBy(_.source).flatMap{case (s1, v1) => {
for (res <- 0 to 0) yield v1.head
}
}
}
}
Example
data1 has this data
Set(
TranslationValue(messages,No,No),
TranslationValue(messages,OrdRef,Order Reference),
TranslationValue(messages,OrdId,Order Id)
)
data2 has this data
Set(
TranslationValue(messages,No,No),
TranslationValue(messages,OrdRef,OrderRef)
TranslationValue(messages,Name,Name)
)
putting data1 over data2 I want to obtain
List(
TranslationValue(messages,No,No),
TranslationValue(messages,OrdRef,Order Reference),
TranslationValue(messages,OrdId,Order Id)
TranslationValue(messages,Name,Name)
)
I know that what I do can be done better, but like I said, I'm learning :)
you can group in one go:
def merge(a: Seq[TranslationValue], b: Seq[TranslationValue]) = {
a.union(b).groupBy(t=>(t.key,t.source)).map(c=>c._2.head)
}
i think you could also override the equals method for TranslationValue so that two translation values are equal when source and key are the same(the hashcode method has also to be overridden). Then a.union(b) would be enough.
edit:
It seems Set doesnt guarantee order of items(Scala: Can I rely on the order of items in a Set?), but a seq should.
Related
I have an app that is reading an ingredients list. At this point I've already retrieved a list of the 2500 most common ingredients. So I've got a list of, say 10 ingredients as strings, and a list of 2500 ingredients, with names as well as other properties. If an ingredient in this list of strings matches the name of an ingredient in the list of ingredients, I'd like to add it to another list third list, of ingredients that exist. The only way I know how to do that is with basically a for loop.
I'd do it as
fun compareLists(listOfIng: List<String>): List<ListIngredientsQuery.Item> {
var returnList = mutableListOf<ListIngredientsQuery.Item>()
for (ing in listOfIng) {
for (serverIngredient in MyApp.metaIngredientList!!) {
if (serverIngredient.name() == ing) {
returnList!!.add(serverIngredient)
}
}
}
return returnList
}
Which would technically work, but I have to imagine there's a better, faster way than iterating over 2500 items, as many times as there are Ingredients in an Ingredient list. What is the like, proper, preferred by real developers, way of doing this.
As each ingredient name is unique, you can use hash map for storing your 2500 ingredients with its name as the key. This way you do not need to loop over that huge collection any more, but just look thing up by the name and let the hash map deal with it.
To put some code to what Marcin said, here is what I would do:
fun compareLists(listOfIng: List<String>) =
MyApp.metaIngredientList!!
.associateBy { it.name() }
.let { metaIngredientMap -> listOfIng.mapNotNull { metaIngredientMap[it] }}
Or if we wanna avoid using !!
fun compareLists(listOfIng: List<String) =
MyApp.metaIngredientList
?.associateBy { it.name() }
?.let { metaIngredientMap -> listOfIng.mapNotNull { metaIngredientMap[it] }}
?: emptyList<ListIngredientQuery.Item>()
Of course, ideally, you would want that MyApp.metaIngredientList to be already a Map and not convert it into a Map for each operation
Let's say I have a giant table, something like:
test.test[1].testing.test.test_test
The table isn't guaranteed to exist. Neither are the tables containing it. I would like to just be able to do:
if test.test[1].testing.test.test_test then
print("it exits!")
end
But of course, this would give me an "Attempt to index ? (a nil value)" error if any of the indices aren't yet defined. So many times, I'll end up doing something like this:
if test then
if test.test then
if test.test[1] then
if test.test[1].testing then -- and so on
Is there a better, less-tedious way to accomplish this?
You can write a function that takes a list of keys to look up and does whatever action you want if it finds the entry. Here's an example:
function forindices(f, table, indices)
local entry = table
for _,idx in ipairs(indices) do
if type(entry) == 'table' and entry[idx] then
entry = entry[idx]
else
entry = nil
break
end
end
if entry then
f()
end
end
test = {test = {{testing = {test = {test_test = 5}}}}}
-- prints "it exists"
forindices(function () print("it exists") end,
test,
{"test", 1, "testing", "test", "test_test"})
-- doesn't print
forindices(function () print("it exists") end,
test,
{"test", 1, "nope", "test", "test_test"})
As an aside, the functional programming concept that solves this kind of problem is the Maybe monad. You could probably solve this with a Lua implementation of monads, though it wouldn't be very nice since there's no syntactic sugar for it.
You can avoid raising errors by setting an __index metamethod for nil:
debug.setmetatable(nil, { __index=function () end })
print(test.test[1].testing.test.test_test)
test = {test = {{testing = {test = {test_test = 5}}}}}
print(test.test[1].testing.test.test_test)
You also use an empty table:
debug.setmetatable(nil, { __index={} })
Current question is relative with next one, but now I need to read the data from database instead of insert.
I have next three case classes:
case class A (id: Long, bList: List[B])
case class B (id: Long, aId: cList: List[C])
case class C (id: Long, bId: Long)
And query with two leftJoin functions and incomingAId for filtering aTable results:
val query = (for {
((aResult,bResult),cResult) <- aTable.filter(_.id === incomigAId)
.joinLeft(bTable).on(_.id === _.aId)
.joinLeft(cTable).on(_._2.map(_.id) === _.bId)
} yield ((aResult,bResult),cResult)).result.transactionally
Next query works and the result looks valid, but isn't easy to handle it to the case classes. Also, executionResult has Seq[Nothing] type and process of mapping requires something like that:
database.run(query).map{ executionResult =>
executionResult.map { vectorElement: [Tuple2[Tuple2[A, Option[B]], Option[C]]]
...
}
}
Is there any proper way to prevent Seq[Nothing] (changes in query)?
Or if the query result type is fine, could you please share solution how to map it to the case classes above?
Right now I'm using next solution, but I suppose that some part of code can be optimized (e.g. groupBy replacing with something else).
execute(query).mapTo[Vector[((A, Option[B]), Option[C])]]
.flatMap { insideFuture =>
insideFuture.groupBy(_._1._1).mapValues { values =>
//scala groupBy losts ordering
val orderedB = values.groupBy(_._1._2).toSeq.sortBy(_._1.map(_.id)).toMap
orderedB.mapValues(_.map(_._2))
}.headOption match {
case Some(data) => Future.successful {
data._1.copy(bList = data._2.map {
case (Some(bElement), optionCElements) =>
bElement.copy(cList = optionCElements.toList.flatten)
case _ =>
throw new Exception("Invalid query result. Unable to find B elements")
}.toList)
}
case None => Future.failed(new Exception("Unable to find A with next id " + incomigAId))
}
}
}
I have a sorting problem in Scala that I could certainly solve with brute-force, but I'm hopeful there is a more clever/elegant solution available. Suppose I have a list of strings in no particular order:
val keys = List("john", "jill", "ganesh", "wei", "bruce", "123", "Pantera")
Then at random, I receive the values for these keys at random (full-disclosure, I'm experiencing this problem in an akka actor, so events are not in order):
def receive:Receive = {
case Value(key, otherStuff) => // key is an element in keys ...
And I want to store these results in a List where the Value objects appear in the same order as their key fields in the keys list. For instance, I may have this list after receiving the first two Value messages:
List(Value("ganesh", stuff1), Value("bruce", stuff2))
ganesh appears before bruce merely because he appears earlier in the keys list. Once the third message is received, I should insert it into this list in the correct location per the ordering established by keys. For instance, on receiving wei I should insert him into the middle:
List(Value("ganesh", stuff1), Value("wei", stuff3), Value("bruce", stuff2))
At any point during this process, my list may be incomplete but in the expected order. Since the keys are redundant with my Value data, I throw them away once the list of values is complete.
Show me what you've got!
I assume you want no worse than O(n log n) performance. So:
val order = keys.zipWithIndex.toMap
var part = collection.immutable.TreeSet.empty[Value](
math.Ordering.by(v => order(v.key))
)
Then you just add your items.
scala> part = part + Value("ganesh", 0.1)
part: scala.collection.immutable.TreeSet[Value] =
TreeSet(Value(ganesh,0.1))
scala> part = part + Value("bruce", 0.2)
part: scala.collection.immutable.TreeSet[Value] =
TreeSet(Value(ganesh,0.1), Value(bruce,0.2))
scala> part = part + Value("wei", 0.3)
part: scala.collection.immutable.TreeSet[Value] =
TreeSet(Value(ganesh,0.1), Value(wei,0.3), Value(bruce,0.2))
When you're done, you can .toList it. While you're building it, you probably don't want to, since updating a list in random order so that it is in a desired sorted order is an obligatory O(n^2) cost.
Edit: with your example of seven items, my solution takes about 1/3 the time of Jean-Philippe's. For 25 items, it's 1/10th the time. 1/30th for 200 (which is the difference between 6 ms and 0.2 ms on my machine).
If you can use a ListMap instead of a list of tuples to store values while they're gathered, this could work. ListMap preserves insertion order.
class MyActor(keys: List[String]) extends Actor {
def initial(values: ListMap[String, Option[Value]]): Receive = {
case v # Value(key, otherStuff) =>
if(values.forall(_._2.isDefined))
context.become(valuesReceived(values.updated(key, Some(v)).collect { case (_, Some(v)) => v))
else
context.become(initial(keys, values.updated(key, Some(v))))
}
def valuesReceived(values: Seq[Value]): Receive = { } // whatever you need
def receive = initial(keys.map { k => (k -> None) })
}
(warning: not compiled)
I am seeing a problem with some Scala 2.7.7 code I'm working on, that should not happen if it the equivalent was written in Java. Loosely, the code goes creates a bunch of card players and assigns them to tables.
class Player(val playerNumber : Int)
class Table (val tableNumber : Int) {
var players : List[Player] = List()
def registerPlayer(player : Player) {
println("Registering player " + player.playerNumber + " on table " + tableNumber)
players = player :: players
}
}
object PlayerRegistrar {
def assignPlayersToTables(playSamplesToExecute : Int, playersPerTable:Int) = {
val numTables = playSamplesToExecute / playersPerTable
val tables = (1 to numTables).map(new Table(_))
assert(tables.size == numTables)
(0 until playSamplesToExecute).foreach {playSample =>
val tableNumber : Int = playSample % numTables
tables(tableNumber).registerPlayer(new Player(playSample))
}
tables
}
}
The PlayerRegistrar assigns a number of players between tables. First, it works out how many tables it will need to break up the players between and creates a List of them.
Then in the second part of the code, it works out which table a player should be assigned to, pulls that table from the list and registers a new player on that table.
The list of players on a table is a var, and is overwritten each time registerPlayer() is called. I have checked that this works correctly through a simple TestNG test:
#Test def testRegisterPlayer_multiplePlayers() {
val table = new Table(1)
(1 to 10).foreach { playerNumber =>
val player = new Player(playerNumber)
table.registerPlayer(player)
assert(table.players.contains(player))
assert(table.players.length == playerNumber)
}
}
I then test the table assignment:
#Test def testAssignPlayerToTables_1table() = {
val tables = PlayerRegistrar.assignPlayersToTables(10, 10)
assertEquals(tables.length, 1)
assertEquals(tables(0).players.length, 10)
}
The test fails with "expected:<10> but was:<0>". I've been scratching my head, but can't work out why registerPlayer() isn't mutating the table in the list. Any help would be appreciated.
The reason is that in the assignPlayersToTables method, you are creating a new Table object. You can confirm this by adding some debugging into the loop:
val tableNumber : Int = playSample % numTables
println(tables(tableNumber))
tables(tableNumber).registerPlayer(new Player(playSample))
Yielding something like:
Main$$anon$1$Table#5c73a7ab
Registering player 0 on table 1
Main$$anon$1$Table#21f8c6df
Registering player 1 on table 1
Main$$anon$1$Table#53c86be5
Registering player 2 on table 1
Note how the memory address of the table is different for each call.
The reason for this behaviour is that a Range is non-strict in Scala (until Scala 2.8, anyway). This means that the call to the range is not evaluated until it's needed. So you think you're getting back a list of Table objects, but actually you're getting back a range which is evaluated (instantiating a new Table object) each time you call it. Again, you can confirm this by adding some debugging:
val tables = (1 to numTables).map(new Table(_))
println(tables)
Which gives you:
RangeM(Main$$anon$1$Table#5492bbba)
To do what you want, add a toList to the end:
val tables = (1 to numTables).map(new Table(_)).toList
val tables = (1 to numTables).map(new Table(_))
This line seems to be causing all the trouble - mapping over 1 to n gives you a RandomAccessSeq.Projection, and to be honest, I don't know how exactly they work, but a bit less clever initialising technique does the job.
var tables: Array[Table] = new Array(numTables)
for (i <- 0 to numTables) tables(i) = new Table(i)
Using the first initialisation method I wasn't able to change the objects (just like you), but using a simple array everything seems to be working.