Given this array, containing javascript objects (json):
Each object has a b
property, and a u
property,
(each contains additional properties I am not concerned with for this exercise).
[
{ "b": "A", "u": "F", ... },
{ "b": "M", "u": "T", ... },
{ "b": "A", "u": "F", ... },
{ "b": "M", "u": "T", ... },
{ "b": "M", "u": "T", ... },
{ "b": "X", "u": "Y", ... },
{ "b": "X", "u": "G", ... },
]
I would like to use ramda to find a set of all the duplicates. The result should look something like this.
[
{ "b": "A", "u":"F" },
{ "b": "M", "u":"T" }
]
These two entries have duplicates they are repeated 2 and 3 times in the original list respectively.
edit
I have found a solution using underscore, that keeps the original array elements, and splits them perfectly into singles and duplicates. I prefer ramda.js, and underscore doesn't just give a set of duplicates - as per the question, so I am leaving the question open until someone can answer using ramda. I am moving on with underscore until the question is answered.
I have a repl that finds the unique values... as a start...
Given this array, containing javascript objects (json):
Each object has a b
property, and a u
property,
(each contains additional properties I am not concerned with for this exercise).
[
{ "b": "A", "u": "F", ... },
{ "b": "M", "u": "T", ... },
{ "b": "A", "u": "F", ... },
{ "b": "M", "u": "T", ... },
{ "b": "M", "u": "T", ... },
{ "b": "X", "u": "Y", ... },
{ "b": "X", "u": "G", ... },
]
I would like to use ramda to find a set of all the duplicates. The result should look something like this.
[
{ "b": "A", "u":"F" },
{ "b": "M", "u":"T" }
]
These two entries have duplicates they are repeated 2 and 3 times in the original list respectively.
edit
I have found a solution using underscore, that keeps the original array elements, and splits them perfectly into singles and duplicates. I prefer ramda.js, and underscore doesn't just give a set of duplicates - as per the question, so I am leaving the question open until someone can answer using ramda. I am moving on with underscore until the question is answered.
I have a repl that finds the unique values... as a start...
Share Improve this question edited Sep 6, 2017 at 9:40 Jim asked Sep 5, 2017 at 17:19 JimJim 16.1k17 gold badges89 silver badges174 bronze badges 3-
Do you want plete duplicates or only those that match on
b
andu
? – Scott Sauyet Commented Sep 5, 2017 at 19:25 - no, only the matching fields, "b" and "u" - though for interest - it would be nice to know. I suspect that R.equals might cater for all being equal. – Jim Commented Sep 5, 2017 at 19:55
- 1 I have spent a whole lot more time trying to resolve this using R.head, and R.tail - have e to the conclusion that this is a m*** of a tricksy question... Trying to somehow iterate over the unique list, and remove one match from the data for each match in unique seems like the right approach... but I haven't managed to get the position correct yet. – Jim Commented Sep 6, 2017 at 12:47
4 Answers
Reset to default 3This seems overplicated and unlikely to be performant, but one options would be this:
const foo = pipe(
project(['b', 'u']),
reduce(
({results, foundOnce}, item) => contains(item, results)
? {results, foundOnce}
: contains(item, foundOnce)
? {results: append(item, results), foundOnce}
: {results, foundOnce: append(item, foundOnce)},
{results: [], foundOnce: []}
),
prop('results')
)
foo(xs); //=> [{b: 'A', u: 'F'}, {b: 'M', u: 'T'}]
Perhaps this version is easier to understand, but it takes an extra iteration through the data:
const foo = pipe(
project(['b', 'u']),
reduce(
({results, foundOnce}, item) => contains(item, foundOnce)
? {results: append(item, results), foundOnce}
: {results, foundOnce: append(item, foundOnce)},
{results: [], foundOnce: []}
),
prop('results'),
uniq
)
repl here
If you don't care about looping over your data multiple times, you could something like this:
- Create partial copies that contain only the relevant props, using
pick
(your own idea) - use
groupBy
with ahash
function to group similar objects. (Alternatively:sort
first and usegroupWith(equals)
) - Get the grouped arrays using
values
- Filter out arrays with only 1 item (those are not duped...) using
filter
- Map over the results and return the first element of each array using
map(head)
In code:
const containsMoreThanOne = pose(lt(1), length);
const hash = JSON.stringify; // Naive.. watch out for key-order!
const getDups = pipe(
map(pick(["b", "u"])),
groupBy(hash),
values,
filter(containsMoreThanOne),
map(head)
);
getDups(data);
Working demo in Ramda REPL.
A more hybrid approach would be to cramp all this logic in one reducer, but it looks kind of messy to me...
const clean = pick(["b", "u"]);
const hash = JSON.stringify;
const dupReducer = hash => (acc, o) => {
const h = hash(o);
// Mutate internal state
acc.done[h] = (acc.done[h] || 0) + 1;
if (acc.done[h] === 2) acc.result.push(o);
return acc;
};
const getDups = (clean, hash, data) =>
reduce(dupReducer(hash), { result: [], done: { } }, map(clean, data)).result;
getDups(clean, hash, data);
REPL
const arr = [];
const duplicates = [];
const values1 = [
{ b: 'A', u: 'F', a: 'q' },
{ b: 'M', u: 'T', a: 'q' },
{ b: 'A', u: 'F', a: 'q' },
{ b: 'M', u: 'T', a: 'q' },
{ b: 'M', u: 'T', a: 'q' },
{ b: 'X', u: 'Y', a: 'q' },
{ b: 'X', u: 'G', a: 'q' },
];
values1.forEach(eachValue => {
arr.push(values(pick(['b', 'u'], eachValue)));
});
arr.forEach(fish => {
if ( indexOf(fish, arr) !== lastIndexOf(fish, arr) ) {
duplicates.push(zipObj(['b', 'u'], fish));
}
});
[blog]: https://ramdafunctionsexamples./ "click here for updates"
<https://ramdafunctionsexamples./>?
Not an expert with Ramda JS but I think the following should work :
var p = [
{ "b": "A", "u": "F" },
{ "b": "A", "u": "F" },
{ "b": "A", "u": "F" },
{ "b": "A", "u": "F" },
{ "b": "A", "u": "F" },
{ "b": "M", "u": "T" }
];
var dupl = n => n > 1;
R.pose(
R.map(JSON.parse),
R.keys,
R.filter(dupl),
R.countBy(String),
R.map(JSON.stringify)
)(p)
Please let me know if it does.