Combining Two Lists by Key Using Thrust

Combining two lists by key using Thrust

Well, I'm not sure this is the best method (@m.s. usually comes up with better approaches than I), but one possible approach would be (method 1):

  1. set_intersection_by_key(Left,Right)
  2. set_intersection_by_key(Right,Left)
  3. Take the values outputs from step 1 and step 2, and perform a transform on them to multiply the values results together (or whatever other mathematical operation you'd like to perform on the corresponding values results from step 1 and step 2).

I don't know what your skill level is with thrust but I can provide a trivial worked example of the above if desired.

Another possible approach (method 2):

  1. merge_by_key the two lists together
  2. Perform a transform using two versions of the resultant list from step 1: The first consisting of [first, last-1) and the second consisting of [first+1, last). This will require a special functor that takes a zipped version of the keys and values, and compares the two keys presented. If it is a match, output the desired mathematical operation on the two presented values; if it is not a match, output some kind of marker or known illegal value. (If such an illegal value is impossible to construct, we can zip a 3rd marker array in if needed.)
  3. Do a remove_if on the output of step 2, to compact the result down to the desired result, removing all value entries that are illegal, or else removing all value entries that are indicated by the marker array.

My sense is the second method might be faster, but I haven't carefully thought through it. In any event, it's better to benchmark test cases than to work off of (my) intuition.

Based on a comment below, here is a description of what is happening starting with the 2nd step of method 2, using your example dataset:

The output of step 1 (the merge_by_key operation) would look like something like this:

keys:   { 1, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7 };
values: { 3, 2, 4, 1, 1, 1, 2, 4, 1, 1, 2 };

Let's construct two versions, the first being "the item" and the second being "the next item to the right":

keys1:   { 1, 1, 2, 3, 4, 4, 5, 5, 6, 6 };
values1: { 3, 2, 4, 1, 1, 1, 2, 4, 1, 1 };

keys2: { 1, 2, 3, 4, 4, 5, 5, 6, 6, 7 };
values2: { 2, 4, 1, 1, 1, 2, 4, 1, 1, 2 };

The actual "construction" is trivial. keys1 is just [keys.begin(), keys.end()-1), and keys2 is just [keys.begin()+1, keys.end()). And likewise for values1 and values2.

We'll zip keys1 and values1 together and we'll zip keys2 and values2 together. Then we'll pass these two zipped entities to a transform operation that has a special functor that will do the following:

If keys1 == keys2, do the desired math operation on the values1 and values2 quantities, and place a one in the marker array. If not, place a 0 in a marker array. The output of this operation would be:

 keys:   { 1, 2, 3, 4, 4, 5, 5, 6, 6, 7 };
values: { 6, 4, 1, 1, 1, 8, 4, 1, 1, 2 };
marker: { 1, 0, 0, 1, 0, 1, 0, 1, 0, 0 };

Now zip the 3 vectors above together, and pass that to remove_if. The remove_if functor would indicate removal of any items for which marker == 0, leaving:

 keys:   { 1, 4, 5, 6 };
values: { 6, 1, 8, 1 };
marker: { 1, 1, 1, 1 };

Here is a fully worked example demonstrating both methods:

$ cat t1007.cu
#include <iostream>
#include <thrust/device_vector.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/set_operations.h>
#include <thrust/transform.h>
#include <thrust/functional.h>
#include <thrust/copy.h>
#include <thrust/merge.h>
#include <thrust/remove.h>
#include <assert.h>

struct mark_mpy_func
{
template <typename T1, typename T2>
__host__ __device__
int operator()(T1 &z1, T2 &z2){
int res = 0;
if (thrust::get<0>(z1) == thrust::get<0>(z2)){
res = thrust::get<1>(z1) * thrust::get<1>(z2);
thrust::get<2>(z1) = 1;}
return res;
}
};

struct mtest_func
{
__host__ __device__
bool operator()(int t){
if (t == 0) return true;
return false;
}
};

int main(){

int Lkeys[] = { 1, 2, 4, 5, 6 };
int Lvals[] = { 3, 4, 1, 2, 1 };
int Rkeys[] = { 1, 3, 4, 5, 6, 7 };
int Rvals[] = { 2, 1, 1, 4, 1, 2 };

size_t Lsize = sizeof(Lkeys)/sizeof(int);
size_t Rsize = sizeof(Rkeys)/sizeof(int);

thrust::device_vector<int> Lkeysv(Lkeys, Lkeys+Lsize);
thrust::device_vector<int> Lvalsv(Lvals, Lvals+Lsize);
thrust::device_vector<int> Rkeysv(Rkeys, Rkeys+Rsize);
thrust::device_vector<int> Rvalsv(Rvals, Rvals+Rsize);

// method 1

thrust::device_vector<int> Lkeysvo(Lsize);
thrust::device_vector<int> Lvalsvo(Lsize);
thrust::device_vector<int> Rkeysvo(Rsize);
thrust::device_vector<int> Rvalsvo(Rsize);

size_t Lsizeo = thrust::set_intersection_by_key(Lkeysv.begin(), Lkeysv.end(), Rkeysv.begin(), Rkeysv.end(), Lvalsv.begin(), Lkeysvo.begin(), Lvalsvo.begin()).first - Lkeysvo.begin();
size_t Rsizeo = thrust::set_intersection_by_key(Rkeysv.begin(), Rkeysv.end(), Lkeysv.begin(), Lkeysv.end(), Rvalsv.begin(), Rkeysvo.begin(), Rvalsvo.begin()).first - Rkeysvo.begin();

assert(Lsizeo == Rsizeo);
thrust::device_vector<int> res1(Lsizeo);
thrust::transform(Lvalsvo.begin(), Lvalsvo.begin()+Lsizeo, Rvalsvo.begin(), res1.begin(), thrust::multiplies<int>());

std::cout << "Method 1 result:" << std::endl << "keys: ";
thrust::copy_n(Lkeysvo.begin(), Lsizeo, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl << "vals: ";
thrust::copy_n(res1.begin(), Lsizeo, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl;

// method 2

thrust::device_vector<int> Mkeysv(Lsize + Rsize);
thrust::device_vector<int> Mvalsv(Lsize + Rsize);

thrust::merge_by_key(Lkeysv.begin(), Lkeysv.end(), Rkeysv.begin(), Rkeysv.end(), Lvalsv.begin(), Rvalsv.begin(), Mkeysv.begin(), Mvalsv.begin());
thrust::device_vector<int> marker(Lsize + Rsize - 1);
thrust::device_vector<int> res2(Lsize + Rsize - 1);
thrust::transform(thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.begin(), Mvalsv.begin(), marker.begin())), thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.end()-1, Mvalsv.end()-1, marker.end())), thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.begin()+1, Mvalsv.begin()+1)), res2.begin(), mark_mpy_func());
size_t rsize2 = thrust::remove_if(thrust::make_zip_iterator(thrust::make_tuple( Mkeysv.begin(), res2.begin())), thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.end()-1, res2.end())), marker.begin(), mtest_func()) - thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.begin(), res2.begin()));
std::cout << "Method 2 result:" << std::endl << "keys: ";
thrust::copy_n(Mkeysv.begin(), rsize2, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl << "vals: ";
thrust::copy_n(res2.begin(), rsize2, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl;

return 0;
}

$ nvcc -o t1007 t1007.cu
$ ./t1007
Method 1 result:
keys: 1,4,5,6,
vals: 6,1,8,1,
Method 2 result:
keys: 1,4,5,6,
vals: 6,1,8,1,
$

If it is acceptable to use a marker value (say, -1) in the output data to inform the remove_if operation, then the separate marker array can be dispensed with. Here's a modified version of method 2 that works this way:

$ cat t1007.cu
#include <iostream>
#include <thrust/device_vector.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/transform.h>
#include <thrust/copy.h>
#include <thrust/merge.h>
#include <thrust/remove.h>

#define MARK_VAL -1

struct mark_mpy_func
{
template <typename T1, typename T2>
__host__ __device__
int operator()(T1 &z1, T2 &z2){
int res = MARK_VAL;
if (thrust::get<0>(z1) == thrust::get<0>(z2)){
res = thrust::get<1>(z1) * thrust::get<1>(z2);}
return res;
}
};

struct mtest_func
{
template <typename T>
__host__ __device__
bool operator()(T t){
if (thrust::get<1>(t) == MARK_VAL) return true;
return false;
}
};

int main(){

int Lkeys[] = { 1, 2, 4, 5, 6 };
int Lvals[] = { 3, 4, 1, 2, 1 };
int Rkeys[] = { 1, 3, 4, 5, 6, 7 };
int Rvals[] = { 2, 1, 1, 4, 1, 2 };

size_t Lsize = sizeof(Lkeys)/sizeof(int);
size_t Rsize = sizeof(Rkeys)/sizeof(int);

thrust::device_vector<int> Lkeysv(Lkeys, Lkeys+Lsize);
thrust::device_vector<int> Lvalsv(Lvals, Lvals+Lsize);
thrust::device_vector<int> Rkeysv(Rkeys, Rkeys+Rsize);
thrust::device_vector<int> Rvalsv(Rvals, Rvals+Rsize);

// method 2

thrust::device_vector<int> Mkeysv(Lsize + Rsize);
thrust::device_vector<int> Mvalsv(Lsize + Rsize);

thrust::merge_by_key(Lkeysv.begin(), Lkeysv.end(), Rkeysv.begin(), Rkeysv.end(), Lvalsv.begin(), Rvalsv.begin(), Mkeysv.begin(), Mvalsv.begin());
thrust::device_vector<int> res2(Lsize + Rsize - 1);
thrust::transform(thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.begin(), Mvalsv.begin())), thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.end()-1, Mvalsv.end()-1)), thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.begin()+1, Mvalsv.begin()+1)), res2.begin(), mark_mpy_func());
size_t rsize2 = thrust::remove_if(thrust::make_zip_iterator(thrust::make_tuple( Mkeysv.begin(), res2.begin())), thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.end()-1, res2.end())), mtest_func()) - thrust::make_zip_iterator(thrust::make_tuple(Mkeysv.begin(), res2.begin()));
std::cout << "Method 2 result:" << std::endl << "keys: ";
thrust::copy_n(Mkeysv.begin(), rsize2, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl << "vals: ";
thrust::copy_n(res2.begin(), rsize2, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl;

return 0;
}

$ nvcc -o t1007 t1007.cu
$ ./t1007
Method 2 result:
keys: 1,4,5,6,
vals: 6,1,8,1,
$

Thrust -- sort two vectors by key

This can be done with a single sort_by_key operation, followed by a "rearrangement" of the sorted values.

The "keys" (things we sort on) will be a zip_iterator combining your actual values to be sorted, along with a row indicator. The sort functor will be arranged to sort first by row, then by value within the row. The "values" (things that get moved along with the keys) will be a column index in each row.

These "values" after sorting can be rearranged to be our indicator of "rank" within a row.

We can use a numerical example following the 3rd row of your matrix:

before sort:

keys:     [2, 4, 2, 0]
values: [0, 1, 2, 3]

after sort:

keys:     [0, 2, 2, 4]
values: [3, 0, 2, 1]

before rearranging:

destination map:      [3, 0, 2, 1]
values: [0, 1, 2, 3]

after rearranging:

destination:          [1, 3, 2, 0]

(as it happens, for the first two rows in your example, there is no change between the sorted values and the destination values)

Here is a worked example:

$ cat t1633.cu
#include <iostream>
#include <thrust/sort.h>
#include <thrust/device_vector.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/iterator/transform_iterator.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/copy.h>

typedef int dtype;

// creates row indices:
// 0 0 0 0 ...
// 1 1 1 1 ...
// 2 2 2 2 ...
struct row_f : public thrust::unary_function<int, int>
{
int ncols;
row_f(int _nc) : ncols(_nc) {};
__host__ __device__
int operator()(int i){
return i/ncols;}
};

// creates column indices:
// 0 1 2 3 ...
// 0 1 2 3 ...
// 0 1 2 3 ...
struct col_f : public thrust::unary_function<int, int>
{
int ncols;
col_f(int _nc) : ncols(_nc) {};
__host__ __device__
int operator()(int i){
return i%ncols;}
};

struct map_f : public thrust::unary_function<thrust::tuple<int, int>, int>
{
int ncols;
map_f(int _nc) : ncols(_nc) {};
__host__ __device__
int operator()(thrust::tuple<int, int> t){
return thrust::get<0>(t) + ncols*thrust::get<1>(t);}
};

struct sort_f
{
template <typename T1, typename T2>
__host__ __device__
bool operator()(T1 k1, T2 k2){
// sort by row first
if (thrust::get<1>(k1) < thrust::get<1>(k2)) return true;
if (thrust::get<1>(k1) > thrust::get<1>(k2)) return false;
// then sort within the row
if (thrust::get<0>(k1) < thrust::get<0>(k2)) return true;
return false;}
};

int main(){

// example data setup
dtype keys[] = {5, 4, 1, 9, 1, 4, 3, 2, 2, 4, 2, 0};
int nrows = 3;
int ds = sizeof(keys)/sizeof(keys[0]);
int ncols = ds/nrows;

thrust::device_vector<dtype> d_keys(keys, keys+ds);
// create values to be moved which is effectively index within row, i.e. column indices
thrust::device_vector<int> d_vals(nrows*ncols);
thrust::sequence(d_vals.begin(), d_vals.end());
thrust::transform(d_vals.begin(), d_vals.end(), d_vals.begin(), col_f(ncols));
// sort
thrust::sort_by_key(thrust::make_zip_iterator(thrust::make_tuple(d_keys.begin(), thrust::make_transform_iterator(thrust::counting_iterator<int>(0), row_f(ncols)))), thrust::make_zip_iterator(thrust::make_tuple(d_keys.end(), thrust::make_transform_iterator(thrust::counting_iterator<int>(nrows*ncols), row_f(ncols)))), d_vals.begin(), sort_f());
// rearrange
thrust::device_vector<int> d_rank(nrows*ncols);
thrust::copy_n(thrust::make_transform_iterator(thrust::counting_iterator<int>(0), col_f(ncols)), nrows*ncols, thrust::make_permutation_iterator(d_rank.begin(), thrust::make_transform_iterator(thrust::make_zip_iterator(thrust::make_tuple(d_vals.begin(), thrust::make_transform_iterator(thrust::counting_iterator<int>(0), row_f(ncols)))), map_f(ncols))));
// print results
thrust::copy_n(d_rank.begin(), ncols*nrows, std::ostream_iterator<int>(std::cout, ","));
std::cout << std::endl;
}
$ nvcc -o t1633 t1633.cu
$ ./t1633
2,1,0,3,0,3,2,1,1,3,2,0,
$

How do I combine two lists in Dart?

You can use:

var newList = new List.from(list1)..addAll(list2);

If you have several lists you can use:

var newList = [list1, list2, list3].expand((x) => x).toList()

As of Dart 2 you can now use +:

var newList = list1 + list2 + list3;

As of Dart 2.3 you can use the spread operator:

var newList = [...list1, ...list2, ...list3];

Output from reduce_by_key() as a function of two reduced vectors

Meanwhile, what am I doing wrong?

thrust::reduce (or thrust::reduce_by_key) will perform a parallel reduction. This parallel reduction requires a reduction operator that can be applied pairwise. To take a very simple example, suppose we want to reduce 3 elements (E1, E2, and E3) and we have a binary operation (bOp) that we will use to define the reduction operation. Thrust might do something like this:

E1       E2      E3
\ /
bOp
\ /
bOp
|
result

That is, the binary op will be used to combine or "reduce" elements E1 and E2 into a single temporary partial result, and this result will be fed back into the binary op combined with element E3 to produce the final result.

The implication of this is that the output of the binary op (and therefore the output type of the values output iterator) must match its input type (and therefore the input type of the values input iterator).

But your binary op does not meet this requirement, nor do the types of the iterators you have passed for values input and values output:

  new_end = thrust::reduce_by_key(keys.begin(), keys.end(),
make_zip_iterator(make_tuple(vals.begin(), other_vals.begin())),
/* dereferencing the above iterator produces an <int, int> tuple */
new_keys.begin(),
output.begin(),
/* dereferencing the above iterator produces an int */
binary_pred,
BinaryTupleOp() );

I believe the above general requirement for the binary op (i.e. for the values input and output iterator types) is expressed in the thrust docs for this function as:

InputIterator2's value_type is convertible to OutputIterator2's value_type.

I can think of two approaches to solve the problem you indicated here:

Oᵢ = Rᵢ / Sᵢ, where Rᵢ and Sᵢ are vectors reduced by the same key, and Oᵢ is the corresponding output vector.

the first of which I think you've mentioned already:

  1. Perform a reduce_by_key just on the two values sequences zipped together. This can be done in a single reduce_by_key call. Then pass the zipped result sequence to a thrust::transform call to perform the elementwise division.

  2. If you're desperate to accomplish everything in a single thrust call, then the clever output iterator work done by @m.s. here may be a possible approach, to perform the transform operation on the values output. (edit: after some study, I'm not sure this method can be used with a reduction)

