Python의 zip 함수와 동등한 Javascript


216

파이썬의 zip 함수와 동등한 자바 스크립트가 있습니까? 즉, 길이가 같은 여러 배열이 주어지면 쌍 배열을 만듭니다.

예를 들어, 다음과 같은 3 개의 배열이있는 경우 :

var array1 = [1, 2, 3];
var array2 = ['a','b','c'];
var array3 = [4, 5, 6];

출력 배열은 다음과 같아야합니다.

var output array:[[1,'a',4], [2,'b',5], [3,'c',6]]

5
우리 파이썬 프로그래머가 루프를 포함하는 멍청한 메소드가 느리기 때문에 '무서워'라고 말하는 것이 공정한 일이므로 항상 내장 된 메소드를 찾습니다. 그러나 Javascript에서는 특별히 느리지 않기 때문에 루프를 작성해야합니다.
— LondonRob

3
@LondonRob 루프는 '빠른'메소드 뒤에 숨겨져 있는지 여부입니다. 자바 스크립트를 확실히 배열의 도입과 함께, 고차 기능에 대한 더 많은 지원을 받고있다 forEach, reduce, map, every, 그것은 단지의 경우와 등 그 zip(a는 "컷을"하지 않았다 flatMap또한 결석)이 아닌 성능 고려 사항 -하지만 공정하게 말하면 .NET (3.5)에는 몇 년 동안 Enumerable에 Zip이 없었습니다! 밑줄 / lodash (lodash 3.x는 느린 시퀀스 평가를 가짐)와 같은 '기능적'라이브러리는 동등한 zip 기능을 제공합니다.
— user2864740

@ user2864740 해석 된 루프 (예 : Python)는 항상 기계 코드 루프보다 훨씬 느립니다. JIT 컴파일 루프 (예 : 최신 JS 엔진과 같은)는 기본 CPU 속도에 근접 할 수 있으므로 기계 코드 루프를 사용하여 발생하는 게인은 익명 함수 호출의 오버 헤드로 상쇄 될 수 있습니다. 그럼에도 불구하고 이러한 내장 함수를 갖고 여러 JS 엔진으로 여러 "내부 루프"변형을 프로파일 링하는 것이 좋습니다. 결과가 명확하지 않을 수 있습니다.
— Tobia

답변:


178

2016 년 업데이트 :

snazzier Ecmascript 6 버전은 다음과 같습니다.

zip= rows=>rows[0].map((_,c)=>rows.map(row=>row[c]))

그림 equiv. 에 파이썬 { zip(*args)} :

> zip([['row0col0', 'row0col1', 'row0col2'],
       ['row1col0', 'row1col1', 'row1col2']]);
[["row0col0","row1col0"],
 ["row0col1","row1col1"],
 ["row0col2","row1col2"]]

(그리고 FizzyTea 다음 함수 정의 파이썬처럼 행동하지만, 고지 아래 참조 ...이 때문에 자신의 역 없습니다 있도록 ES6는 가변 인수 구문 지적 zip(zip(x))것 같지 x맷 크레이머 지적한대로했지만, zip(...zip(...x))==x추천 ( 일반 파이썬에서 zip(*zip(*x))==x))

대체 정의 equiv. 에 파이썬 { zip} :

> zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]))
> zip( ['row0col0', 'row0col1', 'row0col2'] ,
       ['row1col0', 'row1col1', 'row1col2'] );
             // note zip(row0,row1), not zip(matrix)
same answer as above

(현재 ...구문에 성능 문제가있을 수 있으며 나중에는 가변적 인수와 함께 두 번째 답변을 사용하는 경우 구문 테스트를 수행 할 수 있습니다.)


oneliner는 다음과 같습니다.

function zip(arrays) {
    return arrays[0].map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}

// > zip([[1,2],[11,22],[111,222]])
// [[1,11,111],[2,22,222]]]

