I can group the list of students using the below lamda expression. The result would be grouping the student list grouped by "Department" and then grouped by "gender".
Map<String, Map<String, List<Student>>> studentCatg = studentList.stream().collect(groupingBy(Student::getDepartment, groupingBy(Student::getGender)));
Now i need to get a Single List from the above MAP that should contain students of particular department. is there anything reverse of grouping ?
You can get EntrySet for given department, combine Map<String, List<Student>> to List<List<Student>> by map to entry set value and then flatMap to a List<Student>, something like this:
String department = "department name";
List<Student> students = studentCatg.get(department)
.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList())
Reverting to initial list
List<Student> students = studentCatg.values()
.stream()
.map(Map::values)
.flatMap(Collection::stream)
.flatMap(Collection::stream)
.collect(Collectors.toList());
I found this entry when I was looking for a way to invert a groupingBy map but as I didn't find a common solution I constructed my own so just in case it is of interest.
public static <K, V> Map<V, List<K>> invertGroupByMap(final Map<K, List<V>> src)
{
return src.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(a -> new SimpleImmutableEntry<>(a, e.getKey())))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
}
Related
I have a map where the values are strings and the keys are lists:
Map<String, List<BoMLine>> materials
I'd like to filter this map by its values; something like this:
materials.entrySet().stream()
.filter(a -> a.getValue().stream()
.filter(l -> MaterialDao.findMaterialByName(l.getMaterial()).ispresent)
But it's not working for me. Does anybody have an idea?
If I understand your filtering criteria correctly, you want to check if the filtered Stream you produced from the value List has any elements, and if so, pass the corresponding Map entry to the output Map.
Map<String, List<BoMLine>>
filtered = materials.entrySet()
.stream()
.filter(a->a.getValue()
.stream()
.anyMatch(l->MaterialDao.findMaterialByName(l.getMaterial())))
.collect(Collectors.toMap(e->e.getKey(),e->e.getValue()));
This is assuming MaterialDao.findMaterialByName(l.getMaterial()) returns a boolean.
Generally, this is how you can filter a map by its values:
static <K, V> Map<K, V> filterByValue(Map<K, V> map, Predicate<V> predicate) {
return map.entrySet()
.stream()
.filter(entry -> predicate.test(entry.getValue()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
Call it like this:
Map<String, Integer> originalMap = new HashMap<>();
originalMap.put("One", 1);
originalMap.put("Two", 2);
originalMap.put("Three", 3);
Map<String, Integer> filteredMap = filterByValue(originalMap, value -> value == 2);
Map<String, Integer> expectedMap = new HashMap<>();
expectedMap.put("Two", 2);
assertEquals(expectedMap, filteredMap);
All the previous answers provide a solution that creates a new Map. This may be inefficient if you want to remove just a few entries.
The following removes the elements from the existing Map:
Map<String, Integer> myMap = new HashMap<>();
myMap.put("One", 1);
myMap.put("Two", 2);
myMap.put("Three", 3);
myMap.keySet().removeAll(
myMap.entrySet().stream()
.filter(a->a.getValue().equals(2))
.map(e -> e.getKey()).collect(Collectors.toList()));
System.out.println(myMap);
Prints:
{One=1, Three=3}
How does it work? It collects all keys that need to be removed into a temporary List, then removes them from the original Map in one go. A temporary List is required because modifying a Map invalidates streams and iterators. However, building a new Map is potentially a much more costly operation that building a List.
If you want to remove a lot of entries (50% or more), then it's better to build a new Map indeed.
I finally made it work. My second filter was completely useless. The answer is like this:
Map<String, List<BoMLine>>
filtered = materials.entrySet()
.stream()
.filter(a -> a
.getValue()
.stream()
.allMatch(
b -> MaterialDao.findMaterialByName(
b.getMaterial()).isPresent()))
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()))
In my humble opinion, one of the most concise java 8 code may look like this :
public Map<String, ReportPlaceholderValue> getAllPlaceholders() {
Map<String, ReportPlaceholderValue> result = chamberDetailsPlaceHolders;
result.entrySet().stream().filter( e -> !(e.getValue() instanceof StringReportPlaceholderValue) )
.map( Map.Entry::getKey )
.collect( Collectors.toList() ).forEach( result.keySet()::remove );
return result;
}
I need to find a way to convert list of arbitrary values to another list
AutoMapper works if destination type is ICollection<> because it's creating an instance and populating it with Add, but my type is immutable list 'a list
So if I create list of ints:
let ints = [1; 2; 3]
And try to map it to ResizeArray<int64> (synonym to List<T>) with
mapper.Map<ResizeArray<int64>>(ints)
it will work, but if I try to map it to int64 list with
mapper.Map<int64 list>
then it will fail.
I've found a solution that will convert successfully, but it will work only with explicitly defined types
let cfg = MapperConfiguration(
fun c ->
c.CreateMap<int, int64>() |> ignore
c.CreateMap<int list, int64 list>()
.ConvertUsing(
fun source _ (cfg: ResolutionContext) ->
source
|> Seq.map cfg.Mapper.Map<int, int64>
|> Seq.toList))
So question is: How to write type converter that will convert 'a list to 'b list without explicitly defining all possible combinations of these types?
I've finally found solution. All I needed is to look at source code of this ReadOnlyCollection mapper
Solution is not perfect, because collection items transformed and inserted into System.Collections.Generic.List and afterwards converted to Microsoft.FSharp.Collections.FSharpList, which have some overhead. But at least it's working
using System.Collections.Generic;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using AutoMapper.Mappers;
using AutoMapper.Internal;
using static AutoMapper.Internal.CollectionMapperExpressionFactory;
using Microsoft.FSharp.Collections;
public class SeqToFSharpListMapper : EnumerableMapperBase
{
public override bool IsMatch(TypePair context)
=> context.SourceType.IsEnumerableType()
&& context.DestinationType.FullName.StartsWith("Microsoft.FSharp.Collections.FSharpList`1");
public override Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, IMemberMap memberMap,
Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
var listType = typeof(List<>).MakeGenericType(ElementTypeHelper.GetElementType(destExpression.Type));
var list = MapCollectionExpression(configurationProvider, profileMap, memberMap, sourceExpression, Default(listType), contextExpression, typeof(List<>), MapItemExpr);
return Call(typeof(ListModule).GetMethod(nameof(ListModule.OfSeq)).MakeGenericMethod(destExpression.Type.GenericTypeArguments[0]), list);
}
}
And F#
override _.MapExpression (configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression) =
let listType = typedefof<System.Collections.Generic.List<_>>.MakeGenericType(ElementTypeHelper.GetElementType destExpression.Type)
let list = MapCollectionExpression(configurationProvider, profileMap, memberMap,
sourceExpression, Default(listType), contextExpression,
typedefof<System.Collections.Generic.List<_>>,
MapItem(fun c p s d ctx i -> MapItemExpr(c, p, s, d, ctx, &i))) // compiler require explicit lambda
upcast Call(typedefof<obj list>.Assembly // don't want to use AssemblyQualifiedName
.GetType("Microsoft.FSharp.Collections.ListModule") // have to use this trick because we can't access ListModule through typeof
.GetMethod("OfSeq")
.MakeGenericMethod(destExpression.Type.GenericTypeArguments.[0]),
list)
For example, given a map like below:
{
"k1": {
"k2": {
"k3": {
"k4": "v"
}
}
}
}
and a field list ["k1","k2","k3"], I need to retrieve the part {"k4": "v"}.
Below is my java7-style code:
// Ignore the map building code.
Map map1 = new HashMap();
Map map2 = new HashMap();
Map map3 = new HashMap();
Map map4 = new HashMap();
map4.put("k4", "v");
map3.put("k3", map4);
map2.put("k2", map3);
map1.put("k1", map2);
Map map = map1;
System.out.println(map); //=> {k1={k2={k3={k4=v}}}}
// Code to be transformed to java8 style
List<String> fields = Arrays.asList("k1", "k2", "k3");
for(String field: fields) {
map = (Map) map.get(field);
}
System.out.println(map); //=> {k4=v}
Then how to transform above code to java 8 stream style?
I don’t think that there is any benefit in converting this into a functional style; the loop is fine and precisely expresses what you are doing.
But for completeness, you can do it the following way:
map = (Map)fields.stream()
.<Function>map(key -> m -> ((Map)m).get(key))
.reduce(Function.identity(), Function::andThen).apply(map);
This converts each key to a function capable of doing a map lookup of that key, then composes them to a single function that is applied to you map. Postponing the operation to that point is necessary as functions are not allowed to modify local variables.
It’s also possible to fuse the map operation with the reduce operation, which allows to omit the explicit type (<Function>):
map = (Map)fields.parallelStream()
.reduce(Function.identity(), (f, key)->f.andThen(m->((Map)m).get(key)), Function::andThen)
.apply(map);
Maybe you recognize now, that this is a task for which a simple for loop is better suited.
How about?
fields.stream().reduce(map1, (m, key) -> (Map) m.get(key), (a, b) -> a);
I'm trying to insert new key-value pair in dictionary, which nested in another one Dictionary:
var dict = Dictionary<Int, Dictionary<Int, String>>()
dict.updateValue([1 : "one", 2: "two"], forKey: 1)
dict[1]?[1] // {Some "one"}
if var insideDic = dict[1] {
// it is a copy, so I can't insert pair this way:
insideDic[3] = "three"
}
dict // still [1: [1: "one", 2: "two"]]
dict[1]?[3] = "three" // Cannot assign to the result of this expression
dict[1]?.updateValue("three", forKey: 3) // Could not find a member "updateValue"
I believe should be a simple way to handle it, but I spent an hour and still can't figure it out.
I can use NSDictionary instead, but I really like to understand how I should manage nested Dictionaries in Swift?
Dictionarys are value types so are copied on assignment. As a result you are going to have to get the inner dictionary (which will be a copy), add the new key, then re-assign.
// get the nested dictionary (which will be a copy)
var inner:Dictionary<Int, String> = dict[1]!
// add the new value
inner[3] = "three"
// update the outer dictionary
dict[1] = inner
println(dict) // [1: [1: one, 2: two, 3: three]]
You could use one of the new utility libraries such as ExSwift to make this a bit simpler:
dict[1] = dict[1]!.union([3:"three"])
This uses the union method that combines two dictionaries.
I would like to get the names and values from an enum type in D2. I know I can get enum values using std.traits but what about the names?
Given:
enum lst
{
apple,
bottle,
orange,
blue
}
I would like to get an associative array like.
string lstmap[int] = [1:"apple", 2:"bottle", 3:"orange", 4:"blue"].
The answer is yes. The solution, as someone showed me is:
foreach (i, member; __traits(allMembers, lst)) {
lstmap[cast(int) __traits(getMember, lst, member)] = member;
}
foreach (i, member; __traits(allMembers, lst)) {
lstmap[cast(int) __traits(getMember, lst, member)] = member;
}
(copied from question as community wiki)
In case you want this solely for purposes of value-to-string convertation, consider using std.conv.to!string(lst.orange) — will evaluate to "orange".
//ENUMList is the name of Enum
var values = (ENUMList[])Enum.GetValues(typeof(ENUMList));
var query = from name in values
select new EnumData//EnumData is a Modal or Entity
{
ID = (short)name,
Name = GetEnumDescription(name)//Description of Particular Enum Name
};
return query.ToList();