If you didn't like either of the above suggestions, it's possible to realize your desired reduction in a single call to reduce_by_key, at the expense of some additional storage (and arguably some wasted operations). The basic idea is to organize around a 3-tuple instead of a 2-tuple. At each reduction step, we will combine (sum) the corresponding (first, and second) components of our lhs and rhs tuples, storing these results in the first and second positions of the output tuple. In addition, we will compute the value of the 3rd position of the output tuple as the result of the division of the first and second positions of the output tuple. You are doing int operations here, including division, so I chose to modify your input data slightly for easy result verification. Here is a worked example, based loosely on your code:

$ cat t1143.cu
#include <thrust/device_vector.h>
#include <thrust/reduce.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/iterator/constant_iterator.h>
#include <iostream>

using namespace std;
using namespace thrust;
typedef tuple<int,int,int> Tuple;

struct BinaryTupleOp : public thrust::binary_function<const Tuple &, const Tuple &, Tuple>
{
__host__ __device__
Tuple operator()(const Tuple & lhs, const Tuple & rhs) const {
Tuple temp;
get<0>(temp) = get<0>(lhs)+get<0>(rhs);
get<1>(temp) = get<1>(lhs)+get<1>(rhs);
get<2>(temp) = get<0>(temp)/get<1>(temp);

return temp;
}

};