// If you believe the following is a valid return value:
//   > zip([])
//   []
// then you can special-case it, or just do
//  return arrays.length==0 ? [] : arrays[0].map(...)

위의 배열은 배열의 크기가 동일해야한다고 가정합니다. 또한 인수 목록이 가변적 인 Python 버전과 달리 단일 목록 인수 목록을 전달한다고 가정합니다. 이러한 "기능"을 모두 원하면 아래를 참조하십시오. 약 2 줄의 추가 코드가 필요합니다.

다음은 zip배열의 크기가 같지 않고 더 긴 배열 부분이 존재하지 않는 척하는 에지 사례에서 Python의 동작을 모방 합니다.

function zip() {
    var args = [].slice.call(arguments);
    var shortest = args.length==0 ? [] : args.reduce(function(a,b){
        return a.length<b.length ? a : b
    });

    return shortest.map(function(_,i){
        return args.map(function(array){return array[i]})
    });
}

// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222]]]

// > zip()
// []

이것은 배열이 정의되지 않은 위치를 itertools.zip_longest삽입 undefined하여 Python의 동작 을 모방합니다 .

function zip() {
    var args = [].slice.call(arguments);
    var longest = args.reduce(function(a,b){
        return a.length>b.length ? a : b
    }, []);

    return longest.map(function(_,i){
        return args.map(function(array){return array[i]})
    });
}

// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222],[null,null,333]]

// > zip()
// []

이 마지막 두 버전 (다양한 여러 인수 버전)을 사용하는 경우 zip은 더 이상 고유 한 역이 아닙니다. zip(*[...])파이썬 의 관용구 를 모방하려면 zip.apply(this, [...])zip 함수를 반전시키고 싶거나 가변 수의 목록을 입력으로하고 싶을 때해야합니다.


부록 :

이 핸들을 반복 가능하게 만들려면 (예 : Python zip에서 문자열, 범위, 맵 객체 등에서 사용할 수 있음) 다음을 정의 할 수 있습니다.

function iterView(iterable) {
    // returns an array equivalent to the iterable
}

당신이 쓰는 그러나 경우 zip다음의 방법으로 , 심지어는 필요하지 않을 것입니다 :

function zip(arrays) {
    return Array.apply(null,Array(arrays[0].length)).map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}

데모:

> JSON.stringify( zip(['abcde',[1,2,3,4,5]]) )
[["a",1],["b",2],["c",3],["d",4],["e",5]]

(또는 range(...)이미 파이썬 스타일 함수를 작성했다면 사용할 수 있습니다 . 결국 ECMAScript 배열 이해 또는 생성기를 사용할 수 있습니다.)


1
이것은 나를 위해 작동하지 않습니다 : TypeError : Object 1에는 'map'메소드가 없습니다
— Emanuele Paolini

7
variadic args 및 iterable을위한 ES6 :zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
— 1983

"객체 1에는 'map'방법이 없습니다"는 아마도이 게시물의 부록에서 다루는 맵 방법 (예 : 노드 목록 또는 문자열)이없는 객체에서 이것을 사용하려는 경우 일 것입니다
— ninjagecko

variadic ES6 버전이 보존되지 않는 것은 사실이지만 zip(zip(x)) = x, 여전히 확신을 가질 수 zip(...zip(...x)) = x있습니다.
— 매트 Kramer

const the_longest_array_length = Math.max(...(arrays.map(array => array.length)));
— Константин Ван

34

Underscore 라이브러리를 확인하십시오 .

Underscore는 맵, 필터링, 호출 등 자주 사용하는 기능 지원 도우미 (함수 바인딩, 자바 스크립트 템플릿, 빠른 인덱스 생성, 심도 평등 테스트 등)를 지원하는 100 가지가 넘는 기능을 제공합니다.

– 만든 사람들에게

