Skip to content

Commit e41d99e

Browse files
committed
Flatten depth and behavioral differences.
`seq.flatten()` has changed in a couple ways: * Now accepts an optional `depth` argument to describe how deep it should flatten. By default it will flatten infinitely deeply. This is different from the previous behavior of always flattening shallowly. As a convenience, it also accepts boolean "shallow" (e.g., it converts booleans to numbers). * Now *only* flattens Sequence instances and not sequenceable values (which would include Array, string, anything with @@iterator). This is different from previous behavior of flattening anything that could be converted to sequence and not only instances of Sequence. * flatMap() and concat() behavior of have not changed, and still allow mapping to and concatting sequenceable values and not only Sequence instances. (Read: no changes here).
1 parent 91f9bcb commit e41d99e

File tree

6 files changed

+206
-100
lines changed

6 files changed

+206
-100
lines changed

__tests__/flatten.ts

+89-9
Original file line numberDiff line numberDiff line change
@@ -28,33 +28,113 @@ describe('flatten', () => {
2828
expect(flat.forEach(x => x < 4)).toEqual(4);
2929
})
3030

31-
it('flattens anything sequenceable', () => {
31+
it('flattens only Sequences (not sequenceables)', () => {
3232
var nested = I.Sequence(I.Range(1,3),[3,4],I.Vector(5,6,7),8);
3333
var flat = nested.flatten();
34-
expect(flat.toJS()).toEqual([1,2,3,4,5,6,7,8]);
34+
expect(flat.toJS()).toEqual([1,2,[3,4],5,6,7,8]);
3535
})
3636

3737
it('can be reversed', () => {
3838
var nested = I.Sequence(I.Range(1,3),[3,4],I.Vector(5,6,7),8);
3939
var flat = nested.flatten();
4040
var reversed = flat.reverse();
41-
expect(reversed.toJS()).toEqual([8,7,6,5,4,3,2,1]);
41+
expect(reversed.toJS()).toEqual([8,7,6,5,[3,4],2,1]);
4242
})
4343

44+
it('can flatten at various levels of depth', () => {
45+
var deeplyNested = I.fromJS(
46+
[
47+
[
48+
[
49+
[ 'A', 'B' ],
50+
[ 'A', 'B' ],
51+
],
52+
[
53+
[ 'A', 'B' ],
54+
[ 'A', 'B' ],
55+
],
56+
],
57+
[
58+
[
59+
[ 'A', 'B' ],
60+
[ 'A', 'B' ],
61+
],
62+
[
63+
[ 'A', 'B' ],
64+
[ 'A', 'B' ],
65+
]
66+
]
67+
]
68+
);
69+
70+
// deeply flatten
71+
expect(deeplyNested.flatten().toJS()).toEqual(
72+
['A','B','A','B','A','B','A','B','A','B','A','B','A','B','A','B']
73+
);
74+
75+
// shallow flatten
76+
expect(deeplyNested.flatten(true).toJS()).toEqual(
77+
[
78+
[
79+
[ 'A', 'B' ],
80+
[ 'A', 'B' ],
81+
],
82+
[
83+
[ 'A', 'B' ],
84+
[ 'A', 'B' ],
85+
],
86+
[
87+
[ 'A', 'B' ],
88+
[ 'A', 'B' ],
89+
],
90+
[
91+
[ 'A', 'B' ],
92+
[ 'A', 'B' ],
93+
]
94+
]
95+
);
96+
97+
// flatten two levels
98+
expect(deeplyNested.flatten(2).toJS()).toEqual(
99+
[
100+
[ 'A', 'B' ],
101+
[ 'A', 'B' ],
102+
[ 'A', 'B' ],
103+
[ 'A', 'B' ],
104+
[ 'A', 'B' ],
105+
[ 'A', 'B' ],
106+
[ 'A', 'B' ],
107+
[ 'A', 'B' ]
108+
]
109+
);
110+
});
111+
44112
describe('flatMap', () => {
45113

46-
it('first maps, then flattens', () => {
114+
it('first maps, then shallow flattens', () => {
47115
var numbers = I.Range(97, 100);
48-
var letters = numbers.flatMap(v => [
116+
var letters = numbers.flatMap(v => I.fromJS([
49117
String.fromCharCode(v),
50118
String.fromCharCode(v).toUpperCase(),
51-
]);
119+
]));
52120
expect(letters.toJS()).toEqual(
53121
['a','A','b','B','c','C']
54122
)
55-
})
123+
});
56124

57-
})
125+
it('maps to sequenceables, not only Sequences.', () => {
126+
var numbers = I.Range(97, 100);
127+
// map returns Array, rather than Sequence. Array is sequenceable, so this
128+
// works just fine.
129+
var letters = numbers.flatMap(v => [
130+
String.fromCharCode(v),
131+
String.fromCharCode(v).toUpperCase()
132+
]);
133+
expect(letters.toJS()).toEqual(
134+
['a','A','b','B','c','C']
135+
)
136+
});
58137

59-
})
138+
});
60139

140+
});

dist/Immutable.d.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -486,12 +486,21 @@ declare module 'immutable' {
486486
): Sequence<MK, MV>;
487487

488488
/**
489-
* Flattens nested Sequences by one level.
489+
* Flattens nested Sequences.
490490
*
491-
* Note: `flatten` operates on Sequence<any, Sequence<K, V>> and
491+
* Will deeply flatten the Sequence by default, but a `depth` can be
492+
* provided in the form of a number or boolean (where true means to
493+
* shallowly flatten one level). A depth of 0 (or shallow: false) will
494+
* deeply flatten.
495+
*
496+
* Flattens anything Sequencible (Arrays, Objects) with the exception of
497+
* Strings.
498+
*
499+
* Note: `flatten(true)` operates on Sequence<any, Sequence<K, V>> and
492500
* returns Sequence<K, V>
493501
*/
494-
flatten(): Sequence<any, any>;
502+
flatten(depth?: number): Sequence<any, any>;
503+
flatten(shallow?: boolean): Sequence<any, any>;
495504

