Scala: Assign names to anonymous objects in List - list

I'd like to know how to name objects in a list by a field
case class age(id: Int,name:String,age:Int)
val people: List[age] = List(age(2,"Angela",31),age(3,"Rick",28))
In this minimum example, I'd like to create objects Angela and Rick.
My initial idea:
val objects: List[age] = people.map( x => {val x.name = new age(x.name,x.age) })
But of course val x.name doesn't work because u can't use a variable name in a variable name.
This isn't an actual problem on a project but rather a concept I am stuck on.

It's not clear what's your intent. Do you want to create variables named angela and rick? You can do it manually for small number of list element and for large number of list element this doesn't make sense, because how would you use your 100 variables?
It seems you are talking about some mapping from names to properties and then Map will probably suit you the best
val peopleMap: Map[String,age] = people.map(p => p.name -> p). // this will create list of pairs
toMap // this will turn it to a Map
pritnln(peopleMap("Angela")) // now you can use person name to get all the info about them

A simple solution is to use a map:
case class Person(id: Int, name: String, age: Int)
val people: List[Person] = List(Person(2, "Angela", 31), Person(3, "Rick", 28))
val peopleByName: Map[String, Person] = people // List[Person]
.map(p => (p.name, p)) // List[(String, Person)]
.toMap // Map[String, Person]
or, starting with a Map instead of a List:
case class Person(id: Int, age: Int)
val peopleByName: Map[String, Person] = Map(
"Angela" -> Person(2, 31), // (String, Person)
"Rick" -> Person(3, 28) // (String, Person)
) // Map[String, Person]
However, if you want to define a member at runtime, then you'll have to extend the Dynamic trait (code snippet from here, the import is mine (required, otherwise the compiler won't be happy)):
import scala.language.dynamics
class DynImpl extends Dynamic {
def selectDynamic(name: String) = name
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#6040af64
scala> d.foo
res37: String = foo
scala> d.bar
res38: String = bar
scala> d.selectDynamic("foo")
res54: String = foo
If you really want to do this, then I suggest this implementation:
class DynamicPeople(peopleByName: Map[String, Person]) extends Dynamic {
def selectDynamic(name: String) = peopleByName(name)
}

Related

Scala - Creating a new list by combining the contents of one list and the hierarchy of another

I am trying to create a new list that combines the contents of one single-level list with the hierarchy of another nested list and link them by the IDs of objects in both lists. Note: the ID for the BChain object is stored within the ItemObj inside it.
I have three objects:
ItemObj(id: String, stored: DateTime)
BChain(item: ItemObj, List[BChain])
AChain(aid: String, List[AChain])
I have two lists:
val nestedList: List[AChain]
val singleLevelList: List[BChain]
I want the second list to have the hierarchy of the first, but still contain it's own elements. Therefore, BChain should include the original ItemObj and List[BChain] properties (including all of ItemObj's original property data - ID and DateTime) when it is put into the newly desired list output.
So Instead of (input):
val nestedList = List(
AChain("123", List(AChain("456", [])),
AChain("789", [])
)
val singleLevelList = List(
BChain(ItemObj("123", DateTime), []),
BChain(ItemObj("456", DateTime), []),
BChain(ItemObj("789", DateTime), []))
)
I would like the following output:
val combinedLists = List(
BChain(ItemObj("123", DateTime), List(BChain(ItemObj("456", DateTime), [])),
BChain(ItemObj("789", DateTime), [])
)
as the final list.
Note: There may be more items in the nested list than the single-level list, and if this is the case then the extra items should be ignored. Each of the items in the single-level list should correspond to one of the items in the nested list.
How can I accomplish this?
Any help would be appreciated.
First I had to dummy up a DateTime type and value so that the following would compile.
case class ItemObj(id: String, stored: DateTime)
case class BChain(item: ItemObj, bcl :List[BChain])
case class AChain(aid: String, acl :List[AChain])
Then I changed your singleLevelList into real Scala code and created a Map for fast lookup.
val singleLevelList = List(
BChain(ItemObj("123", DateTime), Nil),
BChain(ItemObj("456", DateTime), Nil),
BChain(ItemObj("789", DateTime), Nil)
)
val sLLMap = singleLevelList.groupBy(_.item.id)
Next a recursive method to change all AChains to BChains.
def a2b(aLst :List[AChain]) :List[BChain] =
aLst.map(a => BChain(sLLMap(a.aid).head.item, a2b(a.acl)))
Now to test it.
val nestedList = List(
AChain("123", List(AChain("456", Nil))),
AChain("789", Nil)
)
a2b(nestedList) //appears to work
Of course this will throw if nestedList has an A without a corresponding B in the singleLevelList.
Maybe something like this is what you want?
final case class ItemObj(id: String, stored: DateTime)
final case class BChain(item: ItemObj, list: List[BChain])
final case class AChain(aid: String, list: List[AChain])
def combine(as: List[AChain], bs: List[BChain]): List[BChain] = {
val asMap = as.iterator.map(a => a.aid -> a.list).toMap
def toBChain(a: AChain): BChain =
BChain(
item = ItemObj(id = a.aid, stored = ???),
list = a.list.map(toBChain)
)
bs.map {
case BChain(item, list) =>
val newElements =
asMap
.getOrElse(key = item.id, default = Nil)
.map(toBChain)
BChain(
item,
list ::: newElements
)
}
}
This code will work even if root element of nested list is absent in single level list
final case class ItemObj(id: String, stored: DateTime = null)
final case class AChain(aid: String, children: List[AChain] = List())
final case class BChain(bid: ItemObj, children: List[BChain] = List())
val nestedList = List(
AChain("123", List(AChain("456"))),
AChain("789")
)
def getCombineList(nestedList: List[AChain], singleList: List[BChain]): List[BChain] = {
val singleListMap = singleList.groupBy(_.bid.id)
def combine(items: List[AChain]): List[BChain] = {
items flatMap {item =>
val children = combine(item.children)
val parent = singleListMap.get(item.aid).map(v =>
List(v.head.copy(children = children))
).getOrElse(
children
)
parent
}
}
combine(nestedList)
}
val s = getCombineList(nestedList, List(BChain(ItemObj("456")), BChain(ItemObj("789"))))

Scala Summing Values of a list of Tuples in style (String,Int) based on _._1

I create a list of tuples via taking a String name and matching it to an accompanying int value.
I want to be able to sum those int values in the tuple in the case that there are multiple strings of the same name. My current approach follows this utilization of groupby which if I understand right is returning me a Map with keys based upon _ . _ 1 and list of values:
def mostPopular(data: List[List[String]]): (String, Int) = {
//take the data and create a list[(String,Int)]
val nameSums = data.map(x => x(1) -> x(2).toInt)
//sum the values in _._2 based on same elements in _._1
val grouped = nameSums.groupBy(_._1).foldLeft(0)(_+_._2)
}
I've seen other solution that have dealt with averaging different values of tuples but they haven't explained how to sum values that fall under the same name
In your case value (see below code snippet) is a list of (String, Int) do value.map(_._2).sum or value.foldLeft(0)((r, c) => r + (c._2))
nameSums.groupBy(_._1).map { case (key, value) => key -> (value.map(_._2)).sum}
Scala REPL
scala> val nameSums = List(("apple", 10), ("ball", 20), ("apple", 20), ("cat", 100))
nameSums: List[(String, Int)] = List((apple,10), (ball,20), (apple,20), (cat,100))
scala> nameSums.groupBy(_._1).map { case (key, value) => key -> (value.map(_._2)).sum}
res15: scala.collection.immutable.Map[String,Int] = Map(cat -> 100, apple -> 30, ball -> 20)

How to retain some key value pairs of a List[T] in scala?

I have a List[T] where the datas inside are like below:
[0]- id : "abc", name: "xyz", others: "value"
[1]- id : "bcd", name: "dsf", others: "value"
Now I want to return the same List[T] but with the id and names i.e the returning List will be:
[0]- id : "abc", name: "xyz"
[1]- id : "bcd", name: "dsf"
I tried with below code:
var temps = templates.map(x => List("id" -> x.id, "name" -> x.name))
But it it produces List inside List i.e:
[0]-
[0] id : "abc"
[1] name: "xyz"
[1]-
[0] id : "bcd"
[1] name: "dsf"
I tried with tuple also but in vain. How can i just map my list such that everything is cleaned out except the id and name value pair??
Unless you want to define a new class with just the id and name fields, I think tuples would be your best bet:
scala> case class obj(id: String, name: String, others: String)
defined class obj
scala> val l = List(new obj("abc", "xyz", "value"), new obj("bcd", "dsf", "value"))
l: List[obj] = List(obj(abc,xyz,value), obj(bcd,dsf,value))
scala> l.map(x => (x.id, x.name))
res0: List[(String, String)] = List((abc,xyz), (bcd,dsf))
Also you are actually using tuples in your example, the -> syntax creates tuples in Scala:
scala> "a" -> "b"
res1: (String, String) = (a,b)
Here is the "define another class" option:
scala> case class obj2(id: String, name: String){
| def this(other: obj) = this(other.id, other.name)
| }
defined class obj2
scala> l.map(new obj2(_))
res2: List[obj2] = List(obj2(abc,xyz), obj2(bcd,dsf))
Given that the List[T] is a List[Map], then you may able to do the following:
//Remove the "others" key.
val temps = templates.map( map => {map - "others"})
I think its pretty clear that your list contains objects with three members: id, name, others. Hence you access them by using
{x => x.name}
What I am not so sure about, is how you imagine your end result. You obviously need some data structure, that holds the members.
You realized yourself, that its not very nice to store each objects members in a list inside the new list, but you seem to not be ok with tuples?
I can imagine, that tuples are just what you want.
val newList = oldList.map{e => e.id -> e.name}
results in alist like this:
List(("id_a", "name_a"), ("id_b", "name_b"), ("id_c","name_c"))
and can be accessed like this(for example):
newList.head._1
for the first tuples ids, and
newList.head._2
for the first tuples name.
Another option could be mapping into a map, since this looks pretty much like, what you want in the first place:
newMap = oldList.map{e => e.id -> e.name}.toMap
This way you can access members like this newMap("key") or safer: newMap.get("key") which returns an option and wont end up in an exception, if the key doesn't exist.

Scala list append a class in list and retrieve item

new to scala so missing basic of list.
I'm trying to make a list of following class
case class Person (val name: String, val age:Int, val email: String)
How do I make a list immutable list of Person class?
I tried:-
val list: List[Person] = List(Person("",0,"")) // compilation ok
list.::(person) // compile ok
But when I check size it is 1. It should be 2. Also I don't see the added person in the list.
Please let me know how do I make list of Person class, inset new person and then retrieve person if either age or name or email or by index as well.
Please don't refer me to api doc http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List
It is hard to understand :(
Try this.
val list2 = list.::(person)
Remember list is immutable.
list2 should have size 2.
Keeping in mind immutable lists as aforementioned, for
case class Person (val name: String = "",
val age:Int = 0,
val email: String = "")
(note you can set construction defaults to each field) and
val list: List[Person] = List(Person("",0,"")) // compilation ok
we can construct a second List[Person] also with the List concatenation operator ++ as follows,
val list2 = list ++ List(Person(age=1))
list2: List(Person(,0,), Person(,1,))
To retrive a Person by a given criterium, for instance in this example, by age, consider collect as follows,
val list3 = list2.collect { case p#Person(_, age, _) if age == 1 => p }
List[Person] = List(Person(,1,))
Here we extract the field of interest, age, and check for a condition (whether it equates to 1), thus we select those entries in list2 that hold the condition; to deliver such entries, we use a bind p on each Person instance; perhaps more canonical (and unnecessarily repetitive) approach would be
val list3 = list2.collect {
case Person(name, age, email) if age == 1 => Person(name, age, email) }
A similar approach relies in filter as follows,
list2.filter(p => p.age == 1)
And with a for comprehension, like this
for ( p#Person(n, a, e) <- list2 if a == 1 ) yield p
A more elaborated approach to defining a selection over a list of entries includes the definition of a method that takes a predicate as argument and evaluates it against each entry; in addition this can be encapsulated in an implicit class for ease of use; thus consider this example,
implicit class PersonsOps( val persons: List[Person] ) extends AnyVal {
def select(pred: Person => Boolean) = persons.filter(pred)
}
and so we can define an anonymous function in the call to byAge, namely (x: Person) => x.age == 1; hence
list2.select( (x: Person) => x.age == 1 )
or equivalently
list2.select( _.age == 1 )
Note in this illustrative example select and filter are equivalent or interchangeable, yet select may be enriched/extended with additional requirements.
As Soumya Simanta said, you have an immutable list, so you have to assign the List returned by the :: operator to a new val. By the way you can use following notation
val list2 = person :: list
which is equivalent to
val list2 = list.::(person)
To get the information of your Person instances you can use the following line:
list2 foreach { p: Person => println(p.email) }
foreach is a method of List. It applies a function (e.g. { p: Person => println(p.email) }) to all elements of the list.
list.::(person)
retrieves a new list, containing both elements: the old and the new. However, the operation does not change your list called list. Try this way:
var list: List[Person] = List(Person("",0,"")) // compilation ok
list = list.::(person) // compile ok

How to convert integer list to map removing duplicates?

Let's say that I have a Map of strings -> List of Integers. I would like to create a function which takes in as a parameter a List of strings and returns all the integers correlating to all the string in that list. I.e. if the Map X contains the following mappings:
database = [("Bob",[1,2,3]),("John",[1,5,6]),("Trevor",[4,5,7])]
If this function takes in ["Bob","John"] as the list of names, it should return,
[1,2,3,5,6]
Since Bob correlates to 1,2,3 and John correlates to 1,5,6 (same entries for both names aren't duplicated). I also would like to not introduce a mutable variable if I don't have to, thus leading me to believe a for comprehension that yields this list of number values would be the best way to achieve this, but I'm not sure how.
If you want to use a for-comprehension you can so this:
val result = for {
key <- keys
nums <- map.get(key).toSeq
num <- nums
} yield num
result.distinct
Explanation:
for each key in the list try to get an entry and convert it to a Seq (necessary because flatMap expects a Seq in this case) and add every number in the list to the result. If the key is not present in the map the collection will be empty and therefore not yield any results. At the end call distinct to remove the duplicates.
val myMap = Map("Bob" -> List(1,2,3), "John" -> List(1,5,6), "Trevor" -> List(4,5,7))
val names = List("Bob", "John")
You can add default value to Map using method withDefaultValue:
val mapWithDefaul = myMap withDefaultValue Nil
Then you could use Map as function in flatMap:
names.flatMap(mapWithDefaul).distinct
// List(1, 2, 3, 5, 6)
Let
val db = Map("Bob" -> List(1,2,3), "John" -> List(1,5,6), "Trevor" -> List(4,5,7))
val names = List("Bob", "John")
Then a similar approach to #senia's using flatMap,
implicit class mapCorr[A,B](val db: Map[A,List[B]]) extends AnyVal {
def corr(keys: List[A]): List[B] = {
keys.flatMap{ k => db get k }.flatten.distinct
}
}
and
scala> db.corr(keys)
res0: List[Int] = List(1, 2, 3, 5, 6)
Here we allow for key lists of type A and maps from type A to type List[B] .
val myset = Set("Bob","John")
val database = Map(("Bob"->List(1,2,3)),("John"->List(1,5,6)),("Trevor"->List(4,5,7)))
val ids = database.filter(m => myset.contains(m._1)).map(_._2).flatten.toList.distinct
outputs:
ids: List[Int] = List(1, 2, 3, 5, 6)
Something like:
val result = database.filter(elem => list.contains(elem._1)).foldLeft(List())((res,elem) => res ++ elem._2)
where list is the input list of names.