나는 최근에 그 zip()기능을 위해 특별히 그것을 사용하기 시작했고 그것은 큰 첫인상을 남겼습니다. jQuery와 CoffeeScript를 사용하고 있으며 완벽하게 사용됩니다. 밑줄은 그들이 떠난 곳에서 바로 집어 들고 지금까지 나를 실망시키지 않았습니다. 그건 그렇고, 그것은 단지 3kb 축소되었습니다.

확인 해봐:

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]

3
Underscore를 사용할 때, Haskell의 명확성과 논리적 안락함에 약간 더 가깝게 느껴집니다.
— CamilB

12
대신 밑줄로,이 시도 : lodash.com - 드롭 인 (drop-in) 교체, 같은 좋은 맛, 더 많은 기능, 더 크로스 브라우저의 일관성, 더 나은 반환 한. 설명 은 kitcambridge.be/blog/say-hello-to-lo-dash 를 참조하십시오 .
— 멀린 모건-그레이엄

16

ninjagecko의 우수하고 포괄적 인 답변 외에도 두 개의 JS 배열을 "tuple-mimic"로 압축하는 데 필요한 모든 것은 다음과 같습니다.

//Arrays: aIn, aOut
Array.prototype.map.call( aIn, function(e,i){return [e, aOut[i]];})

설명 :
Javascript에는 tuples유형이 없으므로 언어 ​​스펙에서 튜플, 목록 및 세트에 대한 기능이 우선 순위가 높지 않았습니다.
그렇지 않으면, JS> 1.6의 배열 맵을 통해 유사한 동작에 간단한 방식으로 액세스 할 수 있습니다 . ( map실제로 많은> JS 1.4 엔진에서 JS 엔진 제조업체가 실제로 구현하지는 않지만).
파이썬의 zip,, izip... 와의 주요 차이점 은 함수 인수를 필요로하기 map때문에의 함수 스타일 에서 나온 것 map입니다. 또한 Array-instance 의 기능입니다 . Array.prototype.map입력에 대한 추가 선언이 문제인 경우 대신 사용할 수 있습니다 .

예:

_tarrin = [0..constructor, function(){}, false, undefined, '', 100, 123.324,
         2343243243242343242354365476453654625345345, 'sdf23423dsfsdf',
         'sdf2324.234dfs','234,234fsf','100,100','100.100']
_parseInt = function(i){return parseInt(i);}
_tarrout = _tarrin.map(_parseInt)
_tarrin.map(function(e,i,a){return [e, _tarrout[i]]})

결과:

//'('+_tarrin.map(function(e,i,a){return [e, _tarrout[i]]}).join('),\n(')+')'
>>
(function Number() { [native code] },NaN),
(function (){},NaN),
(false,NaN),
(,NaN),
(,NaN),
(100,100),
(123.324,123),
(2.3432432432423434e+42,2),
(sdf23423dsfsdf,NaN),
(sdf2324.234dfs,NaN),
(234,234fsf,234),
(100,100,100),
(100.100,100)

관련 성능 :

map오버 for루프 사용 :