int main(int argc, char ** argv)
{
const int N = 7;

device_vector<int> keys(N);

keys[0] = 1; // represents sorted keys
keys[1] = 1;
keys[2] = 2;
keys[3] = 2;
keys[4] = 3;
keys[5] = 3;
keys[6] = 3;

device_vector<int> vals(N);

vals[0] = 6; // just some random numbers
vals[1] = 3;
vals[2] = 8;
vals[3] = 4;
vals[4] = 5;
vals[5] = 5;
vals[6] = 5;

device_vector<int> other_vals(N);

other_vals[0] = 1; // more randomness
other_vals[1] = 2;
other_vals[2] = 1;
other_vals[3] = 2;
other_vals[4] = 1;
other_vals[5] = 1;
other_vals[6] = 1;

device_vector<int> new_keys(N);
device_vector<int> output(N);

device_vector<int> v1(N);
device_vector<int> v2(N);

thrust::equal_to<int> binary_pred;

int rsize = thrust::get<0>(thrust::reduce_by_key(keys.begin(), keys.end(),
make_zip_iterator(make_tuple(vals.begin(), other_vals.begin(), thrust::constant_iterator<int>(0))),
new_keys.begin(),
make_zip_iterator(make_tuple(v1.begin(), v2.begin(), output.begin())),
binary_pred,
BinaryTupleOp() )) - new_keys.begin();

for (int i = 0; i < rsize; i++){
int key = new_keys[i];
int val = output[i];
std::cout << "key " << key << " sum " << val << endl;
}

return 0;
}
$ nvcc -o t1143 t1143.cu
$ ./t1143
key 1 sum 3
key 2 sum 4
key 3 sum 5
$