496505
/**
497506
* Returns a new sequence with this sequences's keys as it's values, and this
@@ -949,7 +958,8 @@ declare module 'immutable' {
949958
* Returns IndexedSequence<T>
950959
* @override
951960
*/
952-
flatten(): IndexedSequence<any>;
961+
flatten(depth?: number): IndexedSequence<any>;
962+
flatten(shallow?: boolean): IndexedSequence<any>;
953963

954964
/**
955965
* If this is a sequence of entries (key-value tuples), it will return a

dist/Immutable.js

+36-33
Original file line numberDiff line numberDiff line change
@@ -478,10 +478,12 @@ var $Sequence = Sequence;
478478
return this.find(returnTrue);
479479
},
480480
flatMap: function(mapper, context) {
481-
return this.map(mapper, context).flatten();
481+
return this.map((function(v, k, c) {
482+
return $Sequence(mapper.call(context, v, k, c));
483+
})).flatten(true);
482484
},
483-
flatten: function() {
484-
return flattenFactory(this, true);
485+
flatten: function(depth) {
486+
return flattenFactory(this, depth, true);
485487
},
486488
flip: function() {
487489
return flipFactory(this);
@@ -662,8 +664,8 @@ var $IndexedSequence = IndexedSequence;
662664
first: function() {
663665
return this.get(0);
664666
},
665-
flatten: function() {
666-
return flattenFactory(this, false);
667+
flatten: function(depth) {
668+
return flattenFactory(this, depth, false);
667669
},
668670
flip: function() {
669671
return flipFactory(this.toKeyedSeq());
@@ -1443,7 +1445,7 @@ function concatFactory(sequence, values, useKeys) {
14431445
if (useKeys) {
14441446
concatSequence = concatSequence.toKeyedSeq();
14451447
}
1446-
concatSequence = concatSequence.flatten();
1448+
concatSequence = concatSequence.flatMap(valueMapper);
14471449
concatSequence.length = sequences.reduce((function(sum, seq) {
14481450
if (sum !== undefined) {
14491451
var len = Sequence(seq).length;
@@ -1454,47 +1456,48 @@ function concatFactory(sequence, values, useKeys) {
14541456
}), 0);
14551457
return concatSequence;
14561458
}
1457-
function flattenFactory(sequence, useKeys) {
1459+
function flattenFactory(sequence, depth, useKeys) {
14581460
var flatSequence = sequence.__makeSequence();
14591461
flatSequence.__iterateUncached = function(fn, reverse) {
1460-
var $__0 = this;
14611462
var iterations = 0;
1462-
sequence.__iterate((function(seq) {
1463-
var stopped = false;
1464-
Sequence(seq).__iterate((function(v, k) {
1465-
if (fn(v, useKeys ? k : iterations++, $__0) === false) {
1463+
var stopped = false;
1464+
function flatDeep(seq, currentDepth) {
1465+
var $__0 = this;
1466+
seq.__iterate((function(v, k) {
1467+
if ((!depth || currentDepth < depth) && v instanceof Sequence) {
1468+
flatDeep(v, currentDepth + 1);
1469+
} else if (fn(v, useKeys ? k : iterations++, $__0) === false) {
14661470
stopped = true;
1467-
return false;
14681471
}
1472+
return !stopped;
14691473
}), reverse);
1470-
return !stopped;
1471-
}), reverse);
1474+
}
1475+
flatDeep(sequence, 0);
14721476
return iterations;
14731477
};
14741478
flatSequence.__iteratorUncached = function(type, reverse) {
1475-
var sequenceIterator = sequence.__iterator(ITERATE_VALUES, reverse);
1476-
var iterator;
1479+
var iterator = sequence.__iterator(type, reverse);
1480+
var stack = [];
14771481
var iterations = 0;
14781482
return new Iterator((function() {
1479-
while (true) {
1480-
if (iterator) {
1481-
var step = iterator.next();
1482-
if (!step.done) {
1483-
if (useKeys || type === ITERATE_VALUES) {
1484-
return step;
1485-
} else if (type === ITERATE_KEYS) {
1486-
return iteratorValue(type, iterations++, null, step);
1487-
} else {
1488-
return iteratorValue(type, iterations++, step.value[1], step);
1489-
}
1490-
}
1483+
while (iterator) {
1484+
var step = iterator.next();
1485+
if (step.done !== false) {
1486+
iterator = stack.pop();
1487+
continue;
14911488
}
1492-
var sequenceStep = sequenceIterator.next();
1493-
if (sequenceStep.done) {
1494-
return sequenceStep;
1489+
var value = step.value;
1490+
if (type === ITERATE_ENTRIES) {
1491+
value = value[1];
1492+
}
1493+
if ((!depth || stack.length < depth) && value instanceof Sequence) {
1494+
stack.push(iterator);
1495+
iterator = value.__iterator(type, reverse);
1496+
} else {
1497+
return useKeys ? step : iteratorValue(type, iterations++, value, step);
14951498
}
1496-
iterator = Sequence(sequenceStep.value).__iterator(type, reverse);
14971499
}
1500+
return iteratorDone();
14981501
}));
14991502
};
15001503
return flatSequence;

0 commit comments

Comments
 (0)