참조 : [[1,7], [2,8]에 [1,2] 및 [7,8]을 병합하는 가장 효율적인 방법은 무엇입니까

우편 테스트

참고 : 기본 유형과 같은 false및 undefined에 노출되지 않도록 따라서 프로토 타입 객체 계층 구조를 posess하지 않는 toString기능. 따라서 출력에서 ​​빈 것으로 표시됩니다.
마찬가지로 parseInt두번째 인수까지의 숫자를 변환되는베이스 / 기수 번호가 있으며,이 때문에 map인수 함수의 두번째 인수 인덱스를 통과 래퍼 함수가 사용된다.


첫 번째 예는 "aIn은 함수가 아닙니다"라고 말합니다. 프로토 타입 대신 배열에서 .map을 호출하면 작동합니다. aIn.map(function(e, i) {return [e, aOut[i]];})무엇입니까?
— Noumenon

1
@ Noumenon, Array.prototype.map이어야했습니다 Array.prototype.map.call. 답을 수정했습니다.
— 사용자

11

발전기가있는 최신 ES6 예 :

function *zip (...iterables){
    let iterators = iterables.map(i => i[Symbol.iterator]() )
    while (true) {
        let results = iterators.map(iter => iter.next() )
        if (results.some(res => res.done) ) return
        else yield results.map(res => res.value )
    }
}

먼저, 우리는 iterables리스트를 얻는다 iterators. 이것은 일반적으로 투명하게 발생하지만 여기서는 하나가 소진 될 때까지 단계별로 산출하므로 명시 적으로 수행합니다. .some()주어진 배열에서 (메소드를 사용하여 ) 결과 가 소진되었는지 확인하고, 그렇다면 루프를 중단합니다.


이 답변은 더 많은 설명을 사용할 수 있습니다.
— cmaher

1
이터 러블에서 이터레이터 목록을 얻습니다. 이것은 일반적으로 투명하게 발생합니다. 여기서 우리는 명시 적으로 수행합니다. 중 하나가 고갈 될 때까지 단계별로 산출합니다. 배열의 .some () 메소드 중 하나가 소진되었는지 확인하고 그렇다면 중단합니다.
— Dimitris

11

함께 다른 파이썬과 같은 기능 pythonic제공하는 zip평가 게으른 반환의 추가 혜택과 기능, Iterator그것의 동작에 유사한, 파이썬의 대응 :

import {zip, zipLongest} from 'pythonic';

const arr1 = ['a', 'b'];
const arr2 = ['c', 'd', 'e'];
for (const [first, second] of zip(arr1, arr2))
    console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d

for (const [first, second] of zipLongest(arr1, arr2))
    console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d
// first: undefined, second: e

// unzip
const [arrayFirst, arraySecond] = [...zip(...zip(arr1, arr2))];

공개 저는 Pythonic의 저자이자 관리자입니다


7

파이썬에는 zip과 itertools.zip_longest의 두 가지 기능이 있습니다. JS / ES6에서의 구현은 다음과 같습니다 :

JS / ES6에서 Python의 zip 구현

const zip = (...arrays) => {
    const length = Math.min(...arrays.map(arr => arr.length));
    return Array.from({ length }, (value, index) => arrays.map((array => array[index])));
};

결과 :

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    [11, 221]
));

[[1, 667, 111, 11]]

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111, 212, 323, 433, '1111']
));

[[1, 667, 111], [2, false, 212], [3, -378, 323], [ 'a', '337', 433]]

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[]

JS / ES6에서 Python의 zip_longest 구현

( https://docs.python.org/3.5/library/itertools.html?highlight=zip_longest#itertools.zip_longest )

const zipLongest = (placeholder = undefined, ...arrays) => {
    const length = Math.max(...arrays.map(arr => arr.length));
    return Array.from(
        { length }, (value, index) => arrays.map(
            array => array.length - 1 >= index ? array[index] : placeholder
        )
    );
};

결과 :

console.log(zipLongest(
    undefined,
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1, 667, 111, undefined], [2, false, undefined, undefined],
[3, -378, undefined, undefined], [ 'a', '337', undefined, undefined]]

console.log(zipLongest(
    null,
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1, 667, 111, null], [2, false, null, null], [3, -378, null, null], [ 'a', '337', null, null]]

console.log(zipLongest(
    'Is None',
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1, 667, 111, 'Is None'], [2, false, 'Is None', 'Is None'],
[3, -378, 'Is None', 'Is None'], [ 'a ','337 ','없음 ','없음 ']]


4

ES6를 사용하여 유틸리티 기능을 만들 수 있습니다.

const zip = (arr, ...arrs) => {
  return arr.map((val, i) => arrs.reduce((a, arr) => [...a, arr[i]], [val]));
}

// example

const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];

console.log(zip(array1, array2));                  // [[1, 'a'], [2, 'b'], [3, 'c']]
console.log(zip(array1, array2, array3));          // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]

그러나, 위의 솔루션에서 첫 번째 배열의 길이는 출력 배열의 길이를 정의합니다.