Others may have better ideas about how to craft your desired result.

Python - How to merge two lists into a dictionary in a total random order

Here's an approach to randomize everything:

import random

A = ['SW1', 'SW2', 'SW3', 'SW4']
B = ['N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7']

def randomize_to_dictionary(a, b):
copy_a = a[:]
copy_b = b[:]

random.shuffle(copy_a)
random.shuffle(copy_b)

length_a = len(copy_a)
length_b = len(copy_b)

dictionary = {}

start = 0

for index, key in enumerate(copy_a):
end = -1

if index < length_a - 1:
end = random.randint(start + 1, length_b - (length_a - index))

dictionary[key] = copy_b[start:end]

start = end

return dictionary

print(randomize_to_dictionary(A, B))

The above assumes len(B) >= len(A). You should make that an explicit test if you use this code.

EXAMPLES

% python3 test.py
{'SW4': ['N7', 'N2', 'N3'], 'SW2': ['N5'], 'SW1': ['N1'], 'SW3': ['N4']}
% python3 test.py
{'SW3': ['N4', 'N5'], 'SW4': ['N1'], 'SW2': ['N3', 'N6'], 'SW1': ['N2']}
% python3 test.py
{'SW1': ['N4'], 'SW3': ['N3'], 'SW2': ['N7', 'N6', 'N5'], 'SW4': ['N2']}
%

