I have a list of strings which I send to a queue. I need to split up the list so that I end up with a list of lists where each list contains a maximum (user defined) number of strings. So for example, if I have a list with the following A,B,C,D,E,F,G,H,I and the max size of a list is 4, I want to end up with a list of lists where the first list item contains: A,B,C,D, the second list has: E,F,G,H and the last list item just contains: I. I have looked at the “TakeWhile” function but am not sure if this is the best approach. Any solution for this?
You can set up a List<IEnumerable<string>> and then use Skip and Take to split the list:
IEnumerable<string> allStrings = new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I" };
List<IEnumerable<string>> listOfLists = new List<IEnumerable<string>>();
for (int i = 0; i < allStrings.Count(); i += 4)
{
listOfLists.Add(allStrings.Skip(i).Take(4));
}
Now listOfLists will contain, well, a list of lists.
/// <summary>
/// Splits a <see cref="List{T}"/> into multiple chunks.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list">The list to be chunked.</param>
/// <param name="chunkSize">The size of each chunk.</param>
/// <returns>A list of chunks.</returns>
public static List<List<T>> SplitIntoChunks<T>(List<T> list, int chunkSize)
{
if (chunkSize <= 0)
{
throw new ArgumentException("chunkSize must be greater than 0.");
}
List<List<T>> retVal = new List<List<T>>();
int index = 0;
while (index < list.Count)
{
int count = list.Count - index > chunkSize ? chunkSize : list.Count - index;
retVal.Add(list.GetRange(index, count));
index += chunkSize;
}
return retVal;
}
Reference: http://www.chinhdo.com/20080515/chunking/
Some related reading:
Split a collection into `n` parts with LINQ?
Split List into Sublists with LINQ
LINQ Partition List into Lists of 8 members
Otherwise, minor variation on accepted answer to work with enumerables (for lazy-loading and processing, in case the list is big/expensive). I would note that materializing each chunk/segment (e.g. via .ToList or .ToArray, or simply enumerating each chunk) could have sideeffects -- see tests.
Methods
// so you're not repeatedly counting an enumerable
IEnumerable<IEnumerable<T>> Chunk<T>(IEnumerable<T> list, int totalSize, int chunkSize) {
int i = 0;
while(i < totalSize) {
yield return list.Skip(i).Take(chunkSize);
i += chunkSize;
}
}
// convenience for "countable" lists
IEnumerable<IEnumerable<T>> Chunk<T>(ICollection<T> list, int chunkSize) {
return Chunk(list, list.Count, chunkSize);
}
IEnumerable<IEnumerable<T>> Chunk<T>(IEnumerable<T> list, int chunkSize) {
return Chunk(list, list.Count(), chunkSize);
}
Test (Linqpad)
(note: I had to include the Assert methods for linqpad)
void Main()
{
var length = 10;
var size = 4;
test(10, 4);
test(10, 6);
test(10, 2);
test(10, 1);
var sideeffects = Enumerable.Range(1, 10).Select(i => {
string.Format("Side effect on {0}", i).Dump();
return i;
});
"--------------".Dump("Before Chunking");
var result = Chunk(sideeffects, 4);
"--------------".Dump("After Chunking");
result.Dump("SideEffects");
var list = new List<int>();
foreach(var segment in result) {
list.AddRange(segment);
}
list.Dump("After crawling");
var segment3 = result.Last().ToList();
segment3.Dump("Last Segment");
}
// test
void test(int length, int size) {
var list = Enumerable.Range(1, length);
var c1 = Chunk(list, size);
c1.Dump(string.Format("Results for [{0} into {1}]", length, size));
Assert.AreEqual( (int) Math.Ceiling( (double)length / (double)size), c1.Count(), "Unexpected number of chunks");
Assert.IsTrue(c1.All(c => c.Count() <= size), "Unexpected size of chunks");
}
Related
I have BLoC with the following state which contains 2 words ,
// 2 words are fixed and same length.
// in word_state.dart
abstract class WordState extends Equatable
const WordState(this.quest, this.answer, this.word, this.clicked);
final List<String> wordA; // WordA = ['B','A','L',L']
final List<String> wordB; // WordB = ['','','','']
#override
List<Object> get props => [wordA,wordB];
}
I want to ADD and REMOVE letters.
// in word_event.dart
class AddLetter extends WordEvent {
final int index;
const AddLetter(this.index);
}
class RemoveLetter extends WordEvent {
final int index;
const RemoveLetter(this.index);
}
1.ADD:
If I select the index of 'L' in wordA, then I add the letter 'L' in the first occurrence of '' (empty) in wordB.
// in word_bloc.dart
void _onLetterAdded(AddLetter event, Emitter<WordState> emit) {
final b = [...state.wordB];
b[b.indexOf('')] = state.wordA[event.index];
emit(WordLoaded(state.wordA, b));
}
//wordB, ['','','',''] into ['L','','','']
2.REMOVE:
If I deselect the index of 'L' in wordA, then I remove the last occurence of letter 'L' in wordB and shift the right side letters to left
void _onLetterRemoved(RemoveLetter event, Emitter<WordState> emit) {
final b = [...state.wordB];
final index = b.lastIndexOf(state.wordA[event.index]);
for (int i = index; i < 4 - 1; i++) {
b[i] = b[i + 1];
}
b[3] = '';
emit(WordLoaded(state.wordA, b));
}
}
// What i am trying to
// ['B','L','A','L']
// if index is 1 then ['B','A','L','']
This code is working fine, But I want to do the list operations in efficient way.
can you please check this code understand it and run it on dart pad.
List<String> wordA=['B','A','L','L'];
List<String> wordB=['','','',''];
List<String> wordBAll=['B','L','A','L'];
void main() {
wordB.insert(0,wordA[2]);
wordBAll.removeAt(wordBAll.length-1);
print(wordB);
print(wordBAll);
}
This question already has answers here:
How do I split or chunk a list into equal parts, with Dart?
(21 answers)
Closed 11 months ago.
I have some Lists containing only strings, every string has an equal length. I want to convert these lists into a list that must have 4 items on each list.
For ex.
const _a = [
'0330',
'0355',
'0405',
'0415',
'0425',
'0450',
'0500',
'0525',
'0535',
'0545',
'0555',
'0620',
'0630',
'0655',
'0705',
'0715',
'0725',
'0750',
'0800',
'0845',
];
The result I want is like this...
const _a = [
['0330', '0355', '0405', '0415'],
['0425', '0450', '0500', '0525'],
['0535', '0545', '0555', '0620'],
['0630', '0655', '0705', '0715'],
['0725', '0750', '0800', '0845'],
];
run paritition(_a, 4) and get the result
import 'dart:collection';
/// Partitions the input iterable into lists of the specified size.
Iterable<List<T>> partition<T>(Iterable<T> iterable, int size) {
return iterable.isEmpty ? [] : _Partition<T>(iterable, size);
}
class _Partition<T> extends IterableBase<List<T>> {
_Partition(this._iterable, this._size) {
if (_size <= 0) throw ArgumentError(_size);
}
final Iterable<T> _iterable;
final int _size;
#override
Iterator<List<T>> get iterator =>
_PartitionIterator<T>(_iterable.iterator, _size);
}
class _PartitionIterator<T> implements Iterator<List<T>> {
_PartitionIterator(this._iterator, this._size);
final Iterator<T> _iterator;
final int _size;
List<T>? _current;
#override
List<T> get current {
return _current as List<T>;
}
#override
bool moveNext() {
var newValue = <T>[];
var count = 0;
while (count < _size && _iterator.moveNext()) {
newValue.add(_iterator.current);
count++;
}
_current = (count > 0) ? newValue : null;
return _current != null;
}
}
In my app, at many places I have used Lists like this:-
List<int> nums = [];
// initializing list dynamically with some values.
nums.length = 12; // increasing length of list
// setting these values afterward using nums[i] at different places.
Now after migrating to null-safety obviously nums.length = 4 is giving me a runtime error, so I was wondering is there any method to set the length of the list with default values such that, after if the length of the list was smaller than before then with new length extra elements are added with some default value.
Note: Of course I know we can use for loop, but I was just wondering if there is any easier and cleaner method than that.
var num = List<int>.generate(4, (i) => i);
You can read this.
Another approach:
extension ExtendList<T> on List<T> {
void extend(int newLength, T defaultValue) {
assert(newLength >= 0);
final lengthDifference = newLength - this.length;
if (lengthDifference <= 0) {
return;
}
this.addAll(List.filled(lengthDifference, defaultValue));
}
}
void main() {
var list = <int>[];
list.extend(4, 0);
print(list); // [0, 0, 0, 0];
}
Or, if you must set .length instead of calling a separate method, you could combine it with a variation of julemand101's answer to fill with a specified default value instead of with null:
class ExtendableList<T> with ListMixin<T> {
ExtendableList(this.defaultValue);
final T defaultValue;
final List<T> _list = [];
#override
int get length => _list.length;
#override
T operator [](int index) => _list[index];
#override
void operator []=(int index, T value) {
if (index >= length) {
_list.extend(index + 1, defaultValue);
}
_list[index] = value;
}
#override
set length(int newLength) {
if (newLength > length) {
_list.extend(newLength, defaultValue);
} else {
_list.length = newLength;
}
}
}
(I also made its operator []= automatically grow the ExtendableList if the specified index is out-of-bounds, similar to JavaScript.)
Your problem is that the List in Dart does not have the concept of adding more space while you promise that you are not going to use this new capacity before it is set.
But you can easily make your own List implementation which does this:
import 'dart:collection';
void main() {
List<int> nums = ExtendableList();
nums.length = 3;
nums[2] = 1;
nums[0] = 1;
nums[1] = 1;
print(nums); // [1, 1, 1]
nums.add(2);
print(nums); // [1, 1, 1, 2]
print(nums.runtimeType); // ExtendableList<int>
}
class ExtendableList<T> with ListMixin<T> {
final List<T?> _list = [];
#override
int get length => _list.length;
#override
T operator [](int index) => _list[index] as T;
#override
void operator []=(int index, T value) => _list[index] = value;
#override
set length(int newLength) => _list.length = newLength;
}
As you can see we are using a null type behind the scene but from the outside it will work like the list contains non-nullable. This only works because we assume the [] operator will not be called while a null value are in the list (which happens if we extend the list and does not set the value).
I should add that using such a List implementation does comes with great risk since you don't get any warning/error from the analyzer if you are using it wrongly.
You have to use a list of nullable element to make it longer.
List<int?> nums = [];
nums.length = 4; // OK
print(nums); // [null, null, null, null]
You can also use filled method. Here growable is false by default.
void main() {
var a = List<int>.filled(3, 0, growable: true);
print(a);
// [0, 0, 0]
}
Refer: https://api.flutter.dev/flutter/dart-core/List/List.filled.html
With the following code:
public class Main {
public static void main(String[] args) {
final List<Integer> items =
IntStream.rangeClosed(0, 23).boxed().collect(Collectors.toList());
final String s = items
.stream()
.map(Object::toString)
.collect(Collectors.joining(","))
.toString()
.concat(".");
System.out.println(s);
}
}
I get:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23.
What I would like to do, is to break the line every 10 items, in order to get:
0,1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18,19,
20,21,22,23.
I have try a lot of things after googling without any success !
Can you help me ?
Thanks,
Olivier.
If you're open to using a third-party library, the following will work using Eclipse Collections Collectors2.chunk(int).
String s = IntStream.rangeClosed(0, 23)
.boxed()
.collect(Collectors2.chunk(10))
.collectWith(MutableList::makeString, ",")
.makeString("", ",\n", ".");
The result of Collectors2.chunk(10) will be a MutableList<MutableList<Integer>>. At this point I switch from the Streams APIs to using native Eclipse Collections APIs which are available directly on the collections. The method makeString is similar to Collectors.joining(). The method collectWith is like Stream.map() with the difference that a Function2 and an extra parameter are passed to the method. This allows a method reference to be used here instead of a lambda. The equivalent lambda would be list -> list.makeString(",").
If you use just Eclipse Collections APIs, this problem can be simplified as follows:
String s = Interval.zeroTo(23)
.chunk(10)
.collectWith(RichIterable::makeString, ",")
.makeString("", ",\n", ".");
Note: I am a committer for Eclipse Collections.
If all you want to do is process these ascending numbers, you can do it like
String s = IntStream.rangeClosed(0, 23).boxed()
.collect(Collectors.groupingBy(i -> i/10, LinkedHashMap::new,
Collectors.mapping(Object::toString, Collectors.joining(","))))
.values().stream()
.collect(Collectors.joining(",\n", "", "."));
This solution can be adapted to work on an arbitrary random access list as well, e.g.
List<Integer> items = IntStream.rangeClosed(0, 23).boxed().collect(Collectors.toList());
String s = IntStream.range(0, items.size()).boxed()
.collect(Collectors.groupingBy(i -> i/10, LinkedHashMap::new,
Collectors.mapping(ix -> items.get(ix).toString(), Collectors.joining(","))))
.values().stream()
.collect(Collectors.joining(",\n", "", "."));
However, there is no simple and elegant solution for arbitrary streams, a limitation which applies to all kind of tasks having a dependency to the element’s position.
Here is an adaptation of the already linked in the comments Collector:
private static Collector<String, ?, String> partitioning(int size) {
class Acc {
int count = 0;
List<List<String>> list = new ArrayList<>();
void add(String elem) {
int index = count++ / size;
if (index == list.size()) {
list.add(new ArrayList<>());
}
list.get(index).add(elem);
}
Acc merge(Acc right) {
List<String> lastLeftList = list.get(list.size() - 1);
List<String> firstRightList = right.list.get(0);
int lastLeftSize = lastLeftList.size();
int firstRightSize = firstRightList.size();
// they are both size, simply addAll will work
if (lastLeftSize + firstRightSize == 2 * size) {
System.out.println("Perfect!");
list.addAll(right.list);
return this;
}
// last and first from each chunk are merged "perfectly"
if (lastLeftSize + firstRightSize == size) {
System.out.println("Almost perfect");
int x = 0;
while (x < firstRightSize) {
lastLeftList.add(firstRightList.remove(x));
--firstRightSize;
}
right.list.remove(0);
list.addAll(right.list);
return this;
}
right.list.stream().flatMap(List::stream).forEach(this::add);
return this;
}
public String finisher() {
return list.stream().map(x -> x.stream().collect(Collectors.joining(",")))
.collect(Collectors.collectingAndThen(Collectors.joining(",\n"), x -> x + "."));
}
}
return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher);
}
And usage would be:
String result = IntStream.rangeClosed(0, 24)
.mapToObj(String::valueOf)
.collect(partitioning(10));
Having trouble with a list function I wrote using CouchApp to take items from a view that are name, followed by a hash list of id and a value to create a CSV file for the user.
function(head, req) {
// set headers
start({ "headers": { "Content-Type": "text/csv" }});
// set arrays
var snps = {};
var test = {};
var inds = [];
// get data to associative array
while(row = getRow()) {
for (var i in row.value) {
// add individual to list
if (!test[i]) {
test[i] = 1;
inds.push(i);
}
// add to snps hash
if (snps[row.key]) {
if (snps[row.key][i]) {
// multiple call
} else {
snps[row.key][i] = row.value[i];
}
} else {
snps[row.key] = {};
snps[row.key][i] = row.value[i];
}
//send(row.key+" => "+i+" => "+snps[row.key][i]+'\n');
}
}
// if there are individuals to write
if (inds.length > 0) {
// sort keys in array
inds.sort();
// print header if first
var header = "variant,"+inds.join(",")+"\n";
send(header);
// for each SNP requested
for (var j in snps) {
// build row
var row = j;
for (var k in inds) {
// if snp[rs_num][individual] is set, add to row string
// else add ?
if (snps[j][inds[k]]) {
row = row+","+snps[j][inds[k]];
} else {
row = row+",?";
}
}
// send row
send(row+'\n');
}
} else {
send('No results found.');
}
}
If I request _list/mylist/myview (where mylist is the list function above and the view returns as described above) with ?key="something" or ?keys=["something", "another] then it works, but remove the query string and I get the error below:
{"code":500,"error":"render_error","reason":"function raised error: (new SyntaxError(\"JSON.parse\", \"/usr/local/share/couchdb/server/main.js\", 865)) \nstacktrace: getRow()#/usr/local/share/couchdb/server/main.js:865\n([object Object],[object Object])#:14\nrunList(function (head, req) {var snps = {};var test = {};var inds = [];while ((row = getRow())) {for (var i in row.value) {if (!test[i]) {test[i] = 1;inds.push(i);}if (snps[row.key]) {if (snps[row.key][i]) {} else {snps[row.key][i] = row.value[i];}} else {snps[row.key] = {};snps[row.key][i] = row.value[i];}}}if (inds.length > 0) {inds.sort();var header = \"variant,\" + inds.join(\",\") + \"\\n\";send(header);for (var j in snps) {var row = j;for (var k in inds) {if (snps[j][inds[k]]) {row = row + \",\" + snps[j][inds[k]];} else {row = row + \",?\";}}send(row + \"\\n\");}} else {send(\"No results found.\");}},[object Object],[object Array])#/usr/local/share/couchdb/server/main.js:979\n(function (head, req) {var snps = {};var test = {};var inds = [];while ((row = getRow())) {for (var i in row.value) {if (!test[i]) {test[i] = 1;inds.push(i);}if (snps[row.key]) {if (snps[row.key][i]) {} else {snps[row.key][i] = row.value[i];}} else {snps[row.key] = {};snps[row.key][i] = row.value[i];}}}if (inds.length > 0) {inds.sort();var header = \"variant,\" + inds.join(\",\") + \"\\n\";send(header);for (var j in snps) {var row = j;for (var k in inds) {if (snps[j][inds[k]]) {row = row + \",\" + snps[j][inds[k]];} else {row = row + \",?\";}}send(row + \"\\n\");}} else {send(\"No results found.\");}},[object Object],[object Array])#/usr/local/share/couchdb/server/main.js:1024\n(\"_design/kbio\",[object Array],[object Array])#/usr/local/share/couchdb/server/main.js:1492\n()#/usr/local/share/couchdb/server/main.js:1535\n#/usr/local/share/couchdb/server/main.js:1546\n"}
Can't say for sure since you gave little detail, however, a probable source of problems, is the use of arrays to collect data from every row: it consumes an unpredictable amount of memory. This may explain why it works when you query for a few records, and fails when you query for all records.
You should try to arrange data in a way that eliminates the need to collect all values before sending output to the client. And keep in mind that while map and reduce results are saved on disk, list functions are executed on every single query. If you don't keep list function fast and lean, you'll have problems.