여기에 더 많은 제어권이있는 솔루션이 있습니다. 조금 복잡하지만 그만한 가치가 있습니다.

function _zip(func, args) {
  const iterators = args.map(arr => arr[Symbol.iterator]());
  let iterateInstances = iterators.map((i) => i.next());
  ret = []
  while(iterateInstances[func](it => !it.done)) {
    ret.push(iterateInstances.map(it => it.value));
    iterateInstances = iterators.map((i) => i.next());
  }
  return ret;
}
const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];

const zipShort = (...args) => _zip('every', args);

const zipLong = (...args) => _zip('some', args);

console.log(zipShort(array1, array2, array3)) // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]
console.log(zipLong([1,2,3], [4,5,6, 7]))
// [
//  [ 1, 4 ],
//  [ 2, 5 ],
//  [ 3, 6 ],
//  [ undefined, 7 ]]


4

1. Npm 모듈 : zip-array

자바 스크립트 버전의 파이썬으로 사용할 수있는 npm 모듈을 찾았습니다 zip.

zip-array- Python의 zip 함수와 동등한 Javascript입니다. 각 배열의 값을 병합합니다.

https://www.npmjs.com/package/zip-array

2. tf.data.zip()Tensorflow.js에서

또 다른 대안은 Tensorflow.js 사용자를위한 것 zip입니다. Javascript에서 tensorflow 데이터 세트로 작업하기 위해 Python 의 함수 가 필요한 경우 Tensorflow.js에서 사용할 수 있습니다 tf.data.zip().

여기에 문서화 된 Tensorflow.js의 tf.data.zip ()


3

Javascript 자체에는 내장되어 있지 않습니다. 일반적인 Javascript 프레임 워크 (예 : 프로토 타입) 중 일부는 구현을 제공하거나 직접 작성할 수 있습니다.


1
링크? 또한, jQuery가 그렇게했다면 더 관심이있을 것입니다. 왜냐하면 제가 사용하고있는 것이기 때문입니다
— .


2
그러나 jQuery 하나는 Python이 아닌 객체를 반환하므로 배열이 아닌 객체를 반환하므로 두 목록을 함께 압축 할 수 없다는 점에서 Python과 약간 다르게 작동합니다.
— Amber

저자는 jQuery를 동등한 것으로 호출해서는 안된다.
— pq.

3

@Brandon과 마찬가지로 Underscore 의 zip 기능을 권장 합니다. 그러나 가장 긴 입력 길이를 반환하는 데 필요한 값을 zip_longest추가 하는 것처럼 작동 undefined합니다.

이 mixin메소드를 사용하여 라이브러리의 자체 소스를 기반으로 zipShortestPython과 같은 역할 을하는 밑줄을 확장 했습니다 .zipzip

당신은 당신의 일반적인 JS 코드에 다음을 추가하고 밑줄의 일부인 것처럼 다음을 호출 할 수 _.zipShortest([1,2,3], ['a'])반환 [[1, 'a']]예를 들어,.

// Underscore library addition - zip like python does, dominated by the shortest list
//  The default injects undefineds to match the length of the longest list.
_.mixin({
    zipShortest : function() {
        var args = Array.Prototype.slice.call(arguments);
        var length = _.min(_.pluck(args, 'length')); // changed max to min
        var results = new Array(length);
        for (var i = 0; i < length; i++) {
            results[i] = _.pluck(args, "" + i);
        }
        return results;
}});

의견없이 공감? 이 답변을 개선하게되어 기쁘지만 피드백 없이는 할 수 없습니다.
— Pat

2

내부 배열의 인덱스 결과를 취하여 배열 배열을 줄이고 새 배열을 매핑 할 수 있습니다.

var array1 = [1, 2, 3],
    array2 = ['a','b','c'],
    array3 = [4, 5, 6],
    array = [array1, array2, array3],
    transposed = array.reduce((r, a) => a.map((v, i) => (r[i] || []).concat(v)), []);