Java 8: Merge 2 String Lists into Map

You can iterate over the indices of the Lists with an IntStream:

Map<String, String> result =
IntStream.range(0,keys.size())
.boxed()
.collect(Collectors.toMap(i -> keys.get(i), i -> values.get(i)));

Merge two array of objects based on a key

You can do it like this -

let arr1 = [    { id: "abdc4051", date: "2017-01-24" },    { id: "abdc4052", date: "2017-01-22" }];
let arr2 = [ { id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }];
let arr3 = arr1.map((item, i) => Object.assign({}, item, arr2[i]));
console.log(arr3);

How can I Merge two Lists of custom objects of a Different Type using Java 8

In order to achieve that, firstly, you can create two maps based on the two lists using id as a key.

Then create a stream over the key sets of these maps. Then inside the map() operation you need to create a new Employee object for every key by using passing a name extracted from the employeeList city taken from the personById .

When id is not present in either of the maps the object returned by get() will be null and attempt to invoke method on it will triger the NullPointerException. In order to handle this situation, we can make use of Null-object pattern, by defining two variables that could be safely accessed and will be provided as an argument to getOfDefault().

Then collect the stream element into a list with Collectors.toList().

public static void main(String[] args) {
List<Employee> employeeList = Stream.of(
new Employee("100","Alex",""),
new Employee("200","Rida",""),
new Employee("300","Ganga",""))
.collect(Collectors.toList());

List<Person> personList = Stream.of(
new Person("100","Atlanta"),
new Person("300","Boston"),
new Person("400","Pleasanton"))
.collect(Collectors.toList());

Map<String, Employee> employeeById = employeeList.stream()
.collect(Collectors.toMap(Employee::getId, Function.identity()));

Map<String, Person> personById = personList.stream()
.collect(Collectors.toMap(Person::getId, Function.identity()));

Person nullPerson = new Person("", null); // null-object
Employee nullEmployee = new Employee("", null, null); // null-object

List<Employee> result = Stream.concat(employeeById.keySet().stream(),
personById.keySet().stream())
.distinct() // eliminating duplicated keys
.map(key -> new Employee(key,
employeeById.getOrDefault(key, nullEmployee).getName(),
personById.getOrDefault(key, nullPerson).getCity()))
.collect(Collectors.toList());

result.forEach(System.out::println);
}

Output

Employee{id='100', name='Alex', city='Atlanta'}
Employee{id='200', name='Rida', city='null'}
Employee{id='300', name='Ganga', city='Boston'}
Employee{id='400', name='null', city='Pleasanton'}


Related Topics



Leave a reply



Submit