console.log(transposed);


1

게으른 발전기 솔루션 의 변형 :

function* iter(it) {
    yield* it;
}

function* zip(...its) {
    its = its.map(iter);
    while (true) {
        let rs = its.map(it => it.next());
        if (rs.some(r => r.done))
            return;
        yield rs.map(r => r.value);
    }
}

for (let r of zip([1,2,3], [4,5,6,7], [8,9,0,11,22]))
    console.log(r.join())

// the only change for "longest" is some -> every

function* zipLongest(...its) {
    its = its.map(iter);
    while (true) {
        let rs = its.map(it => it.next());
        if (rs.every(r => r.done))
            return;
        yield rs.map(r => r.value);
    }
}

for (let r of zipLongest([1,2,3], [4,5,6,7], [8,9,0,11,22]))
    console.log(r.join())

그리고 이것은 파이썬의 고전적인 "n-group"관용구입니다 zip(*[iter(a)]*n).

triples = [...zip(...Array(3).fill(iter(a)))]

나는 이것에 어떤 문제가 있는지 궁금해한다. 나는 똑같은 것을 썼다. 나에게 그것은 다른 모든 것보다 기분이 좋지만 어쩌면 우리 둘 다 잘못되었을 수도 있습니다 ... 흐름 유형을 추가하는 방법을 찾고 있었지만 어려움을 겪고 있습니다 : D.
— cglacet

0

Mochikit 도서관이 많은 다른 기능 파이썬처럼 제공한다. Mochikit 개발자는 Python 팬이기도하므로 일반적인 스타일의 Python을 가지고 있으며 비동기 호출을 꼬인 프레임 워크로 포장합니다.


0

위에 게시 된 플러그인이 어떻게 작업을 완료했는지 궁금해하는 순수 JS에서 이것을 실행했습니다. 여기 내 결과가 있습니다. 나는 이것이 IE 등에서 얼마나 안정적인지 전혀 모른다고 말함으로써 이것을 서두를 것입니다. 그것은 단지 빠른 모형입니다.

init();

function init() {
    var one = [0, 1, 2, 3];
    var two = [4, 5, 6, 7];
    var three = [8, 9, 10, 11, 12];
    var four = zip(one, two, one);
    //returns array
    //four = zip(one, two, three);
    //returns false since three.length !== two.length
    console.log(four);
}

function zip() {
    for (var i = 0; i < arguments.length; i++) {
        if (!arguments[i].length || !arguments.toString()) {
            return false;
        }
        if (i >= 1) {
            if (arguments[i].length !== arguments[i - 1].length) {
                return false;
            }
        }
    }
    var zipped = [];
    for (var j = 0; j < arguments[0].length; j++) {
        var toBeZipped = [];
        for (var k = 0; k < arguments.length; k++) {
            toBeZipped.push(arguments[k][j]);
        }
        zipped.push(toBeZipped);
    }
    return zipped;
}

방탄은 아니지만 여전히 흥미 롭습니다.


jsfiddle이 좋아 보인다. TidyUp 버튼이 있습니다! 실행 단추가 결과 패널에 console.log 출력을 표시하지 않았습니다. 왜?
— pq.

(console.log)를 실행하려면 Firebug와 같은 것이 필요합니다. 로 전환 console.log하십시오 alert.

결과 창은 무엇입니까?
— pq.

바이올린의 HTML을 보여줍니다. 이 경우, 나는 그냥 JS를하고 있습니다. 다음은 document.write() jsfiddle.net/PyTWw/5를

-1

이것은 Ddi 의 반복자 기반 답변을 줄입니다.

function* zip(...toZip) {
  const iterators = toZip.map((arg) => arg[Symbol.iterator]());
  const next = () => toZip = iterators.map((iter) => iter.next());
  while (next().every((item) => !item.done)) {
    yield toZip.map((item) => item.value);
  }
}

-1

ES6에 문제가 없다면 :

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.